From 4e6597baa91f17fc05c56e033e6c2133b2d1116e Mon Sep 17 00:00:00 2001 From: "Kai J." Date: Mon, 7 Oct 2024 20:56:38 +0200 Subject: [PATCH 01/19] Docs: add docker deploy documentation (#2829) --- .../deploy-docker-backend-check-version.png | Bin 0 -> 8163 bytes .../assets/images/deploy-docker-backend.png | Bin 0 -> 11314 bytes .../deploy-docker-edge-check-version.png | Bin 0 -> 4834 bytes .../ROOT/assets/images/deploy-docker-edge.png | Bin 0 -> 8516 bytes .../ROOT/assets/images/deploy-docker-ssh.png | Bin 0 -> 3581 bytes doc/modules/ROOT/pages/backend/deploy.adoc | 89 +++++++++++++++++- doc/modules/ROOT/pages/edge/deploy.adoc | 75 ++++++++++++++- 7 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 doc/modules/ROOT/assets/images/deploy-docker-backend-check-version.png create mode 100644 doc/modules/ROOT/assets/images/deploy-docker-backend.png create mode 100644 doc/modules/ROOT/assets/images/deploy-docker-edge-check-version.png create mode 100644 doc/modules/ROOT/assets/images/deploy-docker-edge.png create mode 100644 doc/modules/ROOT/assets/images/deploy-docker-ssh.png diff --git a/doc/modules/ROOT/assets/images/deploy-docker-backend-check-version.png b/doc/modules/ROOT/assets/images/deploy-docker-backend-check-version.png new file mode 100644 index 0000000000000000000000000000000000000000..351a863a1977de79f5430427b124bff0012e1ce9 GIT binary patch literal 8163 zcmai(Wl&sC(C%?}32wn%gG+D?usDRE!QFRp*WinW;2KEc92^|BvXYz@92^2F>|F{C1$Or&ahQWW;61by zW#DS2DGy;CBpc}u(r|F~iI~sk$gn=TtCFDy930NTzYBiErTjA-91T!ePFe?Oa`Fu` ziTGDmFz&$QIRSw7EiTAble!Quojg^WMVLW6@tXmLT4xo4hGSq+W5Z+;z5x8KNKYmap9 zPFBliie?^Wf?luw2r5!}>3jna$-~-3#bsh_Aq%YG=;Hi0Vi(MY`rnAa`!vLVCIIsM z@sR&aur(p?0q&*8NfSBRMDlhum-r8HIEnewB6qhwO(!j_*(FINrs6%lX{M=^-Qjly3~s&^m__p z8#}5~`^tONRZC{5Ojh~RLVNXVBA@2VuYvex;lK>p!#;AETvpDnCYIec>N(F%c1sYs z1K|UsCui9gv2l$Iu1}K(Qc#e`B=5+*KAU2T^SZGy5hH6};A6!~gH;?zzaT!=IwW=Sr1ym?PnE zUcZaS!h4WuVyG!v;&9YsjD8sY``W%WdIvoqXtX&#J9(g)`Y3g1CmCqj-BD(l$iEH%fOmke4+pxuEcA@5sF^LP zNrR@~yyEQ)nF1c1E{FBiT-}| zOlOnR)Xd~Gt|J|K-Nn+TV31K@$;0jr>ucIy^GiK~l*cfEKvl-LI-r6_X4Fs0Q8B42 z4)!Fb&dyyUWsJZy{3{;IKTb2}-1oC$lINJUyk8K5-kJW0k9F}b>l454d^x#eJG4T5 zv5brit8{85@$(=+iyChBja*u8hXy%O9JFn;y%*04CS{^jAnj-1x_)$U3fsIrb8OAK zdaZm~euT!@M6eKZV5!B5pH~G8-@U}WLFZ;O8G6HQ}0%4%>WS6w4Ij1 z?RG|sw)h78okw|>EL>SxyHt&Lwb_aP>cFq-V!wZE-c*pPJ@7_5iMwXX9LvZ}h#?aaE&Q znYxLUEnBWyAh@;bZo_Yy;e+HSw;^IK({S#9L)BNq3F*sAp`M5Mwuk+%FO}Ga?Vc4A zm1O+ZI^t4oiI?t26?*x|294J7jc9UdA5Lu$EHYagC>fO7(y#G@F0GgA%uD_5PEVzt zU++hxyxp9to2sj;UwojQ_)flt1ya+3?|T|gKO8&GWDOUb*e%9ilAnnl_ve$&YU&t5 z1@`Z&8?BO$jl;q!|K8kndgQy=%`d%54GJQtp1e?;yqMM{M4sFFon+iDPro*w2~u)h zR5iqt5aL-8yp6XJ{=&nhc3f*8OfucK`P)@*S?c$)L-QozVhD4mpK8Gr-IF6Y%l>#% zFgi4xgt*q?)#I?q>vT)?mime73wBf_A?6TWrOvOD>r;}sNPU&=CUeq&vu)ru&Crkp zOXPF|=Zfd)+RDPOUs+UQ-mU&evjt(GyLF!r zy1HpAU4AVf*c1UYoKZcmH?`LN;pkkPfQz2z%ZX0|Gk)=pQZvusOVHo?k?bWCzbV!? z;Vl7A-u$jx@A-k}W><5n+*zMGydNaRS@OF4?!51|ai~N;i+cYhHB1j<;9N>&*6@aq zl>|R6)_giXt*ogLe0jPPvdIONf?(cw>G(*497X(x>hvLdNxN($YS+I9T2pCB{ zFukUKwsf1%J5ppk7((M4t;QzaG~8p`=UWHmrO0dP+!RQXj%MC{-LX|tDToiqrC_Ub z6R#W14O)$KNyFWd-6cX{EL*?(a(Ew<>j2GP8hA6`alKRd2pZv7x#N_Qs-~~z%5A>* zjBc|CPT?*#++mCtR&0xmYjFM?VSC-naleoB263Ol^GIYItLsuid%ivjssZY?cz@;s z3*PEz^A2S~$}>2ObU40x*Y_sCZRDlpU0hh`T@sl!&YlI1zG@hB+0erlqG+To@3>Xc^RvQqkL=9#e-=5TWy?j?_Z?{tscS3*`&S5LYKM%g*B0UBQSh$5n=3PY z65{`Rl&F?hfAZAuuJv)V+co_`Ajqo52U>eqHy&pBlV5I5C z=0j-F+*O>xpayQi`w*Qor)S#Dm$q5rMS9?YJHuG`*%5w_#saY3PcGm}+Wxr2n?qz! z{~(>%pef9W?K{g8_zd*H>hCB~lk1A>ylRB)yi=uGDXhRo!C%arOa0m!62DkJJl&pL z%@}dZv!hsf2hb( z#=>MuUsOW%W(?T6Qe^rgT*$9Q{c^=NFGd*mXJ0iwv=r#KmP8^zY5c|oTKZ|(HSsb% zN9~cT(1Jr}cjGqg@w|-`zL0Jwln4T^gKf8XFBBfqOvt;+dHA|1f3b6>p9S7^$g`pd zyuVIpOWbhJgiK^~6b)A331IcP^5AC-s7(TI#?#m_Md^-u@^=m0IEz2Gyghw~vV8Yonc%h@ua}dc{hLfTwdOe4eIkJ) zw`PP<+^V_SF17GfF)=2R)s4dl_FDO0huDxhCLc{-i%zCyJV!L7d|^q20ikQo z{DjS=*PSqewIHXXCv3yG&VJI-Z%LCE=RC>ARZ9j_YW zCE@W3r6*d+U6WfIpu;8BeBOr0d{lLv+U|q+vU0ME7Qs`%}0Db z$5~^)8I^fQ|HD=5`J=P8Tya-_m!BC^%xB`}x2uulBOsBCHyA85G}V{s8=jKpRbtli zZIihc1k(iM#7w*hy93wO^Y7_@=Xg(9I!X%bQA5JZp`kSuX~Gs%XgHi^dS!I#-|xZn zQp!=2isr)hGr1D#0JC=pmX9Fq1o!p17+suY^dxjIwfB~9$tOl*>rob*?h+>L^~Z5N zXoE$lRpw_s5*yGQf!^sKjiPtOxtnVP7N}O<@y3bZ%r$ZhW95Xil+a@mvZwyk#EZVm z5%GAgj5qArph!uMadG2v7BO{*QpjjS=4+yr#n?9K(mZ^Y6WLl}!QfCvn#TNq$APNY zPZ_EO`;DV0RE#fWiU28;K;Y#)KDMgxL`>D1@B&cyB06+~Ng}~L!IY_FS;4f5%+9Qh z82oq=u5SVwqS}o_+w;}I)vAek5M{+;&9|+)r6Tko`Vu#fsSu;&U~XG&CXrNt3}V)h zqLQdB5rHuK5u23+r>b8v*OoARXp{WsgF%Ge-;X>IObmP|)9&)=OA#3zKPRRfc8o>r zJgS1Ukowxtz4+#(1e9Br{bR=OnqS@Lw&pbGoPf&74VJ&3Mwmw5=1xwQjr83!c9rZS z#7aYB65ko&(~WxV(|(38wG5Ytmjynj55KK}H|sP~ds0Kg3Z+>b9f&1%1pdTZ=?V5L z1UFiYB*!3{7txwoBxvImlT6~t5lsQ4Cka6`Se!h?ctR_hu=KDZ50DN&Zn#K;q^ez$ z=(!^#{W$n4vSE+=QWe~MHWBc_EuZ%tzeIu4?W)74Dzg@e-E##cC(&g;gE?Tf*9a&O zKy*GaCzngIu`pa6T8A<8ioD^{Z&Ac}Ds|q2UE7B3h2Xb_r#GeJM|mg`DF93tu?=c8 zCtRx9l%<=<4k!E3mvH5wG5{4`G`4?PzOJ9tC~y1jmTiBJ5;j$|Wi72cvU>2lwY~M% z@68t{O0I@7ec%IW8aM65-fOdFAlw$1KWq-SHQ}5q6 z4Kwv_`8iv)-s#v&YzW(zDFV6DH9fDs&`A{N81w!RiRpEaZh8@_8qYW1c)o8M!b_e~ zH^Nzs*c$YNo&umVpF+^yU0LRo~!Js{68U$6$)bnC(Cy?P*iGO|-c$?pW$5*_u*)rvcx{8=%GWl0)ga{5d)czX^!cV98zS>m7T~P2 zo1!98cTxGkeAXtsG-~kOv9|HmsAFITKSdx^!22(2uX=)0wTCHnRsCI63)JiOGBmQr zH>Jz^YTL|gmouUf73@x^H*VJAqOjuNzFYSKVl-KdKdnVm2a z(8r20$y=>iN!haTMa1?FyIomKOh{RiOvP->+q8+AD zLFvyxA+J=UtcqgB@=lOdrp2Np?D~_n5d?jS_wI}%tXoqQ*A&3}vrWY)W=BQtsiqaaa%N^P ze~^?hOMSb7&*NNJ`0zQjBaIbFNI9obe5 zf(Xx{`is<$vq_EzOGaln;Op-eNi1tyUfhhM7M)N#$J4WJ#w43SlmQEm8g~wQ?9@_Y z8atvAN|vEMn`{UoU^|hEH8yD^Dg#C(<}_XN5K9C!QI=8rPml{@|G|wp8NffbVZD?W z1j#96QSp$EBL06k#OQzLBGH?Yh5$A=iX#83ubToAzSp)t#1dIm>TLrw+2kt?s(Yz7 z9ZA|9SNPU%zDEp<0ATRq&-8;y=qa#Fhy3=z#^I03Xokgc*dQ-f-bZyrnx37HNQqLf zKrS&-wyCJl@xJPojiWrO$bdQ3_1GV4cr!4$*nSpL=|0<{cVMU zS{hT#`>*R$I|*AYOPBv(Dc-^e$AtLB-dB{lSt7!js-$b{Z>3G=0BXsTkLV9tjsa-w zsF$o3-SA0~Dy3bdldFV+Y z{KAZ#yw??O%93km-s9K(%4}|xR$5i=iI`O^8U0revc*#ZZY;hL#0*kCqQSt;tBNwq z^xq~x=8xfUa+i4v59oEx{6P)c!zbDL^RsAO=vopT|po9~kFwA0kH1HX$(z((I)_e+cI@!{9&;hnI1l%6^wM zHAd^a;NYv(2XucuAHePKkc8$i3T*(38t)7K%wFI{lrbjxtW2k>6mu$P4m~Qv7G!a? z_;qffXM(F^k_sfbjYcKv>8c1O{;W{JU^K22p67eS>JzwVXwzm%YC@T6$m8txTji@o9f1@9Z_>+|@X4KC_MM4*ApCi*yQ@UzsLJXpQFj>koGln}qdNw<57| zQuF#~9yE+l4m6AsTEi%MO{OsyUr|o09o8EjgA5i`w^03YLmd&WYt!t_@#SngN9g05aLnX zddEY0$|nz~b0>m^hOgpBFcq1=tx=i&Lr>wa!Q5gTr$sm zhfT`UJEfd_4oo!uD&kSfPGNqZE*4>6To1}9&4CBWB3ta>yy4CL+2!Yx5h=dX;Zqy5 zC{UbrouUYehu0pab!e25!7ZsTas4Sz(&pRhdt#qur7 zAlkf1m+ynVIL|K6UT++x`h7k^0V|(rh+M=E;hGPbJbmBzy$KdGM;XF_yhKH$n(s@@ zx5d%A_F91|()64}&g5Mut1U}W121W{L?VG!u_9!6K&ER#Cth3w`S^fiCZ z4WuReNCD5cM(^HdhaMK%1jE_J7DLp`6nMJOV+gI{{I|1MIw8*fS!q}lf{v~u7D?vf zzG|TNl4{>Xf2H-IQUiN?eE0gpo6X45FZ)TkW9{ECRO2ca1Zun3DTUhQQ}Z5DlI1Zf zL;STs>F<<}I2kUMbY8>`i&NK+Q3YK54%cO2q@TyZtTjn<$5kLQ-3UkyQY8u*_(#gIPM zYaU(@3ecq^f^jLkI_@@uda58DC3w0f#m?3eH1ymoVV-a-+UAhRCKQTqRZ46JjVU&E zN=7cKHyNd`F~4Y?4wwibv}oBl+|(WhIu-^WF#r_a`6048+^q&YoWw<>=MA&bZQLsJ z#@+sHVk$Wp(y~NYQP{GshIH7{25@6Z{q0`I&+JhZa*umm<~y>|19JebIdG#)k}0Sc z%VGx#gB8Q+KY6ax&j+437BvW1#0)I)Hug{nvz>chynQ~|jlvMrdVR_MR5U*QT73?L zRgFL-uS8r`HpLgSTL|M+h;4X{c92(YXW{bBmECBv_F5>C51;Utsb1 zOi4xPxqQJFD^LCw-br}Egzqrv;@m8ZT^2*F6D8vOA*DL;StV5hBcy6O2!!tidKx+9dO{5CFz6OT zwq_Bmy~uZpW0geaMvM^`*9uwkAG9<9L_CQRBj{K8ln@T=J^0;`Yc!Rt`)KzFcclle zyG(o14c3bieLZ!DBrTCka&>lQ2zcqg_#+)#9c1xt)Lv4{0up>d$h%$l&Amd7Sb>k0 zvl9-9#f`n`Vc_xRcs)7BTTLa-!`5XA9s_Mx_x}sQ>jKJiM; zYRze~OE%<4``~IF_K>|@-Vw^(gxDxFha+p%&oI(_YT)eQpTywaopQ-x*Y!brb71h7 zR6SlkY4k5zVKU}$AeCV!CL=F zI@?{d3Yn?$JT{^|nCEj1fN}gGiB5JcS=o{kKZ`%PY%W7?T3;oXvAV_X$d4%Eh{k*t z@TGx#lt3ZfAw%UYBzTaoIkEW}R=arAml#FjJ&F3kXu^gbOw6v=YKeD9?4|*JC|-7R z*m=MFnVjiF7+KnP9ZOmcFdD9n&JaXuWTq5yP0Yu8@J*6Dl4Sz!Ou}J1wKNA3kA^{1 z%n&LgD3OwNG1T9&^)25(6L)7EUh~Zkx#tm;4B57^k_anfXH~D35;GiV@ZM1*MoUgH zf$}f`kQ!0)fBD1sLXGXNoYxfa`8U&46}Pbtx0{X_1OIAG?SGWmUbqk?g@v& zA~hyi?~B|cmv%y{5OIPi!@rc0$ch&}Uq~$h1-VnEf|iFc;@^zirVjG2!ChS%c8^K$ z;dFW&sAp)D-2PRG7-J1*J?QJlW16|feX}GxY^wU#hkgY739^Ou{7nzAzgYnS!*`Jia2r?0E)Cg8^^A1GY zzRVKX<8as#OS?xMnkqoNyNwG)!;tbj>TvYh&LFfipJZY!lVZpDRyxd)FJ!wlBWVtH z4EK2@-e9Z6_S6|;`Yr&3!!7`=zhkG1H#y7K9MZWO&0)Xxafj6k>^tpOwqY@x7-vJC zGWt+_$?;#pl=`Up5&Gu-$w^azC2v&vHYMLVpOgWB^Lue|@wFpp1I-t{%CJmx8!aiL z9;P)tMyPxi*s;w8T{avAoqm7zy*oWI!RjZ!`sn5LBThJr1T`%hVgp0aK1{ag6pa|$ z>oCD%y;Sq*b)9;0XP;VtrQ3wbmF6N}28OX0bd$C)cZ23aO$9i1l#Cx>eA{{H*tp?y zOwgS<>FRdGm~Y^v{Bxa7Ne&i`pGbwR8N$Nj8X*fDCY{TlNK(=p$5NR)c={p8JL6hf25btQ(LKeC<3s4a z;euy5*I=XOXlSe3?oo>aBNVS3Fe>T>3JQitH(}M=yT}Gm?emACfz&kZ~Xw7qfk9fP6sv&Bw(OEQZ zIQNK$WT>1;Ab1mcCli2<8-k}H8n8gWFMzM{naA(O@`bju>L@HpZkjpGMj2-A`*p$e z_oHIBt5hI*Ugt_rP>(8MV5;V*h=)6C6<`nJs~ITu*Jj&t$>WG~s6(ZIOa9t+ zykf8~epQR|hMyK}YbFyLk=}xI32b2;K1d&pPkdhpS8j%!+lFpGd!e}UgsL7Cr`hA*k2i{ z-FBAgmUUw_IRl0_W1ENNH?sZqvBtZ8Lkr-nlk2Hg%*nJ9`JW9=WPD){ z;s1ScZ(v0B$x%Iob*?TdL7$b0b66DMr@G1k(^d4j28(+~@f@<1<-vS3R=j zIktp^@Nx^WJ#|nv9(`Jo>0^Xz%o7;X+Lh(ERy%hfIqVKk^Vh2~yvcBDnHhuw0QFHl zyS0mZ|J~^E#aI?4w!i-D#W(=*F$jQ$j+0#jyj^?i`zl4{WtW%Imlh~tr}4#3hnX33 zUXPeAHn4|_b&F{Yy^ECWfrVF$o|xjRW`OU|BJ027oY#C&GKxJfSk-wPHu(MK6e|y2 z7OWoXu#!7d9c%+!aAsX?LN>y%;cvOGvTQds`%(hix}Z1kn+?$x&!DS=+N=3dD_0-v z`GEJ{{1p6TnEdbX#bA56Hhy}*6!Exl-LH#K-*P)Cw=E^qP`YpOY#VRc1SZq$>KZaX z(jAM$SnSq@JB6JmonX)Wp_Q%?r1lsKbV0Wkwit6omAp)Lk%ijF)}LNEyH0=n+Otu`p-bGM?7u}dR5`@jUy zD)w~oq19W54jR+&A(|v|R++keDE6ukS(kZM&@*TM5IV|wX@tFzIUB~7kFsBHrLVTG zz5g9_cka(j<;~fY6?8tDNxTEP0JaiNplLa+E~~Hi+-NY&1JCPHks<5mW1x5#(u!S|ZQXikkp0KCaaRU9oPKkc(|?3r?p>SG@re2!ZhB9L z-RjpEz$?+R4VA;n3X($C^_Sbl^X6{NYbGV8OC~>FPSCD1ww`NZzukfYV>xygpxB$R z<3-P5B_;P;=x{KDwn7uSUvO{69`0g3!}51x#l;+%MzNcr>C_h~-oXhwEAVmHLa4M> zd#{|+@9+ud#51hU{Gb-pT+(h>ACvZh`f8MNTgT%Z)3?wtn`5-ieoM9EFe~)oi0b)q zh$pY>cjjj3GfF4ZAKYA2van_9#qZF5oo4v8`YyE3gG0EB9zRQt`iyI1V%eOuR!Gym z`#mq&`R`VEfB5%^pR+?JoBNoRhq7MheBkzvtw&DB#UF3QQa5_6J#pi4o2mE}%_=K$ z;l@tp2D|p&nS{tLVoq!42ht>O+z4Q&3VU^(>`K1l!j?_bjh(#!pFgI{x$z^ttw%!& zeq>I|YVQH(?UXDX%V{4*TkQr}79HDq7c1>!_jO13`4FHKi}i(oGYig1^co_d>3xby z+;#ocUSl)sSdH7F@)^qMQBv36B}>K~Y^eiQ#sKK z(JyAcOt8af218%k4W_RJo>sz7VEU5J*kkNEVa$?qd-IJqSiIUf19;$Dr$Pc!hC0b1 zxjY+NjurDj+Rrj*^<)u?ZLI-L$SO6|z!bJg8!p5&(IX0T8#83+|FnqTx{-@NN;VdD z<;B7LPYj?NMT~F;ng@I$V$|^(6BF3ipKLvo9uOgWguGaj_L4K5q)B=y4`Mcw4R-HG zCsxHPTzY!WpX<1~@BcYZ_S?C__K7yzXk_mUJeHh`e*xEhwcR$2UBhPX>$6?fUk#d@ z&!;Xnw(ozlB80t6E?pIO9Lu-lKAL+|PKw()XWeDpcy@HzVo0DeZg}0%Y>&o7i{qU- zLWnVD48uw*RY%Wu#a&lry!$+7OE0AfGU@}fM3KKyg2;b#!4!!3N&xU)fuj+;aW z5~WRF5Qyr4YCRs&rm*r^J%+lKP}5J9faikTLrwmIMJwoMqFX%8A1675S7rCcZPX9e9`bVrcq36!?b!$`tHAP+TFz3Hem5T;!#s+4ki)o?P*4m2aS9x6<;KB6-f2L&T9b!<&XX$-Sr$zInkzs6w-Ky0pW^Eac z+fbMndzp(^Pe5+#{=UI3Q=ZK(syfgX^Q|Zw-F?{vIr5Emc#|lZ8?fJe=DU(LZbHkfAgUG@YEgkt* z^(q^2iMhR#m9_}X#`;Z?!gW7bU_XztVqH;m2o`7+c(JzE1HiaAyN|;URe;Tx5!v8!_piuIl1#`LMN3nge2X37$uSs>GWE3*}T`Of9 z)rN}@($3J{$)GnqF)q^#;&ZnRYbcz%7n=Q8J7~|gHbw@@MqCX2LrRdKM?uDOwxPzL zt4;}bhRB*!XW4W)9ZV?X^9fm3Yl#GXZ}20}_49qVmWx8aOBg|y7+t^HH8Pq$W0`EG_7$q zGtgXD;mY=;if8kna~0NRLKaQQE^~^%4uN8DtL%jByvn9CjBu^1T|33XCvCRcDFyI{ zPviQ@RVV*<8XrUdI~05*E4S`au)PB>2GnAQ-0X%jXA<-~*koNZ&1+pP3YT5NWQO>j zB=S;WcV}zQx(bjE%et8MOT}Y+mxsn$~HZNR}#ZME5Q9fFKpG3~)d+i+v(d=4od4o9ot{~GS8_}oh zr*`+P%xjzvPil4R)r--=f1KwVCgyRS!#ujT^1>>>+aN$RveGaH7tRLO?u zkt|fvZXlHqKkZ4t@#bi@+6HE|FJWQ+^8=mhN})~`o&jm6Nh#@MI1~d}OvnodcaqEU z7_jyoX|ZzN5fkMwYvlsSWqB`kg!a)YX}Sa33M9nT)G(hZ1HK6z;YQ0Y@73$F&@>a< zvXY$OW{BP*R4CIeu;GSl?!~F<7n9TL8$y`F49UN3{HP&d$)eJ6b2#>|-7?lwcA91X zq3m3(6p9;T^$iQ`-EAch^}51Zytnlm!+*y|$9xG|cBVs?1L3gz6b1n{s!;snRZIWF zU#q1QXNp>nh+(>4G7l>0U2!T9%TFM32Kqz~m%@clPkVTPkH?g<*gLEDO9Zlhr+>oy z_ZWEeg?`!`vx_tzKeIC98aT_9_|=PDqJ&Q01N&1(`*(_=v|u$Vd9<&$$@z*mu3nbC zML&xAz8mw9a#ppr)(W55Zf5K$hcVf@exJdB94o&%{9F9Z)fq3iIVXh>aI1C;|OSC7bk5QCmhpI6UxjYqfQ^00471>L4@PQ&G0p!#egqAB$9tf5D-- z24VtAzpnmw_EctkipoyDKZIBB>c=a?X0nqfns0J$SR%?b!XCJLeR)x4xtsFoXLbvT z_!@6)s=ExTsrg}ldTqf>QVxl(lZ4XaAbbURF_d85W6)^VU9R;|%KCKGye^i>$CuqqTs*DJ1&G|gp^nd>B|ncO3L`f< zXJ6Zsj@F{ZU z=q}8dEo<-g+iyJ%u3OPfz$F2+yN7zGlg4E^X;fX`JbYVE!0nvv5C&WZJ~ zo#i>@S|=bnRapB?GedqWRV{}RjME#J*p(PV;%wQxeiLh5TEnkh{2}FA)M%(m5EbRA zGl6pb21F9Q%-u>^W=ADU$Ed)Y2r<-PwLeW-djuH>qK5d*)?`Rr=(bP|d(Ai^=*&7y zf47l!{OXHAK5)iu- zb(yzB{)1>ZW1*$}e`tjNp$n)akID1yH|g3k0l7^?adM&|T-9;2azn7O{s0KJQc<5C z3W$n64gT{^-kLryY_Y zI_n0OUG(?#c%f^FVG8IRE6|z8L7SjUu&n(M8#GZCZpYSl)IY zR9fQ6LB_ty4k1D{Ih%;SOoDKQ0gT5ch$B($^nxbmbz`MucHcKc+I5qaczlc$`V#Cr zfXVeJ_E^^v9~}2PCiqze3@>pkm{NIDr0GBnI?TIQ8S=vz z`t+P>cj=d^jv2ES_c7*bzcG}Qv0>J=ad7@!CwX(s%DPky&e>RJdG~XU&M*-GT>S9L zqkkpAKsim3zlS?kaXDGkY7Y>7{-w1}&g%i;c)PwdFpEr326A#gdBEv<%MD_hzagG{ zj(xO|xw0R>TjDb#R`J`qvP!8-X?RvOUeS|8YFG=#v?TgRn$RNpbkuqiL3CUJLR|BM zL|e{tGZY~nokX`u5{>%h$fE^GfwzJdmnh*lo$N?eV~Y85AdXu5oPZ2G&f~lorqbth zW=MXwmh{4oV~K+Xp~sPaEq+P3icWfA>9|EDs(@F5eb-LF$g_wt;O!JxK3MA9J3m~( zGy?C^PehqYSgjfuawO}$P#E2W!0y{U-fKG2U!)jQj%AfC&9RWV#1U&h)e8NrV|iZu z2JV#fV_w&AD8->q`|y|_YRBN0T2?YHfdmWtMjD|21+#smFLLmSTX*xZtFpef%()^$TOv2mA|N zUd`paLAbtJXNSW{U6F+T_kmwK)GznV3Cn2Etj$5BoL_{Q+hH$8JWrzGG# zA9-PEHRfsez}`D&nLJpRddtcbP9QZY#7Vb?;4=wkcBN$R-b$o)e~FWQ=zI$ILL*&! zqc3wrg1Q+(%pZQ3*UDU%2|>{6m*P~tta4tPag+*5H2n@-dtGuJ^Xr2d^RxzvOZ&*U zK)V?VHo4y%zMDDO2d|SH<@E-)8@l{D!^qRto`*by@&Wo7fvvW8bnO&FbF0+jVUC^K zJY_AH-xbd0Ja54L0!{}S4kn^swAZUE3|C`iC=vO^^A5*~jwCzWkPb~h3x?=YWN#(V z&0#i*NnSOf^uCn*tw*gprHxH`U0=B6Il(w3-YkeApujWH{%v!Rhbe=2Mgt2J4v5ZDztn9MY~y z;1+uSf-5jq`O?AQ{rH1^O7QJPh*hYseuI0shq@ss$WI*2Y(ck1U#wdoWK#NS>t?0` z6a`KQ<^M8h0Awxu>Xm&yFsxAK~Nxg7fvF7D5KxDTy@ zm_D@FE&QHcWKq~G5)7n}Mwg7T))gr`qM}T^KR&cz`8x+at}2reyod5DFv%vp`;vO7 zDln_2OKq3{8s<(0nth&NuS6aeI9=IB6)0im^f>q>A<3UK`|QI&#glQPK-*B5V7iB% z(IjWz@$uIJp?y`NZ%zIiBsV$bTLo^jHG_*to8Kg#)(9O+^6hmL>ni5FNhR$D4v;<1 zn7<9o9dT%Bn>2`(5Xh?@>leTd!n4IO;USo(uCMr>tL{(^7i1zm;t7_hMBt8m<^r-w zm1U#724CMzm0Ij*-DQ%dX222RkrZ2+{T*(drO!ScwA=t5leD39X+)9wvYui^|1GAz zo>r6oTt29j1Cm26J-yEq$^&fX`yAa)hNkbQ4W?TK7u)vb0JEii@y|fBQ6(Otd9-&h zR@H&@TX#?r%R+%G9I`m4xViPN3#u`?Tqs|Ucy`aHy+0l|v%KJmK7q4#kzuvq98rYr zQ=Vhj2r4l>LqalFU%>IpO=gL~C$W6XXd0sCcour1YXP>l9M@X0%H~G)&nJUMGGL;g+SFM2o!+#7J5`|q6 z$u7*BeF&ka?++DYg)X>+b>SXAURDc0d?Rr({_5C)j;NIvRuxbAv;QMd)9WcU-xK+r zABmcV7>AMkW*F>5OWshFQ1WaM+L|J1#~aT{*kUT9#%e(r&GvTHEKl}(4WlCt{_Ji9 z#)x@w9yfCkA@N;&gNUTlmWT5`wfL)1x7Kq(LuRRpJ6qK|6d`#RaVfXVtTT^35=4t< z5lo8P_tu>VWO=P;D=CcLAK8q~eNsk1Gsxw31r+p-O zp=ter3^f0SY?Da2X)kP6%Sa#|U*qkg7rvq_^ZD~=U;eOS$!d$y6Z&QC zDB_|7eCwMmX@Yt)I#BvM6sZ{DIqlP6N|Deo_gT{VghIl2L z@WHT3C{?#s0^qhQ^A>GH-j~OX!0wQy@&@v;>FB(R8-7Z}hdHr|aE|qY(F$PLe_|Ty zG?yq^pUhZb97#Ff!*o?CWXI3}y5!2O3y|#gT(U^8u85_Qm6G};t zhZ{5SX(U>H{rV=+(R2|a;>29JUDEryp+s%Wk<>d)2C6dm1XZ!hExOCXOdHv;{ZQ&e zjr)jYhR+CmX~gjs87lrU^L6G=#p?deaubTERp!WJLI)J?z|PUV=~pkN9{XN!^u@FB;^98W`Pvm41Q#^?%WlM5`#=45hILzn=ipHbRq!#9`P?vJ~2T_`iMI7OviIVKN zK+7j2Nmlf)lL+W8f)w=RAns%9l=<_8{7?L{VFmmo#jJcrqL&{rap5nkDoRxa!_ymJ zRuErxq>s}*HonNHn!vUABH9YuNQ&DjWIV(_$2g*z&J$f48;Nl{Bc0_k%rLox!vz{1 zIe2P5|8{^0Xk~rU!2~NmGW5+)p`I=-ZRp?6TCie{aH(~V9IybBb$(SZn};!XI&^J^ ziZiwM+A|YXnh7DSfl(j^1Hg^fa)($0CC>lMZGUbAP1}>iAqz~PoGfIJ@pNxq;T}rS zsqwCi-R6JtwB2}J$$LydP17Rh{|`7Z%$K^}eHq;^9mLKppke1ng@H3wtn=h+eJpz7 zZ{_)m11v&@GextPi6>OH&c$3kg(AONetPO&W**R zI>5!;9oLqN_0=tYF|S8dHaP4epVDNsNUR&7MsSi#DYJ#)v3+pg`B8f+r48bv-6+sW zx$LBJNEGXx_7$W%&-IU?x93)tYQUMLW;(p$lrm!9>*VBc6vNIErH>=BOG(Jd?M|-? z^qMg1R~G1{IfTBNfJyfno9Q`pcs9;WF(N@mJ}q>#F>J86{SNM3ZcHXsLq7b|sjJ2& zdB}m2JWRRY{bYNh1>Bx=x7+DG=maH&Q~s2vEfzJ-Y5U^t4bu(YJO05TDkiG58{%)e zr3TgYlzU<-sbgo|TIb;!PK|WO>>iPCcsFfL=kIX$WKt1p?%txqvC2t7FFY45kWuQB z>qsgV>9fDEYya4R(t%MfC}V~^&n-u(kWAciYm>r93f%<728wo0OTTh9>;F`l4BgGM z*`i<`+>{CvRFY_mBhR_FMJ(|>PV4=uMBe-3x%0#=;%GpH#!_1lK6&8$-^8*G=-{p< zUk`z&qWh?0bp8=8^Wh=*qSRSzjQLT!s3YHj)6x2%QjdhwvhFYYCx8C>afb^vE*Ve` zk^RwC&vZNYJ)R}QF{wlapFa+%uDMNrdB$h z^Sp%e3EG**a42mnX#ZswM&$30B^OED72+nTPX_w!`-3F%R_OI*yAvnwKT0guu!}(c zMcdB6&75%Gj;N$zO>eWf_ioeWIp1KP!6ES&VFz^hqCK<6mb_V_51#!a?-pXQ07=KQ_QHsOk#$xt@1Ekjjd_w|4SmM+hcn3DVLOWn(%@YZTg&1@AL+v z{5+Le%dQbXx)<&SAdykWLlg(q;MHF0vT*lW`(ch+_m%E!eFsC70-LmZ*#6AN7;gw}~biY*G%Z z9c*-cq7Ce$T~iGV>#pzSv<*+AUT!~=U3lNwsQJh!>|Vu_ykRVn(_%}&UiFma5!OKc zC10yhD4n_qqL=HpCCr~Cz>x1Jx?b0UL9@@EUg|_vWo zk3)@Y9BN#IVcr7c+0^0zD6~LAsgF^CFdi9^T{PlLJVbAPbdzt+2rRv;S$wILAVSV^ z6P$@m=632zcz?K{;jrq2TyUYQo@e>qE7>h>yA`9Tn|+eF;C)wBWz^FB7d<8rmz8MQ zU@!?YtF(jUq}(pT`-Mj>g~4+bv&93{uRfsQqAS6D@PixYztbdC8Mc21vO=v~|N>L?2HyT&iS{i|+8{-XTd~Fg}aOTsQ#NZ#EiheS21du66C>Xdd?X8uU_YYNE^4eL5 zi~;^{swfaNCKSg>vP7BVO6zQwbvKiVp(B-Vc~CojhU-U?iYhr zxnfS7YKm4Iavqy*~UplV7j(R@vQ=@eNBaX4qneE zPVeh6-yq*wOm1@eIKSp6%y702vB@vAfBvRGiNS`Iu7k7)LbXx88$%3Kc3OyEGdXGQ z^As;24J6KuUAPzDQ(ClMUH9Dm6~%# z@VK0TOK`uv`qfWIMm!&W6JG;62wgJGHCZ?G)hJW52zdbK`9fpdT{e(UtUf$cPvdZ4 zv6CbdPcQuQdPz2ivx`|%{};`Hk>p$E!sf`5k3b6br>0C-Q4GRw-_icFs_qT_$)@U7 zB9D~KyhXZ_*T;FQcIk?;%Wx#$!e~#;(lnSEUCF9-boVWUE3b-l)5elT*vZ!6-<|bj z){-u#`oZ5N8I7^gQo07V)D7X(bC{QUNmb8BMV``Ul;^A2F;9pfnK6Gk=_C5}4|y~) zGTsecQiQ8*Q!L8SS6|&U#tTA+Rg@^%WpJIUlKW}%d^~aC`M?1ydAzL9%s+D8InH#O zpSVlq_b(C&HESMP?r@N>E#XK0Bx7IH^M6!hs}%n8&Ev_(Pw9B|G70EBQWvOjdBv5v z$EhgW@!-0+yG<&c8B4d<9;gX+vmw*Bs44qn7yMIYSVX=ZpLgy*5WOb|IJ%BkT>s%6 zSG%{?=)9(>{H8=ZO-T*C3+)XnZF!d^ z;v&@bEq6gjgg|9el~cb3ABj_W?l(pji=ue-Xn-12K-8r{4EU9n$-ar_CCJNE@JQ49 zu9QnoVRJ+dw10d}vFcK5S(vKUOW{T0sXq51bw$4#(uXl8-X66mRPNG0E&FiTmxb&` zVnlH1L6}(O*l)trj}BqooL}yx$~dhv-cDX9R{lYDh#%P@5Sbt|BtFtzZxmPdM~!N| znu^31S0dFN9ma%tS~wVUr-w=N5y*S9f-f=_Xu_48`KzCk4~-8_e}`VqALP_-)H6yv zZSpWMqMNkNb8g<0!vEG(b@C+GWF2SNw4%M9TlyIib_C_tIqZ0FKrVT83ODb)>r?qm zCAj;uH_Bk9rZGbdB2rj^D>iVf@U6OG7m%5C^DY*D!xRuSLHb6lV@PW&-V$h?7GS@B zjOkZ#Zj3h4@ME45BfNi%8cY?30ZS~If~jHt$w6WnzbeS=s!Z(;kC6tvB4l}2<2&emtLD1-+}@5GB~koggY2E3QF=+QZ>*b`ufmX`VcpBj#8xijpoZcr)S VO}5=8uFePx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D5_d^NK~#8N?VSs( z6~!5V=bU>l7cLPLP|+4pAyhz$EeI8%AgM+kDgq|eDp5g96d{d@QL993G^LuxM@+PU zR3k_NR9gXG6O$?YYGqrBP4B>D%9FFoO>F*s5hr?0EIAQK^I2_FYC(IoVhoc#gY$1;Le;p2o z!%?mzCtujr|Lbr#9FB6unOPjiJg1GAC))KK@4Fn1@}%IxyyDk#JKJ|egzH+|mizVP zNb;v%o;6oWu}oXE=^`qskM~{qGi=x}(Wg%z-xVLHoN|h~x1(vG4DY}H{(_WcAt<;o zuNo^G?qJ^$kz_9+DlhQ%xq@J9akIBo{mv7XrA*~zUY<2q;waO&XxYA(ICaniqN?U( zaa^Ziv1#X5UU{V$J$kh7ijPT?CaHTnnufuH2aACN2daHJP|{|W!_NwFaGJM|3PQZ) zU!@SgL5Reu{;{D#R6i<(Ni!CfrA|I&8Yc+yu9vUz9Ynbsf{1JLx@9Ch#$CKh$Bm+=6jV)c!_K1 z&6!-wG)_+XxpDp~2*!B5hb=={A_uqft|5y4X7A#nhnlNF4 z@61f)a8C#(PW0TVZBj5UZLDEo4`mn)wI9o$8oB?#6QI0dFSi?f9(ld zP0cI*AIXt_de@s^Hc6Zyum3?_KTQ5_?cL+hy>kDJ?m`(LzOV^{@XXj51%X$+unV|KN}6~stm{B^UpsQ)z#H%fA76AQ+*~+o-BqBA1+$A zZY}oi-78kFUftk4=yZ4MCM$qs`}XbP#TQ=`bLY-g?&;H~i%y+7iHQ>@DmOOtnKNgK z@4x?EJow;)V(Zqe>i9L+Tq8z}8YMb(=#Vxn?sNY6=ZnpoH;ZZ0riuIRyRRWQka|Y*_;hm^55|`Q>8d$dUe$_|m2A;FJY?^wCFR@#4h|>e`AGEBt>#EMLC7 zQCUG)nG&BiZQ6)Ed-jO_{rf9_I&QkG;>21rM<^Y7!fWJgN8k1uw5j3J$Ps1-|LFNS z@{(-i9sH9%2#X^!pBQTMzjmYNH`ztrPYO+q4o7te8K4tDfFM*3T<3*VAv+5VaYN8l zKj7{4q+9JL@|IyBRKWRor%Keg}5^MTc?xH)cOlP=D&Wh+zA0XwV3T88c=mfw6SyQqitmJO8W)LWo8`d-iPQ=b?ulQjMEy zH3;S#Z@f|L-o0B~am5wNAA%e~kPcIj-*U?>b;5zT5FWzF42xqL1kNLmJfb=V1m35g zeyTbQ1X3~BuwjEZ^UO1Smq+j3z149|TK9{AAj7iQgV)sPB|Sm zWeR=!_7(g0?^m4?VNo}(zWQqaY`i2>sg(aW9Iau(5vj4EfhK$V2PD{}kFKishY(C{ z^8TA}UJteNv7I6mw8n@ANLYzho*P`k`zs@_ApHiu)=;yhEWJ{MOlOHh4k}Z-{E7EE?dyZX+#LdAWT^HzG18AGxLL*O;K0pLNz*%D)ET z&T&nl(nVh5FRG?&Q>H*Y=Keq!Y?N~$Ncwv#s^sXd<-^{FCKm(^PaN?67aKI?M2*xm z!qC7CAXY|@Sf&T&i(OuSY|t(}!KO|_mnwMB-z+IIxV zlrEEOemIvr%llu^%kF-K0O9+;mA5O~HiCQO3zv#Rdp3y!J2!~0mdf5c5B6?-TkKdj zR|@ot{Uc2ac6M7Bv0#P@4(T(oZQC~S-h1f_E#C<~G-W#8;D!)oOLs2a>W~jV{IE`| zMJvZ0MlR_>kP`=1sSrl@?%l<#S+g3A7RnK8Va~)B6PajgnP4Ifvqp2nChZyo9p(ll z@TCSDy2f7SSC|c1`5|w8m^4nwdn=o_u@!2$pX!`^Q;RaONOveU>B<7b@pPEl_kkVj z#HLp#if@)rQUCL$_HcBYF~j*VLA-0%E;SKm!ZoHAjvrgCjuFekg$u>FapM};i~*bb zfddDW{n*^n3D8mkO_~mhNnlJaTC>)~oi2nu<_`orqkyQ-1=y5nQ)4a68ETt?8Z$x= z1Rj%s+PuJoX0Ai+d|_hLgw+1|IKKNh zWparEHc_-XEr{ua^a=_M+9TQ~f(qDkjj2^KE*&hh-Q3^5n&3?aobHa0o~me27Yd$ldKYrJa_yljb1UYr5@vbLcs zo3pp%IAa(#X&0?zI9JujyZR5(&niz#H^KB%b#=WTR)qxpATaSmc-Y`;H+Y)227C44 zzezun>cguXA>(z1??GadE*n1@mU7a8u#zVoVd;uerrHJ_W{b{;?H@cL1$htgvn$>f ztvdW%oP5shN;BW|s?5{~Jo)64N*kL$f4&mP*o1>CAP~F=r+e@&q`?UspGo2-H9y3EJ@XkB$s5uDs zLE=hRO-+qjC8h77%R*050>WOOr(u9(V29wb3MdRBX$%DTpl0%Nb?!?Jb47%=1DEzjT;F zubd#?m;|QdO1B!OT+^k=_n{$&qZF_GlqbiOW?pPCRg_ytq9;{@<|LDHOqava+&HE*^I}tu0uLdcO^|~VEzUtGF9v*<;DL~e_7IS?our`-B-2snD2bX*yC|C*>WaGw-k(ZcJ7xnu+ z(uEZjEok#y3Ydww>vOhpXFaqg)2mz4!5V5F*0J?pD6*ABSRDf!KeqLUd}fI#%OH#0 zzHG1-<`g!rT7PgwoY@(zEL z9exY->e{%7WM&~@WHKA?Zzdxc?m3D_u?0DN_uY4@A<}5r_|O0!Yd~0Q&}?+wVY~b8 zyVdcPD_5$$ExZ<$un;)$0;tn)W$&>%Hh2!24L)W~ZB zbp87EYOgbkLTip+RACqvEJ*hyl+5V2$z`);lYeo%l<3x5Z&i5^wNs!TMZG2;Fdtx| zq%3&-D)pKUEh=HQd#_!lLoQ|V66U6GgXa206|Qk#La~E%(-^V| zScD1lFZqDo9c>W75ce{@rht0Qhlz^;Bf~gMOEzseYP+z(rjCXA$yR@K-e|2{D_h0- zMyf4&33F{;(JNeRh+>;7r7x-=eB75%vO_sBgnsI&r!qEh8VJIKSI6cHVW92q+_^LI z#|0eu6QwPiWNQ~;*wir_KM+=bOsyQU-K!MHOPHT`-g!!6)f%eJ*EjHEs~s|2W8$O~ zzNq59gpwT^5H@;jqUMMt2>G{m?b@vIpyNWb%C`fH>ps+1?i1&zhO)@Ca-+}?ZmxpZ z#HKCB*)DAAm~B1G24-D415+zU0pWNllU(xBEB;@5bR%K%j4!Ec=xxKkzJcig$ZQST zn!Y9og^oZk_7_zsR2qT%5=vu;Gp3WU21NMek*V#ZgXbRjAOK6lOvp<{DZxCZp#?iv z$aJ5m+;!Jo>OSmqg}CdoARpMwNleO@bdMPt@O!X)299`Sg=5}Zr!?W^i6umpEYaND0iunxOnu~)biI}_MMp_6U+i+YGTqvCL8T)^rqPFj}dG0zx>M1w}#73zP9cn2$gH zc-?vKLnG8siOqXPh48XHaT!N^u#uw`nD*3Q9#R?g7gbDx_X{hFboop<(^;WC*}_Cp zI+zS&(-5DNPC6;H40OEH)gAI@$dDlo@|YDAW;mK`_p!}GAWVkbj3{z>IEj2_vQu}+ zq!`l3P5FcM=`3I4wQ_FHuvlY}mdC~*$9F|*DR7V}ZGXUbaJh@+M}gtcV|YZo@@ zvZ-S>er)THjx%LJ+A_8IQX(;V%GA`{IkSCdJWyG%r-wcAk9NQ$iZ0Y#p+c3?7ggej z4|&tvzJvnA$0i=6KU@Bh#}HO`!tP^(?!=kL$oF1hb%XOPLyfDfvV}+9K)z`Lo3|G)D z{zpIG|Mk1B=S|*qXmw?l+OIwUqDOrN4`VgL6++<&`cD4n8qgQ~_zr z?|_i9C)cQfhKn7)6Qj6*n-70)EQb79PTPoM=@oUT|BZ9$tNi@DCsF@~AEO8Pf2XMx zzkdaNIlQBl(J%v*^e}fX`tKGFohXpNLS^vK%0>a}rOTe-){P%v^rka+z=ua4QWUJ5 zWl!WD14YuWcaZXn*ulWI?gq@=%)Vv!O#V$+$dt6iNS47w`)OSfK)4x@A&4(`pLv^5 z!FTW8qTR6F%jKp%vaE-FWp%m;VczJ+`P#zW*}j={B0EC^#Br@vwe!K>DjNGtJ*RC( zjKHR#LDA<<|NKbu96q*GMEg&rDMWWz=>YGBU79RVn|{z}UC#S37PHwXETHB4ec#N3 z9$aMe$x2{xOiq9$!#z|6EoYy0c5rY#4O!8nm<9cZOJR!IfQ0y^>0u!&ei2dY6^j)) zgiAoF)p0;7NDsJ&jsc@}tcd)c_n7|jrSuU#k{uJGJ4v;u?zZr^%LAYM6&o;VHTvLx zwV-H=R;QTW{tsR?UeOCivaI{`6IY0^b!9KYu;WG;ZyOX~_nU+f{PZom79BlX74qgrjU!QZ3ea(~a zb-j8e0@A*TTm;X^yg+w<>F*R_nq%qO_b!J*Fsq=UVKgzAgz>30r-1Z_g{-#yjEAs` z6;ceWq^;g3Y^cni&^VX#;$H~n7!wM9lf2W_`Mr)MO(T7~%BkIi4UIZg?Su^~?-DE+KUNDy8O3!=9_lAx( z#wssB=^X@q@=Clm1g-ot{Nz|pkc3~LgD|pNl9qen&AZR<3z#oWX{Q3)&L~wF!ahtn zFY0i$GoR`2wc3BZPqZa};kH-sO4uFG$Ybcv{PAZMiCl_$E= zBI$^NI^i~0&shPE%oz%JfUoPr50_aRIt%|rDBdpkMms`_O?+v8W{{iG8Jml-zz+!cpL$NAOEPF_U)OCWs({o z^U{&NbPc<$O_dE+wn3V+JOFFA0n;Ewc>%N+%~j+D(PRvVnJfp@@!- zlEML`+Gy8i5xOq0r`P^tuw5p8r%alKVmJK(th=`_^|L2M?4Q7zPa=}BuWwpOcF#~7 z9321)_^{OrQjsuEMlQ-6x4$3O2DWePO>PL`H0iIK(RJH{JaK+VM(~NVumF%8k#jYX zu?I<)^e8<$8w!}8yQ>vlKkG=YSEM00`_sAR27h-If>}99b;^+O{YfY3Zm&s!L)3JM z;M}FHAIKCN`Z<8|m@+%gs^5@cy{9_t`{FI_&Px6hX@Eb)C5%E#nSLeaVp&M{wlWu< zAI8Pbkr`9|p6TzP;2m_c4|-1AiT)dwa_Qd*Wt#bv_-Q}0dokx#ciZnZFFr6>?$UZA z%bn2k0ab1T>VvrgD?eRdq8c!c%mI&)0i)y^0ZDTo{J~L;;VhWzlpya6Jm>_?O6_|- z8PkAZlJSUm73`M6_9GH)(?*I1#VTDJ2b<4NY5rER8egB0G06Se0bj#6wB&%4{s%oa zYi*gW0s#<@V2L&M)Za1`QGt&KsxqD-;#g4m#DO3v#PV=sWb)myV1~nA0J7c+r<)N3 zr4zD_V8XEHe7EYJ`JgI&2^7a=NFQrNJtyfq9{J}?B=3Z?so&OINof)p2GnW&a}1wb6%_4#&fO0 zJ#IY~uDM9%!6#l!97&$+kS4U)&}ql<3g@)>{$Yym;18&mq8v8=iEV*)VMB1mzCVT6 zcCMmj1c~)=M3%D>_{d_T{$OJ}0KKtwI5pvV!|4i7a6f;m^tnOwtP~JlmLgxSYguZw>w<_nB(p4JL?oHueKAjh=A4xO48MF28_hL z_r7EsF8Eug>D0vc{&oYtq}2+^2QIV)746{X-gbF4P>CBkt??55HCbk4Fz#T1DVAM1G63SH81u~ zVFKj~?|(0k*LOMw8eAl5%QpCWS;bx=oCocC?$v|2o}FnjLj4#+cq;1i)?hIVVi{xF zaxI94yzpEj1u%&ijGs%KwCitCsBfA{Q1F+3{@Cd>C2_0U`9BwUfFirTPuo)axqk(v z5Z%d+;8p95qbSUgXGUmxDveMp9LB6yb8LT_3oECh()H-7$bIXgM53wY9-IwXU4 z#^7%Axj4zj<^Z#Lph0Lvf4Etk!)Lpl~tG}PuH@O4iKZP2&c6i;v`5I9O)>Iq4 zAaOMOVt4n3id;SYuDG~M8k{m#{H4$x@WI-kDClvTRhy$~0>FL^r2jANz~kb?cuMo} zyzKzvP~vb_I9nXF#&znqJ8nMgAp}V9*0QI^Ocq!1_4J0&>3?kt$HW2#H>~uHKR~=LFXBf zgRz`z5zvyLrJ%eK^vtJy`ge=3*VxL2BoEjvzW5Fm7e@uW;J+Au-e7bWqCI#f=Oa?p zpW&*F6`DV$|gtf@}B<%l1V=Fu?T|z z66Q|wqFbP10sGDe-Ap$Rv$_8xZmNfkuwc*5;VdnX#)fF%=^l5g)|2h(m-C}(g9yMNo17t+>eP!4b)O=yv|-er1_MjYtG#>J z5HC4aypyC4hirA0PRPAX$>JR1b8856ZHedVEbul>*m^}HR#Dn#R6?Y9G{>pwx;n%3NNx=(OttWz-kJH)4`q^?+(k5c@4q+6-A^!SbT4U1jYg@K;Zboebm74~P(IshkV#21fDO0`Nyrr=y&-S3W1}XWidrh6y@>i#& zopGcTzf1>}wzN(n`R@jh-HEN%W!NB$Bd1Hkar{Zm`(IIAsjunb4r_(&(F7_1Qer6> zRbfnU18CitE|&*&VP1bZa=1D6SEt9Ph?OD0dG_ZZLfB!HOJ_14RXsqh7OD`u%QFDuzGQ1JS;n{NqAa-73m z<|IvWs7Dm+-{g#}-eY(^?sTb|ZdIdM5`Y;kb0-%?;!+$V7+lN14mkXeOMp66RBZs0UuE@gEk`wRWSR04+u%Zv;U-$*tFPTXF3GZ~>9&O}*h=Jn)4Y#h7%|e`d%8Gnm-9ne z9$BoJX&Z-^49*DOBISP>mVz_a+f2D#2tDk2EOT*!jzebj9}LO4#<@ut;;N2edBs4z z+wzVVlmgk#WSDuRG4g$}GdvT!Zslssdooc7{a?D6-r7_afLX~>YB>U>qqFngG{6jv ziES7F`;#WFKgzFShM)TJ2@k6IZhkkuZ}t8l!Z*ml{BiknD1&POAwgjZ4QrpK_t8=! z%&5k8y2AXdYmrmKke;Qn%wg4kA&5Bd=6R}?y@Va+ekUW21i-`XTw1S}2cbWC(Bo?! zHR{w?`dj#%+mXxlHc6N@y&YO@8mnR-DRnLs{X-L2QsRF`yK;@j4lukzBNG9shwxU zpw*kRhX;pVhbu-uN|*6Her1R%-VnC&An34y{G?UngJTn^RLguiNT}a% zQe1Is-E&e7{PB@WruJ2qzH8FeJh!p3ehW5;am~awvTFA;ES1lORWg!~^_Cf%PnV+8 z9}3K6KhbX{JQ>&1{xOxv=ebh7O1>if(SOjjXM4>%*kuYE=y4^! z{yxgd!E5$p`fNQ1<%jfpX7%fcV}rycP(hQ|RB~Dw8nvprskqwP!SgqAvJiU_+nD{ zX>!i1?jFU(Wi5~E$xX3$R&Uh$m^(0U;KepIl~PX0Uhl`vB5*AF#_g7V$){qv<3VBM zyAgHrhzP+Rn;!iu*y8ND23ANskZswIhrHAAM(Vq77e*EtvV6N^6dN6!GthO~DT zGfX2GC{k|llestFIh!x^NKB3OsKs5*UlqvDeC;w)&F?8-K#A1&3rxieW=2|Y;1+j6AJ5@GIJQ5Jr_*LP^X5<#|pVrMH5O{`8lAo5^$sn{Lj(%qYx5K)Gz0?v(-z zv%(mTDwr5@I8tg68epr8Els#B_VC>BLr5g@3(G)D)7e^PeiMdUFQ=sPYhA&moBQ_n z>d~?{!OT(|q_uql$AZpMC(SoJYIMWCl&ESmozjTO^m6&tuZNuWemMe`FKrl=axjKJ zM0kFw{-2(bfloxid>J=#;_{Zt2LYw?430ikk`bG8Pn08I7`SuC_m(}s4VKxx|a@usi?iJA=BB9r^<||%u3=ovRxmRDGY{*LVjD; zF*$79tC}MI;8%%dD1GC~DGukI^oukWkneLc5+z!Dg;3yDuVGE@t_|_xLW|A3_j1gJ zV+6XXq*5;>mV1y;imhTj?`E)?uofb!h4oU#hunU!gEq7`5irYyj_Tk`F*Nf?-rU-% zW_e!Ik@)oPqN&%M$8l3gr)Vmho25OAqJe>-@Yfm)_tp1tsR!&fZu8Y(OP#M1K@Lz- zCv1Gt=_%s-UH3e){!u{|$%d?-42M}n8Z^j;`Dg#L6k7zt`1vasT-`Ls zfj3z*duN5T$EH9gD=l12w(m5N#L1r9dl{41{fc^cTo~oWLme4CCp!JG$~+=9#eY=Z zw15=Q`3oQ;`|-H9X_UF5VU51VV|y~iX#jChw6{$Ah*z%McBw0Jq;74juO5S?&UMb5 zcsb{s+5vr|-$f5na**-xTG?T?{d^8i zF9%Z2qG|ZGocFb(qN4)m&TR)XY{pvLmX&;E;GHa;JbW#Oy27>tX}f%n#dygG-;oSY z7+Nry^#ZuD{=_aPp4ckP0As@_SYS_B+^xDI?*H`SQWwLlJX_qEtsBbnJDz?n3#D2? zGmw8QHViMHbt_c~hX9_P(B*}1knH1xF$_IbMM@LHXpqK`lkR4SJPb*7o!WY7Vk210D!a#)n(T;=w| zm@VkFnY|ZqrB+n;{KU@2twNTcUv~AY82N1I!7)#Cr;>B6Z2G$EnEN}F!{A4bFqhf~ z7|H;pp6gGfd>bkG(v{6#6%@wsgw1cO--^})Q;x8#ZyHTPfcyE&V}-EaAMhe)n!GAC zN#+35(#D;mFK8FwWIC)+9H?1`QJqURov7oRLzMNCKhkMsG;efG*R#D~e)S4_m!`{$U*7HBB&w*zRc4t%F!LHc07B+`L^8$D zNGM)nA%pBkq7Dh*LSXsVHDvaOoNN;kzYclrk`)zVRJut=3$^qLd0sf}i_5rr&z;}# zl0}KluOpVjvbB96?y$WP^`}Ib{uYIB@^Wy{Ss&$B+Fq${AJ!Z%lwVy;_z;$ol`-#` z5{~xW-~it4dT_I01H&~m9-0<9D9x7Zmt9r*^UrT?>d{<>gT>4rIBk;nS+m$Y%;?KUzx!;3h+wQY^ePQ> z#2_l8;FIj_=#)w)l4AbkTTm*vKTkT{Cz!i$v?sOsGg5)(4g&b6N)gNnmljyruj)x7 zlTu;h^=pMoncpmKb1eoeq&f99GeRpj=xX-;(@~^H=7-*SuAsZxqG%kfChOW z4a96o%D_J&^UHv5dBZvyoTi1OxU+U!YNc&{oSzqTE3eZTbG2x)I^@2*AGMNw7ney* zt%kpc0FXR{Wrlg*e(kA(;_XHvtvBNm?kQw>clQVoyN9MW&pM{Qx}1CCDl@6k_rxMw z#mGZ^rFXd4ygbikT;Vc%J2<6sUO9jDTeqX)!cSX#{#u{5h@!%sd0%w76j=~z*;&wB zKsK1A532rdV+b@PLz5la17v+%&Wg3k5D5mO-4j+!DUL#dodjW#3Ah z4QeOqV2AI-N3S$#SVPm-;aK^^G`RX19ishlp-luc$x2u8vn7XPzem|BGUxft6d)FR zX*UZ@*3?*^DQAWeTr%EgOOS(k-wAo>rqVRV&Jw|WK*I)r=f!&bR|6y(PIE+t;YtV{y*|Zv7t3J}a{^Q!Zy-OHo z(Xy)cBw?)?i?n^Eyw$>!QQs&KC(B{Twow;K^Jvm)Fs}?V5t*bPe$jJ_a)wKn^V1;J zAK0r6vi4_Nej>(j$1HrV_2FcWW#ooqp8jRJ9qju$UirIA3j2xW<)`cj!R2o?P>>+~uEbASQ}QQ6~}__VdMXme`8ZU#s}Z2(0uq&n~OVQe{khqB4KRVqUTxc~$_> zM#90aoGbBy5eZ(g(yjG0YBS3xRcs*(>~<`Dcf(QjmY;3S*=P&#EZ1&b1!%7a>Hmme zX<6#z@QyEj&4J!{{LRKqBUp`K?!h0R_kEoPQumRf_Lc%8*2+)X8Gf+P&kqeJSmX3} zL^r7E@`%aI-Zcv#lObP~8+KDDta;a0!s9MnFeTl?b@A-Vp4(hL>ElvlF@04kXQg*- zdW*{>|J>G^QnXZs{+GG}eQQKK=sN>};X3Ewu9O&sKlVwKd{YRScxH3=GR?sCLBxCM z(}iGB7?IgWj<6eiCBj7J)9f~a84lVZE~^<8Rx?9u`XN2U`vWF4k@x5IoB+6J0j_+R zS<_D`vb{+4wgu15ICiktvQ<&jhn0SrYDW6R7#l^3Jxy&`mLF!`aU-vj3cy<+0rPTK zNO}v^Y;;-X6#;+bV)RFQ;&RC}l>whn(*6<(&RJjbw+oYd$odfhWS4s>ZPC>kQrgVd z+YC9|kXE@S+>PrOMRpY=)FDsdV5a8th_?R?9(>LXyxBW?#?g|8x4`}|AzFJra!C2Pk>#(kQ09V7 zfAi_RcA@LD%Zw#G09tuPtT-m0gY{YdnQe@kPu z`OLUnE-LS^pld4tL=DMN=xw-Xc9UJ8l4`tOB2 z|Ci3FB-wUVVZ%(FVq`S(R_DdOvfxm2BPY7_*h^>3rVc^fyH>kuMZY=^5Kl64EsPp+ r-;1Cc46KMBN&ipU9Hp|lW|6nw6}9Y@R>J=C0Y_C)>s7_ecc1HZ1$8Z1QQ(P(h%bxLQ9>Y#|#-;jAh=v5^p>vmM*hMWTPIo1*laq<} z(_#_lm+MCw&mJ*koeMWljtT0w4Jl%M$Yb9$=6z|Pp(7$XzQUTO1j~PB2iJ1w+j^y0 z?EQi_x0VhmdXabfl-k+_;5H>&{>g#Nhd+PrzAy{djAQVv4DhZD2E9V zLkg%lJ5fJX7_k;eFwt2?8pq$FD$!@HY~&m}kVHtkQkPi3z&5`K+LTIRk~Xuy{5cqMX!uc!=u>n?u*RY>vNu*0uD$_TH_TQ_AUwLm7Jq2WbEkGqb|lT0dR6(p8mK+_dw;x3Ghi ze$=SDJQt5LmVY5IJ157Gg>KKo!=t^omv)Ct%uIRgR%crseje8Ac6GrP4yEj?akAlf z)XcZMyzGuf8!2mTY;LyKyuo@kXrpQ;ovN*)PWSisEo^M)>TH>aWSK_}ej0zj<@~VD zR+>*UtjRNbanU6$J-z#nI+XEVj{sVp%f#ILeu$nxKIUpQI}Jc2Cw=;4gyo^B{F-=n zc2*|RFzMOT9!W#^y19T;bwwZv7B4F*%%D633tGlbVFj!-2ZM4_WZyE|1}Ss(^NVX$ zlb(h2x-zSo7&R%k)qy-90=(wcXi)Rr;an&6LdDzY2M>mahbf5U7Rd|92Aljb+!fOt zGyP9ijm0Cca$2oXyq7O)!=VUMQzpOPTI9wC8fcrJcS@V6bRcp7FK?E+?Ay{c6RU&PV!OAWrnDs(Qq2Cf>05(YJP?6Y`1rSI8xk>8OVx1LL_1}X&)ho07w`>yx9~v4;mr1Wz#CrSo?GE3lCFPk0Ke3%7HC0sr2;7~G{^*8B z2x^P7_Dtp6>2(zxv0=$D>eNYimGX%|z{eW*!L57j#_} zdlEwM_;A#`=yx({$>zOgbf+^TGjmZ*4O=WTEQt$#r^3Gd%NO>k>1hh5$$B<(Lqj^? zWofCHQN6!vUX^`iSs9BLcI8pn`uhvX?M?tN)AaD5+>TdQ1@U3}X?8Z(amaNzW8~3X zILxf{n(M$KXoQfhHys?sJYT&{<~QoRu|KAij;GG0--|B?9VG{6;;`!_ODgxskz`_6 zbnr#y;oo0h|9wx7+T5$IHTv?qzrUYQRz@4v3cFj$2@Y_<*!hw`pwKIltAOUK`qNAG z(R=9Y>k~%T3l|lP8?L2(j9OT5CNND;O|`)p8;L|#Mry(s+I2rKCr1YkWj{j*rlf7G z`!>8BU5Bfv(1Dt3@dGbRIE0!$`u^Cs4aC{RJXGg%W+|!3O*0UVLkJ=XFB= ziOgU=l)Zqx4ZFx@$k$STY@VwgDtAxf^%XL{oMyDJ*seDxSTo`G0D0OBE^CZDADBgv zQIfp(CG@*U0YA5=KYUvS!{C{LFcb>aGB!3&o3E>@)Acft0)fnTGHXVWmL5-fa@Y&N zEI&+dgE>6?!P|Uoh6keG!T;0co39EQjgF#N4dB-mdoEQ*p2S&0lZXXH6kYOmts!%}R%2 z*{Yp8sTYXA_qE}oR{`at0da{m2Ps1Oa5y}o?!ng%bX{Rlk3yi(W_ANNzFHF|f*I7}Yh5aEu z)9<}5SI_hh-2JwC{!e-<`SI6xcHgrD7_J9cIfpC?R4|^Y?TjdpajymN{{H^R9Hzc% zk5N&eca5+%)&!I46NEBWOzpThIPk8luGVgh`>M2B&L>aVH{v9UaeFfr)7yF;9ugov zNl8fr-FkD@=ZKXh!|MgiV8y)nLo`o4lvT{!F{$u9wag{GR-k#A$bZR$jZ*i z$xvn@aJNrCaeP%c252KTn@jf zB#JQ4^mt=+1sVeuG9!r|IW;u~tz0Im#nSe%p&@>gor8k|q}r>~n!8f^VEh#)+PWbY{Iu_$mcf%Tqnv%Jm6{O)%avuZdfF68meIWZW%D_`6T!k!-cfQn}cT>*>M74saoY$opK;SDj+Kf7Rh zJ3&QzH1=XwRFaIL;Un(yV6=3!a&vQE%&-3Z<8;Ishm*(H$*eMZDRTFaNUm0844b3^ zhfB9v4)*uqnwrUdnX-vtD_FTt{l4-|c8^`hHJ}ASaeKkcazC87TCKdhxMSGZKaC3= z7EER_J;#lra`N-}vP?wFEG3nBAkG2uP)2bH2?~;rg*ZE$>!}=*hP*swVmIEEl%)OE z%GJ}85naaxx?J=0`vqY(AKmc1X~gkzW4jYehH7nXtp}{fp{dD^XV(S|`kQ@TS*G0g zD&vLNPSy{(+uPd{VB>I`S^gQ(eZ2;xYB3;T+FK$at*k^nO>_lW>+NMc=URBdyFpqbho6|8g_K*; z=b4GUT2}rW#UP(5Bylb^n<8G1(T8(xJ{1J>1mpss>RB&dux_x?Qd-#BCd|&>U06@l z;(unT@=Ix1ShAFiAx+la2m4C&BOjjFot+(&*g3d~M?J_weDf){QpVf+GOHh+2N>Mv zQBY7Ifc@iN%&9fobW?K`hTI6dW2X+L+=Tbk-mUL}D?TIl=|@d7y`Ja^(bLnzm6SL| z!cO;>__ + +==== Setup docker + +To setup docker follow the instuctions from https://docs.docker.com/engine/install/[docs.docker.com]. + +=== Create a Docker compose + +Paste content into a `docker-compose.yml` +---- +services: + openems_backend: + image: openems/backend:latest + container_name: openems_backend + hostname: openems_backend + restart: unless-stopped + volumes: + - openems-backend-conf:/var/opt/openems/config:rw + - openems-backend-data:/var/opt/openems/data:rw + ports: + - 8079:8079 # Apache-Felix + - 8081:8081 # Edge-Websocket + - 8082:8082 # UI-Websocket + + openems-ui: + image: openems/ui-backend:latest + container_name: openems_ui + hostname: openems_ui + restart: unless-stopped + volumes: + - openems-ui-conf:/etc/nginx:rw + - openems-ui-log:/var/log/nginx:rw + environment: + - UI_WEBSOCKET=ws://:8082 # Change to your actual hostname or ip + ports: + - 80:80 + - 443:443 + +volumes: + openems-backend-conf: + openems-backend-data: + openems-ui-conf: + openems-ui-log: +---- + +=== Run compose file + +To start the previously created `docker-compose.yml` run the command: +---- +docker compose up -d +---- + +=== Check logs + +To check if the container is up and running, check `docker ps`: + +image::deploy-docker-backend.png[docker ps] + +or read its logs with: +---- +docker logs openems_backend +---- + +NOTE: If you want to run the backand with an InfluxDB instance as well, see: https://github.com/OpenEMS/openems/tree/develop/tools/docker/backend. \ No newline at end of file diff --git a/doc/modules/ROOT/pages/edge/deploy.adoc b/doc/modules/ROOT/pages/edge/deploy.adoc index 58059b150a3..631099c92c2 100644 --- a/doc/modules/ROOT/pages/edge/deploy.adoc +++ b/doc/modules/ROOT/pages/edge/deploy.adoc @@ -8,6 +8,8 @@ :icons: font :imagesdir: ../../assets/images +== Debian Linux + This chapter explains how OpenEMS can be deployed on a Debian Linux Internet-of-Things Gateway. Similar techniques will work for other operating systems as well. This guide covers a simple, manual approach. For productive systems it is required to automate deployment to IoT devices. Good approaches include a Debian package repository that provides *.deb-files and third-party tools like http://www.eclipse.org/hawkbit/[Eclipse Hawkbit]. This is out-of-scope for this small guide. @@ -19,7 +21,7 @@ Prerequisites: * Setup an SSH client to connect to the Linux console, e.g. http://www.9bis.net/kitty/[KiTTY] * Setup an SCP client to copy the JAR file via SSH, e.g. https://winscp.net/eng/docs/lang:de[WinSCP] -== Connect via SSH and SCP +=== Connect via SSH and SCP . Connect via SSH using KiTTY .. Open KiTTY and connect to the target device. @@ -132,3 +134,74 @@ The command restarts the service (_systemctl restart openems_) while not waiting + .OpenEMS Edge start-up image::deploy-openems-start.png[OpenEMS Edge start-up] + + +== Docker + +This chapter explains how OpenEMS can be deployed using our official https://hub.docker.com/r/openems/edge[Docker image]. + +Prerequisites: + +* A amd64 or arm64 device running Linux. You need the IP address and SSH access. +* A working docker environment. To setup follow instruction from https://docs.docker.com/engine/install/[docs.docker.com]. + +=== Prepare system + +==== Connect to the device + +image::deploy-docker-ssh.png[SSH into device] + +==== Check docker installation + +image::deploy-docker-edge-check-version.png[Check docker installation] + +__if not already installed, follow <>__ + +==== Setup docker + +To setup docker follow the instuctions from https://docs.docker.com/engine/install/[docs.docker.com]. + +=== Start Container + +==== Create a Docker compose + +Paste content into a `docker-compose.yml` +---- +services: + openems-edge: + image: openems/edge:latest + container_name: openems_edge + hostname: openems_edge + restart: unless-stopped + volumes: + - openems-edge-conf:/var/opt/openems/config:rw + - openems-edge-data:/var/opt/openems/data:rw + ports: + - 8080:8080 # Apache-Felix + +volumes: + openems-edge-conf: + openems-edge-data: +---- + +==== Run compose file + +To start the previously created `docker-compose.yml` run the command: +---- +docker compose up -d +---- + +==== Check logs + +To check if the container is up and running, check `docker ps`: + +image::deploy-docker-edge.png[docker ps] + +or read its logs with: +---- +docker logs openems_edge +---- + +--- + +NOTE: If you want to start a UI instance as well, see: https://github.com/OpenEMS/openems/tree/develop/tools/docker/edge. \ No newline at end of file From a98d38c42969b782d93db720204829b258026e10 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Fri, 11 Oct 2024 22:39:42 +0200 Subject: [PATCH 02/19] FEMS Backports 2024-10 (#2835) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FFR: Fast Frequency Response Controller - Includes: - 1. Fast Frequency request controller. - 2. Junit test cases. - 3. Step Response Tester changes in the simulated meter. To test FFR with the simulator meter's frequency. - EVCS HardyBarth: set "No meter values available" as WARN + translate - Changed StateChannel "METER_NOT_AVAILABE" of the HardyBarth component to level WARNING - Added translation files for bundle and channel - Batteries/Battery-Inverters: downgrade FAULT states to WARNING - Downgrade of all possible FAULT states to WARNING states - Whenever one of these states would be true, the EssGeneric will stop the battery and the inverter. If this is necessary, it must be specifically mentioned and the state should have a proper description of the fault. - UI: fix non refactored charts - Display of non refactored charts did not work, because of latest `debounce` change, e.g. Storage history - EVCS KEBA: downgrade fault for Dip Switch 2.6, because it is now used for §14a - ModbusBridge: invalidate Elements on removeProtocol() - Invalidate (i.e. set UNDEFINED/null) Elements on `removeProtocol()` - JUnit test shows behaviour. REGISTER_100 is null after `removeProtocol()` - ModbusTcpApi: improvements - Fixes error taking alias (of ess) instead of Component ID. This will fix dynamically generating channel addresses. - ActivePowerLimit and ReactivePowerLimit result in NaN if null - Improved reusability of code through methods - Removed constant strings - KACO Battery Inverter: fix state transition ERROR to UNDEFINED, Frequency channel - Return from Error Handling to Undefined - Fix Hz channel name - Updates for FENECON Home battery - Adjust modbus task priority level - Reduce number of tasks - Improve stopping process - Improve contains task check in Modbus Bridge - EVCS: Hystereses with info in UI - Hystereses for Charging stations two config values are now shown in the ui for the evcs controller - UI: add role is at least admin for DebugMode - UI: fix timeOfUsePrice zero bug: 0 != null - UI: Username to lowerCase & eslintRule - eslint-rule to prevent getting fdescribe or xdescribe into production - lowercasing username - Generic-ESS state machine improvement - The main approach is to have a statemachine which can be run all our current systems and avoiding retry start process. - Related to any 'Error Handling' topic should be improved or implemented seperately and required a defined generic 'Error handling' concept, which can be applied for all systems. - Instead of maximum start or stop time attempts, starting or stopping timeouts are taken care of and shown in UI as an failure. By doing this, its aimed to let the service or engineers to analyze the failure, and forbid to harm the battery by taking an action without know-how knowledge. - KACO BatteryInverter: component activate improvements + ModbusSlave - Removed second activate annotation - always set configuration in activate - EssPower: handle Exceptions in handleEvent() - UI: style improvements - allow disabling tooltips in charts - remove non existent fixDigitalOutputWidget from history template - break title into multiple lines if overflowing - Fix critical race condition in Modbus Bridge - Modbus-Bridge: **fix criticial race condition! If ON_EXECUTE_WRITE was called before ON_BEFORE_PROCESS_IMAGE, the forever()-loop could get stuck forever, because `cycleTasks` was never initialized.** - A usual cycle looks like this: ON_BEFORE_PROCESS_IMAGE -> _other events_ -> ON_EXEUCTE_WRITE -> _long wait_ -> ON_BEFORE_PROCESS_IMAGE -> ... - The time between ON_BEFORE_PROCESS_IMAGE and ON_EXEUCTE_WRITE is typically very short, especially before all Controllers are activated. This applies for the time when usually a Modbus Bridge gets activated, because this happens before all Devices and Controllers. As a result it was very unlikely, that Modbus-Bridge would receive ON_EXEUCTE_WRITE before ON_BEFORE_PROCESS_IMAGE. During activating and modifying of many components at the same time after initial setup (IBN via App Center) it seems it was much more likely for this race condition to happen. - In this very unlikely case, unfortunately the `CycleTasksManager` would get stuck, as can be seen in this log: ``` [modbus1] State: FINISHED -> WRITE (onExecuteWrite) [modbus1] WRITE -> WRITE (onExecuteWrite) [modbus1] WRITE unchanged (in onBeforeProcessImage) Delay [0] (time is invalid) [modbus1] WRITE -> WRITE (onExecuteWrite) [modbus1] WRITE unchanged (in onBeforeProcessImage) Delay [0] (time is invalid) ... ``` - This problem was solved by initializing `this.cycleTasks` from `tasksSupplier`. - New behaviour is proven to work in `CycleTasksManagerTest.java`, `testExecuteWriteBeforeNextProcessImage()` which would fail with previous code. - Modbus-Bridge: - ignore events before activate - renew logging to show Component-ID everywhere - RRD4j: reduce log warnings; remove stacktrace; add channelAddress - WsData: - reduce logging noise - Instead of `[ctrlBackend0] [BackendApi.WsData []] Unable to send message:` - Only: `[ctrlBackend0] Unable to send message:` - Extend payload from 100 to 200 chars - EnergyScheduler: - remove unnecessary log message - add log verbosity - cleanup config - AbstractWorker: - synchronized activate/modified code - before calling activate twice would result in an error - it's still not possible to stop and restart a worker (e.g. when enabling/disabling a component that uses a worker) - AbstractOpenemsModbusComponent: - combine common code of activate() and modified() - removeProtocol and retryModbusCommunication are handled internally by ModbusBridge - fix modbusReference in modified() - Home Battery: - wait for activate to set hardware type default - ComponentManager is required; otherwise ClockProvider is null during init - Ess.Power: - fix ArrayIndexOutOfBoundsException - fix ConcurrentModificationException when ESS are added/removed - GoodWe Battery-Inverter: fix handleEvent called before activate. Possible NPE. - UI: Style adjustments: currentVoltageChart - Switch left and right YAxis - Remove beginAtZero from current and voltage chart - hide gridLines for right yAxis - Voltage - change order of labels, now first consumptionMeters then chargers - UI: round slightly negative values - Round negative `QueryHistoricQuery` -Data around a defined threshold to 0 - GridOptimized charge requires a production power to run properly - Avoid exception and inform user about unexpected behaviour of controller --------- Co-authored-by: Pooran Chandrashekaraiah <46567310+pooran-c@users.noreply.github.com> Co-authored-by: Johann Kaufmann <165755282+johannk24@users.noreply.github.com> Co-authored-by: Hueseyin Sahutoglu <34771592+huseyinsaht@users.noreply.github.com> Co-authored-by: Sebastian Asen <47855186+sebastianasen@users.noreply.github.com> Co-authored-by: Lukas Rieger <73471197+lukasrgr@users.noreply.github.com> Co-authored-by: Stefan Feilmeier <3515268+sfeilmeier@users.noreply.github.com> Co-authored-by: Fabian Brandtner <10850256+fabian94533@users.noreply.github.com> Co-authored-by: Michael Grill <59126309+michaelgrill@users.noreply.github.com> --- cnf/pom.xml | 34 +- .../backend/edge/application/TestClient.java | 7 +- .../openems/backend/b2bwebsocket/WsData.java | 8 +- .../backend/b2bwebsocket/TestClient.java | 7 +- .../backend/b2bwebsocket/WsDataTest.java | 35 + .../openems/backend/edgewebsocket/WsData.java | 9 +- .../backend/edgewebsocket/WsDataTest.java | 37 + .../backend/uiwebsocket/impl/WsData.java | 17 +- .../backend/uiwebsocket/impl/WsDataTest.java | 35 + .../src/io/openems/common/channel/Unit.java | 9 +- .../io/openems/common/timedata/Timeout.java | 55 + .../common/websocket/AbstractWebsocket.java | 16 +- .../websocket/DummyWebsocketServer.java | 7 +- .../common/websocket/WebsocketUtils.java | 10 +- .../io/openems/common/websocket/WsData.java | 11 +- .../openems/common/worker/AbstractWorker.java | 11 +- .../openems/common/timedata/TimeoutTest.java | 27 + .../ClientReconnectorWorkerTest.java | 19 +- .../websocket/DummyWebsocketServerTest.java | 15 + .../common/websocket/WebsocketUtilsTest.java | 15 + .../openems/common/websocket/WsDataTest.java | 17 + io.openems.edge.application/EdgeApp.bndrun | 8 +- .../BydBatteryBoxCommercialC130.java | 140 +- .../fenecon/home/BatteryFeneconHome.java | 2 +- .../fenecon/home/BatteryFeneconHomeImpl.java | 16 +- .../battery/fenecon/home/ModbusHelper.java | 12 +- .../fenecon/home/statemachine/Context.java | 4 - .../home/statemachine/GoStoppedHandler.java | 36 +- .../BatterySoltaroClusterVersionB.java | 68 +- .../soltaro/cluster/versionb/SingleRack.java | 46 +- .../BatterySoltaroClusterVersionC.java | 70 +- .../soltaro/cluster/versionc/RackChannel.java | 22 +- .../BatterySoltaroSingleRackVersionA.java | 22 +- .../BatterySoltaroSingleRackVersionB.java | 6 +- .../BatterySoltaroSingleRackVersionC.java | 30 +- ...BatteryInverterKacoBlueplanetGridsave.java | 33 +- ...eryInverterKacoBlueplanetGridsaveImpl.java | 19 +- .../blueplanetgridsave/KacoSunSpecModel.java | 4 +- .../statemachine/ErrorHandler.java | 11 +- .../refu88k/BatteryInverterRefuStore88k.java | 50 +- .../sinexcel/BatteryInverterSinexcel.java | 4 +- .../bridge/modbus/BridgeModbusSerialImpl.java | 9 +- .../bridge/modbus/BridgeModbusTcpImpl.java | 9 +- .../modbus/api/AbstractModbusBridge.java | 57 +- .../api/AbstractOpenemsModbusComponent.java | 45 +- .../edge/bridge/modbus/api/Config.java | 60 + .../bridge/modbus/api/ModbusComponent.java | 8 +- .../modbus/api/worker/ModbusWorker.java | 19 +- .../worker/internal/CycleTasksManager.java | 79 +- .../worker/internal/DefectiveComponents.java | 49 +- .../worker/internal/TasksSupplierImpl.java | 66 +- .../bridge/modbus/test/DummyModbusBridge.java | 3 +- .../modbus/BridgeModbusTcpImplTest.java | 29 +- .../internal/CycleTasksManagerTest.java | 49 +- .../internal/DefectiveComponentsTest.java | 7 +- .../internal/TasksSupplierImplTest.java | 16 +- .../src/io/openems/edge/common/meta/Meta.java | 16 +- .../modbusslave/ModbusRecordChannel.java | 7 + .../modbusslave/ModbusRecordConstant.java | 43 + .../modbusslave/ModbusRecordCycleValue.java | 23 +- .../modbusslave/ModbusRecordFloat32.java | 2 +- .../modbusslave/ModbusRecordFloat64.java | 2 +- .../modbusslave/ModbusRecordString16.java | 5 +- .../modbusslave/ModbusRecordUint16.java | 3 +- .../ModbusRecordUint16BlockLength.java | 5 +- .../modbusslave/ModbusRecordUint16Hash.java | 4 +- .../modbusslave/ModbusRecordUint32.java | 3 +- .../modbusslave/ModbusRecordUint64.java | 59 + .../ModbusRecordUint64Reserved.java | 14 + .../modbusslave/ModbusSlaveNatureTable.java | 34 +- .../edge/common/modbusslave/ModbusType.java | 1 + .../src/io/openems/edge/common/sum/Sum.java | 2 + .../modbusslave/ModbusRecordFloat32Test.java | 30 + .../modbusslave/ModbusRecordFloat64Test.java | 30 + .../modbusslave/ModbusRecordString16Test.java | 42 + .../modbusslave/ModbusRecordUint16Test.java | 52 + .../modbusslave/ModbusRecordUint32Test.java | 40 + .../modbusslave/ModbusRecordUint64Test.java | 40 + .../api/backend/WebsocketClient.java | 1 + .../edge/controller/api/backend/WsData.java | 16 - io.openems.edge.controller.api.modbus/bnd.bnd | 1 + .../api/modbus/AbstractModbusTcpApi.java | 2 +- .../GetModbusProtocolExportXlsxResponse.java | 28 +- .../ControllerApiModbusTcpReadWriteImpl.java | 105 +- ...ntrollerApiModbusTcpReadWriteImplTest.java | 30 +- io.openems.edge.controller.api.mqtt/bnd.bnd | 4 +- .../edge/controller/api/websocket/WsData.java | 17 +- .../.classpath | 12 + .../.gitignore | 2 + .../.project | 23 + .../org.eclipse.core.resources.prefs | 2 + .../bnd.bnd | 16 + .../doc/statemachine.md | 18 + .../readme.adoc | 333 ++++ .../ess/fastfrequencyreserve/Config.java | 54 + .../ControllerFastFrequencyReserve.java | 283 ++++ .../ControllerFastFrequencyReserveImpl.java | 312 ++++ .../enums/ActivationTime.java | 30 + .../enums/ControlMode.java | 32 + .../enums/SupportDuration.java | 32 + .../SetActivateFastFreqReserveRequest.java | 228 +++ .../statemachine/ActivationTimeHandler.java | 106 ++ .../BufferedTimeBeforeRecoveryHandler.java | 103 ++ .../statemachine/Context.java | 64 + .../statemachine/DeactivationTimeHandler.java | 81 + .../statemachine/PreActivationHandler.java | 53 + .../statemachine/RecoveryTimeHandler.java | 29 + .../statemachine/StateMachine.java | 62 + .../SupportDurationTimeHandler.java | 64 + .../statemachine/UndefinedHandler.java | 40 + .../test/.gitignore | 0 .../ess/fastfrequencyreserve/JsonRpcTest.java | 68 + .../ess/fastfrequencyreserve/MyConfig.java | 107 ++ .../MyControllerTest.java | 295 ++++ .../MyControllerTest2.java | 185 +++ .../ess/fastfrequencyreserve/TestClient.java | 122 ++ .../ControllerEssGridOptimizedCharge.java | 25 + .../ControllerEssGridOptimizedChargeImpl.java | 7 + .../translation_de.properties | 2 + .../translation_en.properties | 2 + ...trollerEssGridOptimizedChargeImplTest.java | 37 + .../openems/edge/controller/evcs/Config.java | 6 + .../edge/controller/evcs/ControllerEvcs.java | 10 +- .../controller/evcs/ControllerEvcsImpl.java | 96 ++ .../evcs/ControllerEvcsImplTest.java | 83 + .../edge/controller/evcs/MyConfig.java | 22 + .../FeneconHomeComponents.java | 23 +- .../app/timeofusetariff/AwattarHourly.java | 3 +- .../edge/app/timeofusetariff/EntsoE.java | 3 +- .../edge/app/timeofusetariff/GroupeE.java | 3 +- .../edge/app/timeofusetariff/RabotCharge.java | 3 +- .../timeofusetariff/StadtwerkHassfurt.java | 3 +- .../timeofusetariff/StromdaoCorrently.java | 3 +- .../edge/app/timeofusetariff/Tibber.java | 3 +- .../ComponentAggregateTaskImpl.java | 32 +- .../core/appmanager/translation_de.properties | 7 +- .../core/appmanager/translation_en.properties | 7 +- .../core/appmanager/validator/Checkables.java | 20 +- .../io/openems/edge/core/meta/MetaImpl.java | 17 + .../src/io/openems/edge/core/sum/SumImpl.java | 1 - .../edge2edge/common/AbstractEdge2Edge.java | 22 +- .../src/io/openems/edge/energy/Config.java | 3 + .../edge/energy/EnergySchedulerImpl.java | 60 +- .../io/openems/edge/energy/LogVerbosity.java | 13 + .../edge/energy/optimizer/Optimizer.java | 21 +- .../edge/energy/EnergySchedulerImplTest.java | 6 +- .../test/io/openems/edge/energy/MyConfig.java | 29 +- .../io/openems/edge/ess/api/HybridEss.java | 2 + .../edge/ess/api/ManagedSymmetricEss.java | 2 +- .../io/openems/edge/ess/api/SymmetricEss.java | 1 + .../io/openems/edge/ess/core/power/Data.java | 4 +- .../edge/ess/core/power/EssPowerImpl.java | 20 +- .../ess/core/power/data/LinearSolverUtil.java | 44 +- .../core/power/data/LinearSolverUtilTest.java | 44 + .../common/AbstractGenericManagedEss.java | 22 +- .../ess/generic/common/GenericManagedEss.java | 144 +- .../generic/offgrid/EssGenericOffGrid.java | 2 +- .../symmetric/EssGenericManagedSymmetric.java | 49 + .../EssGenericManagedSymmetricImpl.java | 12 +- .../symmetric/statemachine/Context.java | 47 +- .../symmetric/statemachine/ErrorHandler.java | 50 +- .../statemachine/StartBatteryHandler.java | 42 +- .../StartBatteryInverterHandler.java | 46 +- .../statemachine/StartedHandler.java | 16 +- .../statemachine/StopBatteryHandler.java | 46 +- .../StopBatteryInverterHandler.java | 43 +- .../statemachine/StoppedHandler.java | 16 +- .../statemachine/UndefinedHandler.java | 34 +- .../EssGenericManagedSymmetricImplTest.java | 32 + .../edge/evcs/hardybarth/EvcsHardyBarth.java | 4 +- .../evcs/hardybarth/translation_de.properties | 1 + .../evcs/hardybarth/translation_en.properties | 1 + .../keba/kecontact/EvcsKebaKeContact.java | 3 +- .../GoodWeBatteryInverter.java | 2 +- .../GoodWeBatteryInverterImpl.java | 4 +- .../io/openems/edge/goodwe/common/GoodWe.java | 44 +- .../simulator/meter/grid/acting/Config.java | 7 + .../grid/acting/SimulatorGridMeterActing.java | 36 + .../acting/SimulatorGridMeterActingImpl.java | 65 + .../simulator/meter/grid/acting/State.java | 33 + .../grid/acting/StepResponseHandler.java | 57 + .../simulator/meter/grid/acting/MyConfig.java | 22 + .../SimulatorGridMeterActingImplTest.java | 23 + .../edge/timedata/rrd4j/Rrd4jReadHandler.java | 3 +- .../edge/timedata/rrd4j/Rrd4jSupplier.java | 2 +- io.openems.wrapper/bnd.bnd | 4 + io.openems.wrapper/helins-linux-i2c.bnd | 20 + ui/.eslintrc.json | 11 + ui/angular.json | 15 +- ui/package-lock.json | 1444 +++++++++-------- ui/package.json | 4 +- ui/src/app/app-routing.module.ts | 3 +- .../app/edge/history/abstracthistorychart.ts | 62 +- .../consumption/details/details.overview.html | 2 +- .../common/energy/chart/channels.spec.ts | 4 +- .../edge/history/common/energy/chart/chart.ts | 1 + .../common/grid/details/details.overview.html | 4 +- .../common/grid/details/details.overview.ts | 19 +- .../common/grid/overview/overview.html | 2 +- .../history/common/grid/overview/overview.ts | 20 +- .../production/details/details.overview.html | 2 +- .../common/production/overview/overview.ts | 4 +- .../app/edge/history/history.component.html | 4 - .../TimeOfUseTariff/modal/statePriceChart.ts | 5 +- .../live/Controller/Evcs/modal/modal.html | 12 + .../edge/live/Controller/Evcs/modal/modal.ts | 3 + ui/src/app/edge/live/live.module.ts | 16 +- ui/src/app/index/login.component.ts | 18 +- ui/src/app/index/login.spec.ts | 16 +- .../components/chart/abstracthistorychart.ts | 23 +- .../components/chart/chart.constants.ts | 4 +- ui/src/app/shared/components/chart/chart.html | 3 +- .../currentVoltage/chart/asymmetricMeter.ts | 12 +- .../currentVoltage/chart/symmetricMeter.ts | 13 +- .../currentVoltage.overview.html | 3 +- .../edge/offline/offline.component.html | 6 + .../edge}/offline/offline.component.ts | 12 +- .../components/edge/offline/offline.module.ts | 18 + .../flat/abstract-flat-widget-line.ts | 19 + .../flat-widget-line-item.html | 5 +- .../flat-widget-line/flat-widget-line.html | 4 +- .../flat/flat-widget-line/flat-widget-line.ts | 3 - .../form-field-multi-step.html | 66 + .../form-field-multi-step.ts | 70 + .../formly-field-checkbox-with-image.html | 11 +- .../formly-field-checkbox-with-image.ts | 19 +- .../components/header/header.component.ts | 17 +- .../app/shared/components/shared/converter.ts | 14 + .../app/shared/components/shared/formatter.ts | 29 +- .../components/shared/testing/common.ts | 7 +- .../components/shared/testing/tester.ts | 1 + .../app/shared/jsonrpc/jsonrpcutils.spec.ts | 10 + ui/src/app/shared/jsonrpc/jsonrpcutils.ts | 20 + ui/src/app/shared/service/defaulttypes.ts | 12 +- ui/src/app/shared/service/service.ts | 20 +- ui/src/app/shared/service/utils.spec.ts | 10 +- ui/src/app/shared/service/utils.ts | 10 +- ui/src/app/shared/shared.module.ts | 3 + ui/src/app/user/user.component.html | 11 +- ui/src/app/user/user.component.ts | 8 + ui/src/assets/i18n/de.json | 5 +- ui/src/assets/i18n/en.json | 7 +- ui/src/global-ion-custom.scss | 11 + ui/src/global.scss | 14 +- 244 files changed, 6793 insertions(+), 1925 deletions(-) create mode 100644 io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/WsDataTest.java create mode 100644 io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java create mode 100644 io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java create mode 100644 io.openems.common/src/io/openems/common/timedata/Timeout.java create mode 100644 io.openems.common/test/io/openems/common/timedata/TimeoutTest.java create mode 100644 io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java create mode 100644 io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java create mode 100644 io.openems.common/test/io/openems/common/websocket/WsDataTest.java create mode 100644 io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java create mode 100644 io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java create mode 100644 io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java create mode 100644 io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java create mode 100644 io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java create mode 100644 io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java create mode 100644 io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java create mode 100644 io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java create mode 100644 io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java delete mode 100644 io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/.classpath create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/.project create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/test/.gitignore create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest2.java create mode 100644 io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java create mode 100644 io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties create mode 100644 io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties create mode 100644 io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java create mode 100644 io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/data/LinearSolverUtilTest.java create mode 100644 io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_de.properties create mode 100644 io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_en.properties create mode 100644 io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/State.java create mode 100644 io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/StepResponseHandler.java create mode 100644 io.openems.wrapper/helins-linux-i2c.bnd create mode 100644 ui/src/app/shared/components/edge/offline/offline.component.html rename ui/src/app/{edge/live => shared/components/edge}/offline/offline.component.ts (78%) create mode 100644 ui/src/app/shared/components/edge/offline/offline.module.ts create mode 100644 ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.html create mode 100644 ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.ts create mode 100644 ui/src/app/shared/jsonrpc/jsonrpcutils.spec.ts create mode 100644 ui/src/global-ion-custom.scss diff --git a/cnf/pom.xml b/cnf/pom.xml index 83b8c69ddd9..dca7896bc3f 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -139,6 +139,34 @@ 1.5 + + + + io.helins + linux-common + 0.1.4 + + + + + io.helins + linux-i2c + 1.0.2 + + + + + io.helins + linux-io + 0.0.4 + + + + + io.helins + linux-errno + 1.0.2 + io.reactivex.rxjava3 rxjava @@ -254,8 +282,8 @@ org.bouncycastle - bcpkix-jdk15on - 1.70 + bcpkix-jdk15to18 + 1.77 org.dhatim @@ -414,4 +442,4 @@ 3.9 - + \ No newline at end of file diff --git a/io.openems.backend.application/test/io/openems/backend/edge/application/TestClient.java b/io.openems.backend.application/test/io/openems/backend/edge/application/TestClient.java index c6030384134..12c4a97ac8a 100644 --- a/io.openems.backend.application/test/io/openems/backend/edge/application/TestClient.java +++ b/io.openems.backend.application/test/io/openems/backend/edge/application/TestClient.java @@ -92,12 +92,7 @@ public void setOnNotification(OnNotification onNotification) { @Override protected WsData createWsData(WebSocket ws) { - return new WsData(ws) { - @Override - public String toString() { - return "TestClient.WsData []"; - } - }; + return new WsData(ws); } @Override diff --git a/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WsData.java b/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WsData.java index 24298d8c0f2..9276a41a2c1 100644 --- a/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WsData.java +++ b/io.openems.backend.b2bwebsocket/src/io/openems/backend/b2bwebsocket/WsData.java @@ -65,7 +65,11 @@ public SubscribedEdgesChannelsWorker getSubscribedChannelsWorker() { } @Override - public String toString() { - return "B2bWebsocket.WsData [user=" + this.user.getNow(null) + "]"; + public String toLogString() { + var user = this.user.getNow(null); + var userId = user == null // + ? "UNDEFINED" // + : user.getId(); + return "B2bWebsocket.WsData [user=" + userId + "]"; } } diff --git a/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/TestClient.java b/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/TestClient.java index 3f313d2c93b..1e04b149df7 100644 --- a/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/TestClient.java +++ b/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/TestClient.java @@ -93,12 +93,7 @@ public void setOnNotification(OnNotification onNotification) { @Override protected WsData createWsData(WebSocket ws) { - return new WsData(ws) { - @Override - public String toString() { - return "TestClient.WsData []"; - } - }; + return new WsData(ws); } @Override diff --git a/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/WsDataTest.java b/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/WsDataTest.java new file mode 100644 index 00000000000..eeedb52b567 --- /dev/null +++ b/io.openems.backend.b2bwebsocket/test/io/openems/backend/b2bwebsocket/WsDataTest.java @@ -0,0 +1,35 @@ +package io.openems.backend.b2bwebsocket; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +import java.util.Optional; + +import org.junit.Test; + +import io.openems.backend.common.metadata.User; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; + +public class WsDataTest { + + @Test + public void test() throws OpenemsNamedException { + var sut = new WsData(null, null); + assertEquals("B2bWebsocket.WsData [user=UNDEFINED]", sut.toLogString()); + assertEquals(Optional.empty(), sut.getUserOpt()); + assertThrows(OpenemsNamedException.class, () -> sut.getUserWithTimeout(1, MILLISECONDS)); + assertEquals(null, sut.getUser().getNow(null)); + + var user = new User("foo", null, null, null, null, false, null); + sut.setUser(user); + assertEquals("B2bWebsocket.WsData [user=foo]", sut.toLogString()); + assertEquals(Optional.of(user), sut.getUserOpt()); + assertEquals(user, sut.getUserWithTimeout(1, MILLISECONDS)); + assertNotNull(sut.getSubscribedChannelsWorker()); + + sut.dispose(); + } + +} diff --git a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WsData.java b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WsData.java index 180eaca7c5a..6ff6bab290d 100644 --- a/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WsData.java +++ b/io.openems.backend.edgewebsocket/src/io/openems/backend/edgewebsocket/WsData.java @@ -76,10 +76,11 @@ public synchronized Optional getEdgeId() { } @Override - public String toString() { - return "EdgeWebsocket.WsData [" // - + "edgeId=" + this.edgeId.orElse("UNKNOWN") // - + "]"; + protected String toLogString() { + return new StringBuilder("EdgeWebsocket.WsData [edgeId=") // + .append(this.edgeId.orElse("UNKNOWN")) // + .append("]") // + .toString(); } } diff --git a/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java b/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java new file mode 100644 index 00000000000..707168f40e3 --- /dev/null +++ b/io.openems.backend.edgewebsocket/test/io/openems/backend/edgewebsocket/WsDataTest.java @@ -0,0 +1,37 @@ +package io.openems.backend.edgewebsocket; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.Optional; + +import org.junit.Test; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.GenericJsonrpcNotification; +import io.openems.common.jsonrpc.base.JsonrpcMessage; + +public class WsDataTest { + + private static final String EDGE_ID = "edge0"; + private static final JsonrpcMessage JMSG = new GenericJsonrpcNotification("foo", new JsonObject()); + + @Test + public void test() throws OpenemsException { + var sut = new WsData(null); + assertEquals("EdgeWebsocket.WsData [edgeId=UNKNOWN]", sut.toLogString()); + assertThrows(OpenemsNamedException.class, () -> sut.assertEdgeId(JMSG)); + assertThrows(OpenemsNamedException.class, () -> sut.assertEdgeIdWithTimeout(JMSG, 1, MILLISECONDS)); + + sut.setEdgeId(EDGE_ID); + assertEquals("EdgeWebsocket.WsData [edgeId=edge0]", sut.toLogString()); + sut.assertEdgeId(null); + sut.assertEdgeIdWithTimeout(JMSG, 1, MILLISECONDS); + assertEquals(Optional.of(EDGE_ID), sut.getEdgeId()); + } + +} diff --git a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java index c60424b0d3d..b2bfdca34ef 100644 --- a/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java +++ b/io.openems.backend.uiwebsocket/src/io/openems/backend/uiwebsocket/impl/WsData.java @@ -154,14 +154,15 @@ public String assertToken() throws OpenemsNamedException { } @Override - public String toString() { - String tokenString; - if (this.token.isPresent()) { - tokenString = this.token.get().toString(); - } else { - tokenString = "UNKNOWN"; - } - return "UiWebsocket.WsData [userId=" + this.userId.orElse("UNKNOWN") + ", token=" + tokenString + "]"; + protected String toLogString() { + return new StringBuilder("UiWebsocket.WsData [userId=") // + .append(this.userId.orElse("UNKNOWN")) // + .append(", token=") // + .append(this.token.isPresent() // + ? this.token.get().toString() // + : "UNKNOWN") // + .append("]") // + .toString(); } /** diff --git a/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java b/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java new file mode 100644 index 00000000000..8832a3c6dfd --- /dev/null +++ b/io.openems.backend.uiwebsocket/test/io/openems/backend/uiwebsocket/impl/WsDataTest.java @@ -0,0 +1,35 @@ +package io.openems.backend.uiwebsocket.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import java.util.Optional; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; + +public class WsDataTest { + + private static final String USER_ID = "user0"; + private static final String TOKEN = "token"; + + @Test + public void test() throws OpenemsNamedException { + var sut = new WsData(null); + assertEquals(Optional.empty(), sut.getUser(null)); + assertThrows(OpenemsNamedException.class, () -> sut.assertToken()); + assertEquals("UiWebsocket.WsData [userId=UNKNOWN, token=UNKNOWN]", sut.toLogString()); + + sut.setUserId(USER_ID); + sut.setToken(TOKEN); + + assertEquals(Optional.of(USER_ID), sut.getUserId()); + assertEquals(Optional.of(TOKEN), sut.getToken()); + assertEquals(TOKEN, sut.assertToken()); + assertEquals("UiWebsocket.WsData [userId=user0, token=token]", sut.toLogString()); + + sut.logout(); + } + +} diff --git a/io.openems.common/src/io/openems/common/channel/Unit.java b/io.openems.common/src/io/openems/common/channel/Unit.java index 466ea4fbef6..2bcc50113fd 100644 --- a/io.openems.common/src/io/openems/common/channel/Unit.java +++ b/io.openems.common/src/io/openems/common/channel/Unit.java @@ -284,7 +284,12 @@ public enum Unit { /** * Unit of Pressure [bar]. */ - BAR("bar"); + BAR("bar"), + + /** + * Unit of Pressure [mbar]. + */ + MILLIBAR("mbar", BAR, -3); public final String symbol; public final Unit baseUnit; @@ -363,7 +368,7 @@ public String format(Object value, OpenemsType type) { MILLIWATT, WATT_HOURS, OHM, KILOOHM, SECONDS, AMPERE_HOURS, HOUR, CUMULATED_SECONDS, KILOAMPERE_HOURS, KILOVOLT_AMPERE, KILOVOLT_AMPERE_REACTIVE, KILOVOLT_AMPERE_REACTIVE_HOURS, KILOWATT_HOURS, MICROOHM, MILLIAMPERE_HOURS, MILLIOHM, MILLISECONDS, MINUTE, THOUSANDTH, VOLT_AMPERE_HOURS, - VOLT_AMPERE_REACTIVE_HOURS, WATT_HOURS_BY_WATT_PEAK, CUMULATED_WATT_HOURS, BAR -> // + VOLT_AMPERE_REACTIVE_HOURS, WATT_HOURS_BY_WATT_PEAK, CUMULATED_WATT_HOURS, BAR, MILLIBAR -> // value + " " + this.symbol; case ON_OFF -> // diff --git a/io.openems.common/src/io/openems/common/timedata/Timeout.java b/io.openems.common/src/io/openems/common/timedata/Timeout.java new file mode 100644 index 00000000000..bb489594998 --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/Timeout.java @@ -0,0 +1,55 @@ +package io.openems.common.timedata; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; + +public class Timeout { + + private Instant entryTime = Instant.MIN; + private Duration timeout; + + private Timeout(Duration duration) { + this.timeout = duration; + } + + /** + * Get the {@link Timeout} of seconds. + * + * @param timeout the amount seconds + * @return the {@link Timeout} + */ + public static Timeout ofSeconds(int timeout) { + return new Timeout(Duration.ofSeconds(timeout)); + } + + /** + * Get the {@link Timeout} of minutes. + * + * @param timeout the amount minutes + * @return the {@link Timeout} + */ + public static Timeout ofMinutes(int timeout) { + return new Timeout(Duration.ofMinutes(timeout)); + } + + /** + * Sets the entry time. + * + * @param clock the {@link Clock} + */ + public void start(Clock clock) { + this.entryTime = Instant.now(clock); + } + + /** + * Checks the whether time elapsed. + * + * @param clock the {@link Clock} + * @return true if time is elapsed + */ + public boolean elapsed(Clock clock) { + return Instant.now(clock).isAfter(this.entryTime.plus(this.timeout)); + } + +} diff --git a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java index c76b34dd560..f313c175896 100644 --- a/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java +++ b/io.openems.common/src/io/openems/common/websocket/AbstractWebsocket.java @@ -128,11 +128,17 @@ protected final boolean sendMessage(WebSocket ws, JsonrpcMessage message) { } private void sendMessageFailedLog(WebSocket ws, JsonrpcMessage message) { - this.logWarn(this.log, new StringBuilder() // - .append("[").append(generateWsDataString(ws)) // - .append("] Unable to send message: Connection is closed. ") // - .append(toShortString(simplifyJsonrpcMessage(message), 100)) // - .toString()); + final var b = new StringBuilder(); + + var wsDataString = generateWsDataString(ws); + if (!wsDataString.isEmpty()) { + b.append("[").append(generateWsDataString(ws)).append("] "); + } + + this.logWarn(this.log, // + b.append("Unable to send message: Connection is closed. ") // + .append(toShortString(simplifyJsonrpcMessage(message), 200)) // + .toString()); } /** diff --git a/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java b/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java index b815deeb71c..f4b2b6cf916 100644 --- a/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java +++ b/io.openems.common/src/io/openems/common/websocket/DummyWebsocketServer.java @@ -93,12 +93,7 @@ private DummyWebsocketServer(DummyWebsocketServer.Builder builder) { @Override protected WsData createWsData(WebSocket ws) { - return new WsData(ws) { - @Override - public String toString() { - return "DummyWebsocketServer.WsData []"; - } - }; + return new WsData(ws); } @Override diff --git a/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java b/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java index e9f51ae5b04..cb80dc12b69 100644 --- a/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java +++ b/io.openems.common/src/io/openems/common/websocket/WebsocketUtils.java @@ -53,11 +53,11 @@ public static String parseRemoteIdentifier(WebSocket ws, Handshakedata handshake } /** - * Gets the toString() content of the WsData attachment of the WebSocket; or + * Gets the toLogString() content of the WsData attachment of the WebSocket; or * empty string if not available. * * @param ws the WebSocket - * @return the {@link WsData#toString()} content + * @return the {@link WsData#toLogString()} content */ public static String generateWsDataString(WebSocket ws) { if (ws == null) { @@ -67,6 +67,10 @@ public static String generateWsDataString(WebSocket ws) { if (wsData == null) { return ""; } - return wsData.toString(); + var logString = wsData.toLogString(); + if (logString == null) { + return ""; + } + return logString; } } diff --git a/io.openems.common/src/io/openems/common/websocket/WsData.java b/io.openems.common/src/io/openems/common/websocket/WsData.java index b43aedad7f7..852b4290125 100644 --- a/io.openems.common/src/io/openems/common/websocket/WsData.java +++ b/io.openems.common/src/io/openems/common/websocket/WsData.java @@ -21,14 +21,14 @@ * Objects of this class are used to store additional data with websocket * connections of WebSocketClient and WebSocketServer. */ -public abstract class WsData { +public class WsData { /** * Holds the WebSocket. */ private final WebSocket websocket; - protected WsData(WebSocket ws) { + public WsData(WebSocket ws) { this.websocket = ws; } @@ -138,10 +138,11 @@ public void handleJsonrpcResponse(JsonrpcResponse response) throws OpenemsNamedE } /** - * Provides a specific toString method. + * Provides a specific log string. * * @return a specific string for this instance */ - @Override - public abstract String toString(); + protected String toLogString() { + return ""; + } } diff --git a/io.openems.common/src/io/openems/common/worker/AbstractWorker.java b/io.openems.common/src/io/openems/common/worker/AbstractWorker.java index 29b371fd99b..c78b265fb2b 100644 --- a/io.openems.common/src/io/openems/common/worker/AbstractWorker.java +++ b/io.openems.common/src/io/openems/common/worker/AbstractWorker.java @@ -65,9 +65,7 @@ public void activate(String name) { * false */ public void modified(String name, boolean initiallyTriggerNextRun) { - if (!this.thread.isAlive() && !this.thread.isInterrupted() && !this.isStopped.get()) { - this.startWorker(name, initiallyTriggerNextRun); - } + this.startWorker(name, initiallyTriggerNextRun); } /** @@ -79,12 +77,13 @@ public void modified(String name) { this.modified(name, true); } - private void startWorker(String name, boolean autoTriggerNextRun) { + private synchronized void startWorker(String name, boolean autoTriggerNextRun) { if (name != null) { this.thread.setName(name); } - this.thread.start(); - + if (!this.thread.isAlive() && !this.thread.isInterrupted() && !this.isStopped.get()) { + this.thread.start(); + } if (autoTriggerNextRun) { this.triggerNextRun(); } diff --git a/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java b/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java new file mode 100644 index 00000000000..15ef3d8b49d --- /dev/null +++ b/io.openems.common/test/io/openems/common/timedata/TimeoutTest.java @@ -0,0 +1,27 @@ +package io.openems.common.timedata; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.temporal.ChronoUnit; + +import org.junit.Test; + +import io.openems.common.test.TimeLeapClock; + +public class TimeoutTest { + + @Test + public void test() { + final var timeout = Timeout.ofSeconds(120); + final var timeLeap = new TimeLeapClock(); + timeout.start(timeLeap); + + timeLeap.leap(20, ChronoUnit.SECONDS); + assertFalse(timeout.elapsed(timeLeap)); + + timeLeap.leap(121, ChronoUnit.SECONDS); + assertTrue(timeout.elapsed(timeLeap)); + } + +} diff --git a/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java b/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java index 3eb4161bd07..62373bfe85a 100644 --- a/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java +++ b/io.openems.common/test/io/openems/common/websocket/ClientReconnectorWorkerTest.java @@ -9,28 +9,15 @@ public class ClientReconnectorWorkerTest { - private static class MyWsData extends WsData { - - public MyWsData(WebSocket ws) { - super(ws); - } - - @Override - public String toString() { - return ""; - } - - } - - private static class MyWebsocketClient extends AbstractWebsocketClient { + private static class MyWebsocketClient extends AbstractWebsocketClient { public MyWebsocketClient(String name, URI serverUri) { super(name, serverUri); } @Override - protected MyWsData createWsData(WebSocket ws) { - return new MyWsData(ws); + protected WsData createWsData(WebSocket ws) { + return new WsData(ws); } @Override diff --git a/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java b/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java new file mode 100644 index 00000000000..172d3516be5 --- /dev/null +++ b/io.openems.common/test/io/openems/common/websocket/DummyWebsocketServerTest.java @@ -0,0 +1,15 @@ +package io.openems.common.websocket; + +import org.junit.Test; + +public class DummyWebsocketServerTest { + + @Test + public void test() { + var sut = DummyWebsocketServer.create() // + .build(); + sut.createWsData(null); + sut.stop(); + } + +} diff --git a/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java b/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java new file mode 100644 index 00000000000..25febb897d4 --- /dev/null +++ b/io.openems.common/test/io/openems/common/websocket/WebsocketUtilsTest.java @@ -0,0 +1,15 @@ +package io.openems.common.websocket; + +import static io.openems.common.websocket.WebsocketUtils.generateWsDataString; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class WebsocketUtilsTest { + + @Test + public void test() { + assertEquals("", generateWsDataString(null)); + } + +} diff --git a/io.openems.common/test/io/openems/common/websocket/WsDataTest.java b/io.openems.common/test/io/openems/common/websocket/WsDataTest.java new file mode 100644 index 00000000000..5dd1a7aad2f --- /dev/null +++ b/io.openems.common/test/io/openems/common/websocket/WsDataTest.java @@ -0,0 +1,17 @@ +package io.openems.common.websocket; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class WsDataTest { + + @Test + public void test() { + var sut = new WsData(null); + assertEquals("", sut.toLogString()); + + sut.dispose(); + } + +} diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 11442b68bce..fe957a9b082 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -74,6 +74,7 @@ bnd.identity;id='io.openems.edge.controller.ess.delaycharge',\ bnd.identity;id='io.openems.edge.controller.ess.delayedselltogrid',\ bnd.identity;id='io.openems.edge.controller.ess.emergencycapacityreserve',\ + bnd.identity;id='io.openems.edge.controller.ess.fastfrequencyreserve',\ bnd.identity;id='io.openems.edge.controller.ess.fixactivepower',\ bnd.identity;id='io.openems.edge.controller.ess.fixstateofcharge',\ bnd.identity;id='io.openems.edge.controller.ess.gridoptimizedcharge',\ @@ -193,9 +194,9 @@ -runbundles: \ Java-WebSocket;version='[1.5.4,1.5.5)',\ - bcpkix;version='[1.70.0,1.70.1)',\ - bcprov;version='[1.70.0,1.70.1)',\ - bcutil;version='[1.70.0,1.70.1)',\ + bcpkix;version='[1.77.0,1.77.1)',\ + bcprov;version='[1.77.0,1.77.1)',\ + bcutil;version='[1.77.0,1.77.1)',\ com.fasterxml.aalto-xml;version='[1.3.3,1.3.4)',\ com.fazecast.jSerialComm;version='[2.10.4,2.10.5)',\ com.ghgande.j2mod;version='[3.2.1,3.2.2)',\ @@ -245,6 +246,7 @@ io.openems.edge.controller.ess.delaycharge;version=snapshot,\ io.openems.edge.controller.ess.delayedselltogrid;version=snapshot,\ io.openems.edge.controller.ess.emergencycapacityreserve;version=snapshot,\ + io.openems.edge.controller.ess.fastfrequencyreserve;version=snapshot,\ io.openems.edge.controller.ess.fixactivepower;version=snapshot,\ io.openems.edge.controller.ess.fixstateofcharge;version=snapshot,\ io.openems.edge.controller.ess.gridoptimizedcharge;version=snapshot,\ diff --git a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java index 2dedfc9f26a..72d31daa444 100644 --- a/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java +++ b/io.openems.edge.battery.bydcommercial/src/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130.java @@ -787,27 +787,27 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { LEVEL1_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Cluster 2 Charge Current High Alarm Level 2")), // - LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Voltage High Alarm Level 3")), // - LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Voltage Low Alarm Level 3")), // - LEVEL2_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_DIFF_TOO_BIG(Doc.of(Level.WARNING) // .text("Alarm Level 3 Battery Cells Unbalanced")), // - LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Discharge Temperature High Alarm Level 3")), // - LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Discharge Temperature Low Alarm Level 3")), // - LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Charge Temperature High Alarm Level 3")), // - LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Charge Temperature Low Alarm Level 3")), // - LEVEL2_TEMP_DIFF_TOO_BIG(Doc.of(Level.FAULT) // + LEVEL2_TEMP_DIFF_TOO_BIG(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Temperature Diff High Alarm Level 3")), // - LEVEL2_POWER_POLE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_POWER_POLE_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Cell Temperature High Alarm Level 3")), // - LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Discharge Current High Alarm Level 3")), // - LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Cluster 3 Charge Current High Alarm Level 3")), // ALARM_LEVEL_1_TOTAL_VOLTAGE_DIFF_HIGH(Doc.of(Level.WARNING) // @@ -822,69 +822,69 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("Cluster 1 Total Voltage Low Alarm Level 1")), // ALARM_LEVEL_1_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Cluster 1 Total Voltage High Alarm Level 1")), // - ALARM_FUSE(Doc.of(Level.FAULT) // + ALARM_FUSE(Doc.of(Level.WARNING) // .text(" Fuse Alarm")), // SHIELDED_SWITCH_STATE(Doc.of(Level.WARNING) // .text("Shielded switch state")), // ALARM_BAU_COMMUNICATION(Doc.of(Level.WARNING) // .text("BAU Communication Alarm")), // - ALARM_INSULATION_CHECK(Doc.of(Level.FAULT) // + ALARM_INSULATION_CHECK(Doc.of(Level.WARNING) // .text("Inuslation Resistance Alarm")), // ALARM_CURRENT_SENSOR(Doc.of(Level.WARNING) // .text("Current Sensor Alarm")), // ALARM_BCU_BMU_COMMUNICATION(Doc.of(Level.WARNING) // .text("BCU BMU Communication Alarm")), // - ALARM_CONTACTOR_ADHESION(Doc.of(Level.FAULT)// + ALARM_CONTACTOR_ADHESION(Doc.of(Level.WARNING)// .text("Contactor Adhesion Alarm ")), // ALARM_BCU_NTC(Doc.of(Level.WARNING) // .text("BCU NTC Alarm")), // ALARM_SLAVE_CONTROL_SUMMARY(Doc.of(Level.WARNING) // .text("Slave Control Summary Alarm")), // - FAILURE_INITIALIZATION(Doc.of(Level.FAULT) // + FAILURE_INITIALIZATION(Doc.of(Level.WARNING) // .text("Initialization failure")), // - FAILURE_EEPROM(Doc.of(Level.FAULT) // + FAILURE_EEPROM(Doc.of(Level.WARNING) // .text("EEPROM fault")), // - FAILURE_EEPROM2(Doc.of(Level.FAULT) // + FAILURE_EEPROM2(Doc.of(Level.WARNING) // .text("EEPROM2 fault")), // - FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.FAULT) // + FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.WARNING) // .text("Intranet communication fault")), // - FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.FAULT) // + FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.WARNING) // .text("Temperature sampling line fault")), // - FAILURE_BALANCING_MODULE(Doc.of(Level.FAULT) // + FAILURE_BALANCING_MODULE(Doc.of(Level.WARNING) // .text("Balancing module fault")), // - FAILURE_TEMP_SENSOR(Doc.of(Level.FAULT) // + FAILURE_TEMP_SENSOR(Doc.of(Level.WARNING) // .text("Temperature sensor fault")), // - FAILURE_TEMP_SAMPLING(Doc.of(Level.FAULT) // + FAILURE_TEMP_SAMPLING(Doc.of(Level.WARNING) // .text("Temperature sampling fault")), // - FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.FAULT) // + FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.WARNING) // .text("Voltage sampling fault")), // - FAILURE_VOLTAGE_SAMPLING_LINE(Doc.of(Level.FAULT) // + FAILURE_VOLTAGE_SAMPLING_LINE(Doc.of(Level.WARNING) // .text("Voltage sampling Line fault")), // - FAILURE_SLAVE_UNIT_INITIALIZATION(Doc.of(Level.FAULT) // + FAILURE_SLAVE_UNIT_INITIALIZATION(Doc.of(Level.WARNING) // .text("Failure Slave Unit Initialization")), - FAILURE_CONNECTING_LINE(Doc.of(Level.FAULT) // + FAILURE_CONNECTING_LINE(Doc.of(Level.WARNING) // .text("Connecting Line Failure")), // - FAILURE_SAMPLING_CHIP(Doc.of(Level.FAULT) // + FAILURE_SAMPLING_CHIP(Doc.of(Level.WARNING) // .text("Sampling Chip Failure")), // - FAILURE_CONTACTOR(Doc.of(Level.FAULT) // + FAILURE_CONTACTOR(Doc.of(Level.WARNING) // .text("Contactor Failure")), // - FAILURE_PASSIVE_BALANCE(Doc.of(Level.FAULT) // + FAILURE_PASSIVE_BALANCE(Doc.of(Level.WARNING) // .text("Passive Balance Failure")), // - FAILURE_PASSIVE_BALANCE_TEMP(Doc.of(Level.FAULT) // + FAILURE_PASSIVE_BALANCE_TEMP(Doc.of(Level.WARNING) // .text("Passive Balance Temp Failure")), // - FAILURE_ACTIVE_BALANCE(Doc.of(Level.FAULT) // + FAILURE_ACTIVE_BALANCE(Doc.of(Level.WARNING) // .text("Active Balance Failure")), // - FAILURE_LTC6803(Doc.of(Level.FAULT) // + FAILURE_LTC6803(Doc.of(Level.WARNING) // .text("LTC6803 sfault")), // - FAILURE_CONNECTOR_WIRE(Doc.of(Level.FAULT) // + FAILURE_CONNECTOR_WIRE(Doc.of(Level.WARNING) // .text("connector wire fault")), // - FAILURE_SAMPLING_WIRE(Doc.of(Level.FAULT) // + FAILURE_SAMPLING_WIRE(Doc.of(Level.WARNING) // .text("sampling wire fault")), // - PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.FAULT) // + PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.WARNING) // .text("precharge time was too long")), // NEED_CHARGE(Doc.of(Level.WARNING) // .text("Battery Need Charge")), // - FAULT(Doc.of(Level.FAULT) // + FAULT(Doc.of(Level.WARNING) // .text("battery fault state")), // STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // @@ -928,76 +928,76 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("ALARM LEVEL 2 SOH LOWER")), // LEVEL1_PACK_TEMP_HIGH(Doc.of(Level.WARNING) // .text("ALARM LEVEL 2 PACK TEMP HIGH")), // - LEVEL2_SYSTEM_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM VOLTAGE HIGH")), // - LEVEL2_SYSTEM_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM VOLTAGE LOW")), // - LEVEL2_SYSTEM_VOLTAGE_UNBALANCED(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_VOLTAGE_UNBALANCED(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM VOLTAGE UNBALANCED")), // - LEVEL2_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) // + LEVEL2_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 INSULATION RESISTANCE LOWER")), // - LEVEL2_POS_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) // + LEVEL2_POS_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 POS INSULATION RESISTANCE LOWER")), // - LEVEL2_NEG_INSULATION_RESISTANCE_LOWER(Doc.of(Level.FAULT) // + LEVEL2_NEG_INSULATION_RESISTANCE_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 NEG INSULATION RESISTANCE LOWER")), // - LEVEL2_SYSTEM_SOC_LOWER(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_SOC_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM SOC LOWER")), // - LEVEL2_SYSTEM_SOC_HIGH(Doc.of(Level.FAULT) // + LEVEL2_SYSTEM_SOC_HIGH(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SYSTEM SOC HIGH")), // LEVEL2_SOH_LOWER(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 SOH LOWER")), // - LEVEL2_PACK_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_PACK_TEMP_HIGH(Doc.of(Level.WARNING) // .text("ALARM LEVEL 3 PACK TEMP HIGH")), // - SLAVE_11_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_11_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_11")), // - SLAVE_12_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_12_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_12")), // - SLAVE_13_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_13_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_13")), // - SLAVE_14_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_14_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_14")), // - SLAVE_15_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_15_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_15")), // - SLAVE_16_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_16_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_16")), // - SLAVE_17_COMMUNICATION_ERROR(Doc.of(Level.FAULT)// + SLAVE_17_COMMUNICATION_ERROR(Doc.of(Level.WARNING)// .text("Master control and Slave control Communication Fault 1 SLAVE_CTRL_17")), // - SLAVE_18_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_18_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_18")), // - SLAVE_19_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_19_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_19")), // - SLAVE_20_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_20_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_20")), // - SLAVE_21_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_21_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_21")), // - SLAVE_22_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_22_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_22")), // - SLAVE_23_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_23_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_23")), // - SLAVE_24_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_24_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_24")), // - SLAVE_25_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_25_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_25")), // - SLAVE_26_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_26_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_26")), // - SLAVE_27_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_27_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_27")), // - SLAVE_28_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_28_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_28")), // - SLAVE_29_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_29_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_29")), // - SLAVE_30_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_30_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_30")), // - SLAVE_31_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_31_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_31")), // - SLAVE_32_COMMUNICATION_ERROR(Doc.of(Level.FAULT) // + SLAVE_32_COMMUNICATION_ERROR(Doc.of(Level.WARNING) // .text("Master control and Slave control Communication Fault 2 SLAVE_CTRL_31")), // // OpenEMS Faults - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // ; diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java index 0ea7cc798ae..d4298dde150 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHome.java @@ -673,7 +673,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // LOW_MIN_VOLTAGE_WARNING(Doc.of(Level.WARNING) // .text("Low min voltage warning " diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java index efa68f7d43d..e9d933bc5e0 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImpl.java @@ -117,17 +117,12 @@ public BatteryFeneconHomeImpl() { BatteryProtection.ChannelId.values(), // BatteryFeneconHome.ChannelId.values() // ); - this.updateHardwareType(BatteryFeneconHomeHardwareType.DEFAULT); } @Activate private void activate(ComponentContext context, Config config) throws OpenemsException { this.config = config; - - // Predefine BatteryProtection. Later adapted to the hardware type. - this.batteryProtection = BatteryProtection.create(this) // - .applyBatteryProtectionDefinition(new FeneconHomeBatteryProtection52(), this.componentManager) // - .build(); + this.updateHardwareType(BatteryFeneconHomeHardwareType.DEFAULT); // initialize to default if (super.activate(context, config.id(), config.alias(), config.enabled(), config.modbusUnitId(), this.cm, "Modbus", config.modbus_id())) { @@ -194,7 +189,7 @@ private void handleStateMachine() { @Override protected ModbusProtocol defineModbusProtocol() { return new ModbusProtocol(this, // - new FC3ReadRegistersTask(500, Priority.LOW, // + new FC3ReadRegistersTask(500, Priority.HIGH, // m(new BitsWordElement(500, this) // .bit(0, BatteryFeneconHome.ChannelId.RACK_PRE_ALARM_CELL_OVER_VOLTAGE) // .bit(1, BatteryFeneconHome.ChannelId.RACK_PRE_ALARM_CELL_UNDER_VOLTAGE) // @@ -271,10 +266,7 @@ protected ModbusProtocol defineModbusProtocol() { .bit(6, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_7) // .bit(7, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_8) // .bit(8, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_9) // - .bit(9, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_10))// - ), // - - new FC3ReadRegistersTask(506, Priority.LOW, // + .bit(9, BatteryFeneconHome.ChannelId.FAULT_POSITION_BCU_10)), // m(Battery.ChannelId.VOLTAGE, new UnsignedWordElement(506), SCALE_FACTOR_MINUS_1), // [V] m(Battery.ChannelId.CURRENT, new SignedWordElement(507), SCALE_FACTOR_MINUS_1), // [A] m(Battery.ChannelId.SOC, new UnsignedWordElement(508), SCALE_FACTOR_MINUS_1), // [%] @@ -1066,7 +1058,7 @@ public BridgeModbus getModbus() { } @Override - public ModbusProtocol getDefinedModbusProtocol() throws OpenemsException { + public ModbusProtocol getDefinedModbusProtocol() { return this.getModbusProtocol(); } diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java index b072d1ebdc4..4f95aa64a66 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/ModbusHelper.java @@ -1,24 +1,22 @@ package io.openems.edge.battery.fenecon.home; -import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.ModbusProtocol; public interface ModbusHelper { /** - * Get modbus bridge. + * Get the {@link BridgeModbus}. * - * @return modbus bridge. + * @return the {@link BridgeModbus} */ public BridgeModbus getModbus(); /** - * Get defined modbus protocol. + * Get defined {@link ModbusProtocol}. * - * @return modbus protocol - * @throws OpenemsException on error + * @return the {@link ModbusProtocol} */ - public ModbusProtocol getDefinedModbusProtocol() throws OpenemsException; + public ModbusProtocol getDefinedModbusProtocol(); } diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java index a2828143f76..6cb49490bb6 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/Context.java @@ -32,8 +32,4 @@ public Context(BatteryFeneconHome parent, Clock clock, Boolean batteryStartUpRel this.modbusCommunicationFailed = modbusCommunicationFailed; this.retryModbusCommunication = retryModbusCommunication; } - - protected void retryModbusCommunication() { - this.getParent().retryModbusCommunication(); - } } \ No newline at end of file diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java index a41a0bf4837..d539b27df30 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/statemachine/GoStoppedHandler.java @@ -12,26 +12,25 @@ public class GoStoppedHandler extends StateHandler { private static int TIMEOUT = 2100; // [35 minutes in seconds] private Instant timeAtEntry = Instant.MIN; - private boolean didProtocolAdd = false; + private boolean isProtocolAdded = false; @Override - protected void onEntry(Context context) throws OpenemsNamedException { - final var battery = context.getParent(); - final var modbus = battery.getModbus(); - modbus.removeProtocol(battery.id()); - this.didProtocolAdd = false; + protected void onEntry(Context context) { + // Remove the protocol to trigger BMS timeout + this.removeProtocol(context); + this.timeAtEntry = Instant.now(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsException { - final var battery = context.getParent(); var now = Instant.now(context.clock); - if (Duration.between(this.timeAtEntry, now).getSeconds() > TIMEOUT && !this.didProtocolAdd) { - this.addAndRetryModbusProtocol(context); + if (Duration.between(this.timeAtEntry, now).getSeconds() > TIMEOUT && !this.isProtocolAdded) { + this.addProtocol(context); return State.GO_STOPPED; } + final var battery = context.getParent(); if (battery.getModbusCommunicationFailed()) { return State.STOPPED; } @@ -40,12 +39,25 @@ public State runAndGetNextState(Context context) throws OpenemsException { return State.GO_STOPPED; } - private void addAndRetryModbusProtocol(Context context) throws OpenemsException { + @Override + protected void onExit(Context context) throws OpenemsNamedException { + // Make sure to leave this GoStoppedHandler with added protocol + if (!this.isProtocolAdded) { + this.addProtocol(context); + } + } + + private void addProtocol(Context context) { final var battery = context.getParent(); final var modbus = battery.getModbus(); + this.isProtocolAdded = true; modbus.addProtocol(battery.id(), battery.getDefinedModbusProtocol()); - modbus.retryModbusCommunication(battery.id()); - this.didProtocolAdd = true; } + private void removeProtocol(Context context) { + final var battery = context.getParent(); + final var modbus = battery.getModbus(); + this.isProtocolAdded = false; + modbus.removeProtocol(battery.id()); + } } diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java index 1276244de00..867c5b99a9f 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionB.java @@ -52,80 +52,80 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .accessMode(AccessMode.READ_WRITE)), // // StateChannels - MASTER_ALARM_COMMUNICATION_ERROR_WITH_SUBMASTER(Doc.of(Level.FAULT) // + MASTER_ALARM_COMMUNICATION_ERROR_WITH_SUBMASTER(Doc.of(Level.WARNING) // .text("Communication error with submaster")), - MASTER_ALARM_PCS_EMS_COMMUNICATION_FAILURE(Doc.of(Level.FAULT) // + MASTER_ALARM_PCS_EMS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) // .text("PCS/EMS communication failure alarm")), - MASTER_ALARM_PCS_EMS_CONTROL_FAIL(Doc.of(Level.FAULT) // + MASTER_ALARM_PCS_EMS_CONTROL_FAIL(Doc.of(Level.WARNING) // .text("PCS/EMS control fail alarm")), MASTER_ALARM_LEVEL_1_INSULATION(Doc.of(Level.WARNING) // .text("System insulation alarm level 1")), - MASTER_ALARM_LEVEL_2_INSULATION(Doc.of(Level.FAULT) // + MASTER_ALARM_LEVEL_2_INSULATION(Doc.of(Level.WARNING) // .text("System insulation alarm level 2")), - RACK_1_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_1_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 1 Level 2 Alarm")), - RACK_1_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_1_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 1 PCS control fault")), - RACK_1_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_1_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 1 Communication with master error")), - RACK_1_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_1_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 1 Device error")), - RACK_1_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_1_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")), - RACK_2_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_2_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 2 Level 2 Alarm")), - RACK_2_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_2_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 2 PCS control fault")), - RACK_2_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_2_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 2 Communication with master error")), - RACK_2_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_2_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 2 Device error")), - RACK_2_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_2_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")), - RACK_3_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_3_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 3 Level 2 Alarm")), - RACK_3_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_3_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 3 PCS control fault")), - RACK_3_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_3_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 3 Communication with master error")), - RACK_3_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_3_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 3 Device error")), - RACK_3_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_3_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")), - RACK_4_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_4_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 4 Level 2 Alarm")), - RACK_4_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_4_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 4 PCS control fault")), - RACK_4_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_4_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 4 Communication with master error")), - RACK_4_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_4_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 4 Device error")), - RACK_4_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_4_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")), - RACK_5_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_5_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 5 Level 2 Alarm")), - RACK_5_PCS_CONTROL_FAULT(Doc.of(Level.FAULT) // + RACK_5_PCS_CONTROL_FAULT(Doc.of(Level.WARNING) // .text("Rack 5 PCS control fault")), - RACK_5_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.FAULT) // + RACK_5_COMMUNICATION_WITH_MASTER_ERROR(Doc.of(Level.WARNING) // .text("Rack 5 Communication with master error")), - RACK_5_DEVICE_ERROR(Doc.of(Level.FAULT) // + RACK_5_DEVICE_ERROR(Doc.of(Level.WARNING) // .text("Rack 5 Device error")), - RACK_5_CYCLE_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_5_CYCLE_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Cycle over current")), - RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Voltage difference")),; private final Doc doc; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java index 45d65c88cfb..02835194b33 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionb/SingleRack.java @@ -439,28 +439,28 @@ private Map createChannelIdMap() { this.addEntry(map, KEY_MAX_CELL_TEMPERATURE, new IntegerDoc().unit(Unit.DEZIDEGREE_CELSIUS)); this.addEntry(map, KEY_MIN_CELL_TEMPERATURE_ID, new IntegerDoc().unit(Unit.NONE)); this.addEntry(map, KEY_MIN_CELL_TEMPERATURE, new IntegerDoc().unit(Unit.DEZIDEGREE_CELSIUS)); - this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_LOW, Doc.of(Level.FAULT) + this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_LOW, Doc.of(Level.WARNING) .text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 2")); /* Bit 15 */ - this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.FAULT) + this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.WARNING) .text("Rack" + this.rackNumber + " Cell Discharge Temperature High Alarm Level 2")); /* Bit 14 */ this.addEntry(map, KEY_ALARM_LEVEL_2_GR_TEMPERATURE_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 2")); /* Bit 10 */ - this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_LOW, Doc.of(Level.FAULT) + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " GR Temperature High Alarm Level 2")); /* Bit 10 */ + this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_LOW, Doc.of(Level.WARNING) .text("Rack" + this.rackNumber + " Cell Charge Temperature Low Alarm Level 2")); /* Bit 7 */ - this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_HIGH, Doc.of(Level.FAULT) + this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_CHA_TEMP_HIGH, Doc.of(Level.WARNING) .text("Rack" + this.rackNumber + " Cell Charge Temperature High Alarm Level 2")); /* Bit 6 */ - this.addEntry(map, KEY_ALARM_LEVEL_2_DISCHA_CURRENT_HIGH, Doc.of(Level.FAULT) + this.addEntry(map, KEY_ALARM_LEVEL_2_DISCHA_CURRENT_HIGH, Doc.of(Level.WARNING) .text("Rack" + this.rackNumber + " Discharge Current High Alarm Level 2")); /* Bit 5 */ this.addEntry(map, KEY_ALARM_LEVEL_2_TOTAL_VOLTAGE_LOW, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 2")); /* Bit 4 */ + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage Low Alarm Level 2")); /* Bit 4 */ this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_VOLTAGE_LOW, - Doc.of(Level.FAULT).text("Cluster 1 Cell Voltage Low Alarm Level 2")); /* Bit 3 */ + Doc.of(Level.WARNING).text("Cluster 1 Cell Voltage Low Alarm Level 2")); /* Bit 3 */ this.addEntry(map, KEY_ALARM_LEVEL_2_CHA_CURRENT_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Charge Current High Alarm Level 2")); /* Bit 2 */ + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Charge Current High Alarm Level 2")); /* Bit 2 */ this.addEntry(map, KEY_ALARM_LEVEL_2_TOTAL_VOLTAGE_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Total Voltage High Alarm Level 2")); /* Bit 1 */ + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Total Voltage High Alarm Level 2")); /* Bit 1 */ this.addEntry(map, KEY_ALARM_LEVEL_2_CELL_VOLTAGE_HIGH, - Doc.of(Level.FAULT).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 2")); /* Bit 0 */ + Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 2")); /* Bit 0 */ this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_LOW, Doc.of(Level.WARNING) .text("Rack" + this.rackNumber + " Cell Discharge Temperature Low Alarm Level 1")); /* Bit 15 */ this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_DISCHA_TEMP_HIGH, Doc.of(Level.WARNING) @@ -492,23 +492,23 @@ private Map createChannelIdMap() { this.addEntry(map, KEY_ALARM_LEVEL_1_CELL_VOLTAGE_HIGH, Doc.of(Level.WARNING).text("Rack" + this.rackNumber + " Cell Voltage High Alarm Level 1")); /* Bit 0 */ this.addEntry(map, KEY_RUN_STATE, Doc.of(Enums.ClusterRunState.values())); // - this.addEntry(map, KEY_FAILURE_INITIALIZATION, Doc.of(Level.FAULT).text("Initialization failure")); /* Bit */ - this.addEntry(map, KEY_FAILURE_EEPROM, Doc.of(Level.FAULT).text("EEPROM fault")); /* Bit 11 */ + this.addEntry(map, KEY_FAILURE_INITIALIZATION, Doc.of(Level.WARNING).text("Initialization failure")); /* Bit */ + this.addEntry(map, KEY_FAILURE_EEPROM, Doc.of(Level.WARNING).text("EEPROM fault")); /* Bit 11 */ this.addEntry(map, KEY_FAILURE_INTRANET_COMMUNICATION, - Doc.of(Level.FAULT).text("Internal communication fault")); /* Bit 10 */ + Doc.of(Level.WARNING).text("Internal communication fault")); /* Bit 10 */ this.addEntry(map, KEY_FAILURE_TEMPERATURE_SENSOR_CABLE, - Doc.of(Level.FAULT).text("Temperature sensor cable fault")); /* Bit 9 */ + Doc.of(Level.WARNING).text("Temperature sensor cable fault")); /* Bit 9 */ this.addEntry(map, KEY_FAILURE_BALANCING_MODULE, Doc.of(Level.OK).text("Balancing module fault")); /* Bit 8 */ - this.addEntry(map, KEY_FAILURE_TEMPERATURE_PCB, Doc.of(Level.FAULT).text("Temperature PCB error")); /* Bit 7 */ - this.addEntry(map, KEY_FAILURE_GR_TEMPERATURE, Doc.of(Level.FAULT).text("GR Temperature error")); /* Bit 6 */ - this.addEntry(map, KEY_FAILURE_TEMP_SENSOR, Doc.of(Level.FAULT).text("Temperature sensor fault")); /* Bit 5 */ + this.addEntry(map, KEY_FAILURE_TEMPERATURE_PCB, Doc.of(Level.WARNING).text("Temperature PCB error")); /* Bit 7 */ + this.addEntry(map, KEY_FAILURE_GR_TEMPERATURE, Doc.of(Level.WARNING).text("GR Temperature error")); /* Bit 6 */ + this.addEntry(map, KEY_FAILURE_TEMP_SENSOR, Doc.of(Level.WARNING).text("Temperature sensor fault")); /* Bit 5 */ this.addEntry(map, KEY_FAILURE_TEMP_SAMPLING, - Doc.of(Level.FAULT).text("Temperature sampling fault")); /* Bit 4 */ + Doc.of(Level.WARNING).text("Temperature sampling fault")); /* Bit 4 */ this.addEntry(map, KEY_FAILURE_VOLTAGE_SAMPLING, - Doc.of(Level.FAULT).text("Voltage sampling fault")); /* Bit 3 */ - this.addEntry(map, KEY_FAILURE_LTC6803, Doc.of(Level.FAULT).text("LTC6803 fault")); /* Bit 2 */ - this.addEntry(map, KEY_FAILURE_CONNECTOR_WIRE, Doc.of(Level.FAULT).text("connector wire fault")); /* Bit 1 */ - this.addEntry(map, KEY_FAILURE_SAMPLING_WIRE, Doc.of(Level.FAULT).text("sampling wire fault")); /* Bit 0 */ + Doc.of(Level.WARNING).text("Voltage sampling fault")); /* Bit 3 */ + this.addEntry(map, KEY_FAILURE_LTC6803, Doc.of(Level.WARNING).text("LTC6803 fault")); /* Bit 2 */ + this.addEntry(map, KEY_FAILURE_CONNECTOR_WIRE, Doc.of(Level.WARNING).text("connector wire fault")); /* Bit 1 */ + this.addEntry(map, KEY_FAILURE_SAMPLING_WIRE, Doc.of(Level.WARNING).text("sampling wire fault")); /* Bit 0 */ this.addEntry(map, KEY_SLEEP, Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_WRITE)); this.addEntry(map, KEY_RESET, Doc.of(OpenemsType.INTEGER).accessMode(AccessMode.READ_WRITE)); diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java index 99f8fea4c9f..cec303bdbf7 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionC.java @@ -265,82 +265,82 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId // Master BMS Alarm Registers MASTER_EMS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) // .text("Master EMS Communication Failure")), - MASTER_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + MASTER_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Master PCS Control Failure")), - MASTER_PCS_COMMUNICATION_FAILURE(Doc.of(Level.FAULT) // + MASTER_PCS_COMMUNICATION_FAILURE(Doc.of(Level.WARNING) // .text("Master PCS Communication Failure")), // Rack #1 cannot be paralleled to DC Bus reasons - RACK_1_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_1_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 1 Level 2 Alarm")), - RACK_1_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_1_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 1 PCS Control Failure")), - RACK_1_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_1_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 1 Communication to Master BMS Failure")), - RACK_1_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_1_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 1 Hardware Failure")), - RACK_1_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_1_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 1 Too big circulating Current among clusters (>4A)")), - RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_1_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 1 Too big boltage difference among clusters (>50V)")), // Rack #2 cannot be paralleled to DC Bus reasons - RACK_2_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_2_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 2 Level 2 Alarm")), - RACK_2_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_2_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 2 PCS Control Failure")), - RACK_2_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_2_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 2 Communication to Master BMS Failure")), - RACK_2_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_2_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 2 Hardware Failure")), - RACK_2_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_2_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 2 Too big circulating Current among clusters (>4A)")), - RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_2_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 2 Too big boltage difference among clusters (>50V)")), // Rack #3 cannot be paralleled to DC Bus reasons - RACK_3_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_3_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 3 Level 2 Alarm")), - RACK_3_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_3_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 3 PCS Control Failure")), - RACK_3_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_3_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 3 Communication to Master BMS Failure")), - RACK_3_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_3_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 3 Hardware Failure")), - RACK_3_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_3_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 3 Too big circulating Current among clusters (>4A)")), - RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_3_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 3 Too big boltage difference among clusters (>50V)")), // Rack #4 cannot be paralleled to DC Bus reasons - RACK_4_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_4_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 4 Level 2 Alarm")), - RACK_4_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_4_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 4 PCS Control Failure")), - RACK_4_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_4_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 4 Communication to Master BMS Failure")), - RACK_4_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_4_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 4 Hardware Failure")), - RACK_4_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_4_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 4 Too big circulating Current among clusters (>4A)")), - RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_4_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 4 Too big boltage difference among clusters (>50V)")), // Rack #5 cannot be paralleled to DC Bus reasons - RACK_5_LEVEL_2_ALARM(Doc.of(Level.FAULT) // + RACK_5_LEVEL_2_ALARM(Doc.of(Level.WARNING) // .text("Rack 5 Level 2 Alarm")), - RACK_5_PCS_CONTROL_FAILURE(Doc.of(Level.FAULT) // + RACK_5_PCS_CONTROL_FAILURE(Doc.of(Level.WARNING) // .text("Rack 5 PCS Control Failure")), - RACK_5_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.FAULT) // + RACK_5_COMMUNICATION_TO_MASTER_FAILURE(Doc.of(Level.WARNING) // .text("Rack 5 Communication to Master BMS Failure")), - RACK_5_HARDWARE_FAILURE(Doc.of(Level.FAULT) // + RACK_5_HARDWARE_FAILURE(Doc.of(Level.WARNING) // .text("Rack 5 Hardware Failure")), - RACK_5_OVER_CURRENT(Doc.of(Level.FAULT) // + RACK_5_OVER_CURRENT(Doc.of(Level.WARNING) // .text("Rack 5 Too big circulating Current among clusters (>4A)")), - RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.FAULT) // + RACK_5_VOLTAGE_DIFFERENCE(Doc.of(Level.WARNING) // .text("Rack 5 Too big boltage difference among clusters (>50V)")), // OpenEMS Faults - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // NUMBER_OF_MODULES_PER_TOWER(Doc.of(OpenemsType.INTEGER) // .persistencePriority(PersistencePriority.HIGH) // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java index ec13258ac7c..daee5253fa6 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/cluster/versionc/RackChannel.java @@ -463,27 +463,27 @@ public enum RackChannel { LEVEL1_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Total Voltage High Alarm Level 1")), // // Alarm Level 2 - LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Discharge Temperature Low Alarm Level 2")), // - LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Discharge Temperature High Alarm Level 2")), // - LEVEL2_INSULATION_VALUE(Doc.of(Level.FAULT) // + LEVEL2_INSULATION_VALUE(Doc.of(Level.WARNING) // .text("Insulation Value Failure Alarm Level 2")), // - LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Power Pole temperature too high Alarm Level 2")), // - LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Cell Charge Temperature Low Alarm Level 2")), // - LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Charge Temperature High Alarm Level 2")), // - LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Discharge Current High Alarm Level 2")), // - LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Total Voltage Low Alarm Level 2")), // - LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Cell Voltage Low Alarm Level 2")), // - LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Charge Current High Alarm Level 2")), // - LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Total Voltage High Alarm Level 2")), // LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.INFO) // .text("Cell Voltage High Alarm Level 2")), // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java index 9ee4f1c6abf..7d65c2aacd1 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionA.java @@ -681,29 +681,29 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .text("Cluster 1 Total Voltage High Alarm Level 1")), // ALARM_LEVEL_1_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Cluster 1 Cell Voltage High Alarm Level 1")), // - FAILURE_INITIALIZATION(Doc.of(Level.FAULT) // + FAILURE_INITIALIZATION(Doc.of(Level.WARNING) // .text("Initialization failure")), // - FAILURE_EEPROM(Doc.of(Level.FAULT) // + FAILURE_EEPROM(Doc.of(Level.WARNING) // .text("EEPROM fault")), // - FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.FAULT) // + FAILURE_INTRANET_COMMUNICATION(Doc.of(Level.WARNING) // .text("Intranet communication fault")), // - FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.FAULT) // + FAILURE_TEMP_SAMPLING_LINE(Doc.of(Level.WARNING) // .text("Temperature sampling line fault")), // - FAILURE_BALANCING_MODULE(Doc.of(Level.FAULT) // + FAILURE_BALANCING_MODULE(Doc.of(Level.WARNING) // .text("Balancing module fault")), // - FAILURE_TEMP_SENSOR(Doc.of(Level.FAULT) // + FAILURE_TEMP_SENSOR(Doc.of(Level.WARNING) // .text("Temperature sensor fault")), // - FAILURE_TEMP_SAMPLING(Doc.of(Level.FAULT) // + FAILURE_TEMP_SAMPLING(Doc.of(Level.WARNING) // .text("Temperature sampling fault")), // - FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.FAULT) // + FAILURE_VOLTAGE_SAMPLING(Doc.of(Level.WARNING) // .text("Voltage sampling fault")), // - FAILURE_LTC6803(Doc.of(Level.FAULT) // + FAILURE_LTC6803(Doc.of(Level.WARNING) // .text("LTC6803 fault")), // FAILURE_CONNECTOR_WIRE(Doc.of(Level.WARNING) // .text("connector wire fault")), // - FAILURE_SAMPLING_WIRE(Doc.of(Level.FAULT) // + FAILURE_SAMPLING_WIRE(Doc.of(Level.WARNING) // .text("sampling wire fault")), // - PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.FAULT) // + PRECHARGE_TAKING_TOO_LONG(Doc.of(Level.WARNING) // .text("precharge time was too long")), // STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java index ecc741e2cc2..8434481d85c 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionB.java @@ -995,11 +995,11 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("precharge time was too long")), // OpenEMS Faults - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // ; diff --git a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java index 28a54d5b9af..1f57a137c60 100644 --- a/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java +++ b/io.openems.edge.battery.soltaro/src/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionC.java @@ -514,29 +514,29 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId // Faults and warnings // Alarm Level 2 - LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Discharge Temperature Low Alarm Level 2")), // - LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Discharge Temperature High Alarm Level 2")), // - LEVEL2_INSULATION_VALUE(Doc.of(Level.FAULT) // + LEVEL2_INSULATION_VALUE(Doc.of(Level.WARNING) // .text("Insulation Value Failure Alarm Level 2")), // - LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_POWER_POLE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Power Pole temperature too high Alarm Level 2")), // - LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_LOW(Doc.of(Level.WARNING) // .text("Cell Charge Temperature Low Alarm Level 2")), // - LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_TEMP_HIGH(Doc.of(Level.WARNING) // .text("Charge Temperature High Alarm Level 2")), // - LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_DISCHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Discharge Current High Alarm Level 2")), // - LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_TOTAL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Total Voltage Low Alarm Level 2")), // - LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_LOW(Doc.of(Level.WARNING) // .text("Cell Voltage Low Alarm Level 2")), // - LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CHARGE_CURRENT_HIGH(Doc.of(Level.WARNING) // .text("Charge Current High Alarm Level 2")), // - LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_TOTAL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Total Voltage High Alarm Level 2")), // - LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.FAULT) // + LEVEL2_CELL_VOLTAGE_HIGH(Doc.of(Level.WARNING) // .text("Cell Voltage High Alarm Level 2")), // // Alarm Level 1 @@ -672,11 +672,11 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("Slave 20 communication error")), // // OpenEMS Faults - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // ; diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java index d9124765b01..b32ba021cd3 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsave.java @@ -33,29 +33,40 @@ public interface BatteryInverterKacoBlueplanetGridsave extends ManagedSymmetricB public static final int WATCHDOG_TRIGGER_SECONDS = 10; public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + + /* + * Whenever one of these states would be Level.FAULT, the EssGeneric will stop + * the battery and the inverter. If this is necessary, it must be specifically + * mentioned and the state should have a proper description of the fault. + */ + STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_TIMEOUT(Doc.of(Level.FAULT) // + MAX_START_TIMEOUT(Doc.of(Level.WARNING) // .text("Max start time is exceeded")), // - MAX_STOP_TIMEOUT(Doc.of(Level.FAULT) // + MAX_STOP_TIMEOUT(Doc.of(Level.WARNING) // .text("Max stop time is exceeded")), // - INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.FAULT) // + + /** + * Internal StateMachine from KACO. + */ + INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.WARNING) // .text("The 'CurrentState' is invalid")), // - GRID_DISCONNECTION(Doc.of(Level.FAULT) // + GRID_DISCONNECTION(Doc.of(Level.WARNING) // .text("External grid protection disconnection (17)")), // - GRID_FAILURE_LINE_TO_LINE(Doc.of(Level.FAULT) // + GRID_FAILURE_LINE_TO_LINE(Doc.of(Level.WARNING) // .text("Grid failure phase-to-phase voltage (47)")), // - LINE_FAILURE_UNDER_FREQ(Doc.of(Level.FAULT) // + LINE_FAILURE_UNDER_FREQ(Doc.of(Level.WARNING) // .text("Line failure: Grid frequency is too low (48)")), // - LINE_FAILURE_OVER_FREQ(Doc.of(Level.FAULT) // + LINE_FAILURE_OVER_FREQ(Doc.of(Level.WARNING) // .text("Line failure: Grid frequency is too high (49)")), // - PROTECTION_SHUTDOWN_LINE_1(Doc.of(Level.FAULT) // + PROTECTION_SHUTDOWN_LINE_1(Doc.of(Level.WARNING) // .text("Grid Failure: grid voltage L1 protection (81)")), // - PROTECTION_SHUTDOWN_LINE_2(Doc.of(Level.FAULT) // + PROTECTION_SHUTDOWN_LINE_2(Doc.of(Level.WARNING) // .text("Grid Failure: grid voltage L2 protection (82)")), // - PROTECTION_SHUTDOWN_LINE_3(Doc.of(Level.FAULT) // + PROTECTION_SHUTDOWN_LINE_3(Doc.of(Level.WARNING) // .text("Grid Failure: grid voltage L3 protection (83)")), // ; diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java index 84ec0153305..9b5a3cefef3 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImpl.java @@ -28,6 +28,7 @@ import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; +import io.openems.common.channel.AccessMode; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.OptionsEnum; @@ -57,6 +58,9 @@ import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.startstop.StartStop; import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.common.taskmanager.Priority; @@ -75,7 +79,7 @@ ) public class BatteryInverterKacoBlueplanetGridsaveImpl extends AbstractSunSpecBatteryInverter implements BatteryInverterKacoBlueplanetGridsave, ManagedSymmetricBatteryInverter, SymmetricBatteryInverter, - ModbusComponent, OpenemsComponent, TimedataProvider, StartStoppable { + ModbusComponent, ModbusSlave, OpenemsComponent, TimedataProvider, StartStoppable { private static final int UNIT_ID = 1; private static final int READ_FROM_MODBUS_BLOCK = 1; @@ -140,7 +144,6 @@ protected void setModbus(BridgeModbus modbus) { // .put(SunSpecModel.S_136, Priority.LOW) // // .put(SunSpecModel.S_160, Priority.LOW) // - @Activate public BatteryInverterKacoBlueplanetGridsaveImpl() { super(// ACTIVE_MODELS, // @@ -158,11 +161,11 @@ public BatteryInverterKacoBlueplanetGridsaveImpl() { @Activate private void activate(ComponentContext context, Config config) throws OpenemsException { + this.config = config; if (super.activate(context, config.id(), config.alias(), config.enabled(), UNIT_ID, this.cm, "Modbus", config.modbus_id(), READ_FROM_MODBUS_BLOCK)) { return; } - this.config = config; } @Override @@ -279,6 +282,16 @@ private void handleGridDisconnection() { }); } + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(accessMode), // + SymmetricBatteryInverter.getModbusSlaveNatureTable(accessMode), // + ManagedSymmetricBatteryInverter.getModbusSlaveNatureTable(accessMode), // + ModbusSlaveNatureTable.of(BatteryInverterKacoBlueplanetGridsave.class, accessMode, 100) // + .build()); + } + @Override public BatteryInverterConstraint[] getStaticConstraints() throws OpenemsException { if (this.stateMachine.getCurrentState() == State.RUNNING) { diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java index 2757dbf6616..88974aeedf0 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/KacoSunSpecModel.java @@ -106,7 +106,8 @@ public static enum S64201 implements SunSpecPoint { V_AR(new ScaledValuePoint("S64201_V_AR", "AC Reactive Power", "", // ValuePoint.Type.INT16, true, AccessMode.READ_ONLY, Unit.VOLT_AMPERE_REACTIVE, "V_AR_SF")), // HZ(new ScaledValuePoint("S64201_HZ", "Line Frequency", "", // - ValuePoint.Type.INT16, true, AccessMode.READ_ONLY, Unit.MILLIHERTZ, "mHZ_SF")), // + ValuePoint.Type.UINT16, true, AccessMode.READ_ONLY, Unit.HERTZ, "Hz_SF") + ), // RESERVED_36(new ReservedPoint("S64201_RESERVED_36")), // RESERVED_37(new ReservedPoint("S64201_RESERVED_37")), // RESERVED_38(new ReservedPoint("S64201_RESERVED_38")), // @@ -271,6 +272,7 @@ public static enum S64201StVnd implements OptionsEnum { LINE_FAILURE_OVERVOLTAGE_3(46, "Line failure overvoltage L3 The voltage of a grid phase is too low; the grid cannot be fed into. The phase experiencing failure is displayed."), // GRID_FAILURE_PHASETOPHASE(47, "Grid failure phase-to-phase voltage"), // + LINE_FAILURE_UNDERFREQ(48, "Line failure: underfreq. Grid frequency is too low. This fault may be gridrelated."), // LINE_FAILURE_OVERFREQ(49, diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java index 8dc242a57b6..1424879da21 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/src/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/statemachine/ErrorHandler.java @@ -14,11 +14,18 @@ protected void onEntry(Context context) throws OpenemsNamedException { } @Override - public State runAndGetNextState(Context context) throws OpenemsNamedException { + public State runAndGetNextState(Context context) { final var inverter = context.getParent(); if (!inverter.hasFailure()) { - return State.GO_STOPPED; + return State.UNDEFINED; } return State.ERROR; } + + @Override + protected void onExit(Context context) { + final var inverter = context.getParent(); + inverter._setMaxStartTimeout(false); + inverter._setMaxStopTimeout(false); + } } diff --git a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java index 2a9e69ceb53..64a0473325d 100644 --- a/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java +++ b/io.openems.edge.batteryinverter.refu88k/src/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88k.java @@ -43,16 +43,22 @@ public interface BatteryInverterRefuStore88k */ public static int RETRY_COMMAND_MAX_ATTEMPTS = 30; + /* + * Whenever one of these states would be Level.FAULT, the EssGeneric will stop + * the battery and the inverter. If this is necessary, it must be specifically + * mentioned and the state should have a proper description of the fault. + */ + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // - MAX_START_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_START_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of start attempts failed")), // - MAX_STOP_ATTEMPTS(Doc.of(Level.FAULT) // + MAX_STOP_ATTEMPTS(Doc.of(Level.WARNING) // .text("The maximum number of stop attempts failed")), // - INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.FAULT) // + INVERTER_CURRENT_STATE_FAULT(Doc.of(Level.WARNING) // .text("The 'CurrentState' is invalid")), // /* @@ -106,41 +112,41 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { ST(Doc.of(OperatingState.values())), // ST_VND(Doc.of(VendorOperatingState.values())), // // Evt1 Alarms and Warnings - GROUND_FAULT(Doc.of(Level.FAULT) // + GROUND_FAULT(Doc.of(Level.WARNING) // .text("Ground fault")), // - DC_OVER_VOLTAGE(Doc.of(Level.FAULT) // + DC_OVER_VOLTAGE(Doc.of(Level.WARNING) // .text("Dc over voltage")), // - AC_DISCONNECT(Doc.of(Level.FAULT) // + AC_DISCONNECT(Doc.of(Level.WARNING) // .text("AC disconnect open")), // - DC_DISCONNECT(Doc.of(Level.FAULT) // + DC_DISCONNECT(Doc.of(Level.WARNING) // .text("DC disconnect open")), // - GRID_DISCONNECT(Doc.of(Level.FAULT) // + GRID_DISCONNECT(Doc.of(Level.WARNING) // .text("Grid shutdown")), // - CABINET_OPEN(Doc.of(Level.FAULT) // + CABINET_OPEN(Doc.of(Level.WARNING) // .text("Cabinet open")), // - MANUAL_SHUTDOWN(Doc.of(Level.FAULT) // + MANUAL_SHUTDOWN(Doc.of(Level.WARNING) // .text("Manual shutdown")), // - OVER_TEMP(Doc.of(Level.FAULT) // + OVER_TEMP(Doc.of(Level.WARNING) // .text("Over temperature")), // - OVER_FREQUENCY(Doc.of(Level.FAULT) // + OVER_FREQUENCY(Doc.of(Level.WARNING) // .text("Frequency above limit")), // - UNDER_FREQUENCY(Doc.of(Level.FAULT) // + UNDER_FREQUENCY(Doc.of(Level.WARNING) // .text("Frequency under limit")), // - AC_OVER_VOLT(Doc.of(Level.FAULT) // + AC_OVER_VOLT(Doc.of(Level.WARNING) // .text("AC Voltage above limit")), // - AC_UNDER_VOLT(Doc.of(Level.FAULT) // + AC_UNDER_VOLT(Doc.of(Level.WARNING) // .text("AC Voltage under limit")), // - BLOWN_STRING_FUSE(Doc.of(Level.FAULT) // + BLOWN_STRING_FUSE(Doc.of(Level.WARNING) // .text("Blown String fuse on input")), // - UNDER_TEMP(Doc.of(Level.FAULT) // + UNDER_TEMP(Doc.of(Level.WARNING) // .text("Under temperature")), // - MEMORY_LOSS(Doc.of(Level.FAULT) // + MEMORY_LOSS(Doc.of(Level.WARNING) // .text("Generic Memory or Communication error (internal)")), // - HW_TEST_FAILURE(Doc.of(Level.FAULT) // + HW_TEST_FAILURE(Doc.of(Level.WARNING) // .text("Hardware test failure")), // - OTHER_ALARM(Doc.of(Level.FAULT) // + OTHER_ALARM(Doc.of(Level.WARNING) // .text("Other alarm")), // - OTHER_WARNING(Doc.of(Level.FAULT) // + OTHER_WARNING(Doc.of(Level.WARNING) // .text("Other warning")), // EVT_2(Doc.of(OpenemsType.INTEGER).unit(Unit.NONE).accessMode(AccessMode.READ_ONLY)), // EVT_VND_1(Doc.of(OpenemsType.INTEGER).unit(Unit.NONE).accessMode(AccessMode.READ_ONLY)), // diff --git a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java index daa20ac5029..d54e8bbe2d1 100644 --- a/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java +++ b/io.openems.edge.batteryinverter.sinexcel/src/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcel.java @@ -44,7 +44,7 @@ public interface BatteryInverterSinexcel extends OffGridBatteryInverter, Managed public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // SET_ACTIVE_POWER(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_WRITE)// @@ -96,7 +96,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // .persistencePriority(PersistencePriority.HIGH) // .accessMode(AccessMode.READ_ONLY)), // - FAULT_STATUS(Doc.of(Level.FAULT) // + FAULT_STATUS(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY)), // ALERT_STATUS(Doc.of(Level.WARNING) // .accessMode(AccessMode.READ_ONLY)), // diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java index d36835f41f8..3c5c5d64c19 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java @@ -24,6 +24,7 @@ import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.BridgeModbusSerial; +import io.openems.edge.bridge.modbus.api.Config; import io.openems.edge.bridge.modbus.api.Parity; import io.openems.edge.bridge.modbus.api.Stopbit; import io.openems.edge.common.component.OpenemsComponent; @@ -86,15 +87,15 @@ public BridgeModbusSerialImpl() { @Activate private void activate(ComponentContext context, ConfigSerial config) { - super.activate(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), - config.invalidateElementsAfterReadErrors()); + super.activate(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors())); this.applyConfig(config); } @Modified private void modified(ComponentContext context, ConfigSerial config) { - super.modified(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), - config.invalidateElementsAfterReadErrors()); + super.modified(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors())); this.applyConfig(config); this.closeModbusConnection(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java index 9cd9cd3dff3..aacd72d89fd 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java @@ -22,6 +22,7 @@ import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.BridgeModbusTcp; +import io.openems.edge.bridge.modbus.api.Config; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; @@ -56,15 +57,15 @@ public BridgeModbusTcpImpl() { @Activate private void activate(ComponentContext context, ConfigTcp config) throws UnknownHostException { - super.activate(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), - config.invalidateElementsAfterReadErrors()); + super.activate(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors())); this.applyConfig(config); } @Modified private void modified(ComponentContext context, ConfigTcp config) throws UnknownHostException { - super.modified(context, config.id(), config.alias(), config.enabled(), config.logVerbosity(), - config.invalidateElementsAfterReadErrors()); + super.modified(context, new Config(config.id(), config.alias(), config.enabled(), config.logVerbosity(), + config.invalidateElementsAfterReadErrors())); this.applyConfig(config); this.closeModbusConnection(); } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java index 52087943576..2410fbaf055 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractModbusBridge.java @@ -1,6 +1,5 @@ package io.openems.edge.bridge.modbus.api; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import org.osgi.service.component.ComponentContext; @@ -35,8 +34,7 @@ public abstract class AbstractModbusBridge extends AbstractOpenemsComponent impl */ protected static final int DEFAULT_RETRIES = 1; - private final AtomicReference logVerbosity = new AtomicReference<>(LogVerbosity.NONE); - private int invalidateElementsAfterReadErrors = 1; + private Config config = null; protected final ModbusWorker worker = new ModbusWorker( // Execute Task @@ -47,8 +45,8 @@ public abstract class AbstractModbusBridge extends AbstractOpenemsComponent impl state -> this._setCycleTimeIsTooShort(state), // Set ChannelId.CYCLE_DELAY cycleDelay -> this._setCycleDelay(cycleDelay), - // LogVerbosity - this.logVerbosity // + // LogHandler + () -> this.config.log // ); protected AbstractModbusBridge(io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, @@ -62,12 +60,11 @@ protected void activate(ComponentContext context, String id, String alias, boole throw new IllegalArgumentException("Use the other activate() method."); } - protected void activate(ComponentContext context, String id, String alias, boolean enabled, - LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { - super.activate(context, id, alias, enabled); - this.applyConfig(logVerbosity, invalidateElementsAfterReadErrors); - if (enabled) { - this.worker.activate(id); + protected void activate(ComponentContext context, Config config) { + super.activate(context, config.id, config.alias, config.enabled); + this.applyConfig(config); + if (config.enabled) { + this.worker.activate(config.id); } } @@ -84,20 +81,18 @@ protected void modified(ComponentContext context, String id, String alias, boole throw new IllegalArgumentException("Use the other modified() method."); } - protected void modified(ComponentContext context, String id, String alias, boolean enabled, - LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { - super.modified(context, id, alias, enabled); - this.applyConfig(logVerbosity, invalidateElementsAfterReadErrors); - if (enabled) { - this.worker.modified(id); + protected void modified(ComponentContext context, Config config) { + super.modified(context, config.id, config.alias, config.enabled); + this.applyConfig(config); + if (config.enabled) { + this.worker.modified(config.id); } else { this.worker.deactivate(); } } - private void applyConfig(LogVerbosity logVerbosity, int invalidateElementsAfterReadErrors) { - this.logVerbosity.set(logVerbosity); - this.invalidateElementsAfterReadErrors = invalidateElementsAfterReadErrors; + private void applyConfig(Config config) { + this.config = config; } /** @@ -124,22 +119,24 @@ public void removeProtocol(String sourceId) { @Override public void handleEvent(Event event) { - if (!this.isEnabled()) { + if (this.config == null || !this.isEnabled()) { return; } switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: - this.worker.onBeforeProcessImage(); - break; - case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE: - this.worker.onExecuteWrite(); - break; + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // + -> this.worker.onBeforeProcessImage(); + + case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // + -> this.worker.onExecuteWrite(); } } @Override public String debugLog() { - return switch (this.logVerbosity.get()) { + if (this.config == null) { + return null; + } + return switch (this.config.log.verbosity) { case NONE -> // null; case DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE, @@ -167,7 +164,7 @@ public String debugLog() { * @return {@link LogVerbosity} */ public LogVerbosity getLogVerbosity() { - return this.logVerbosity.get(); + return this.config.log.verbosity; } /** @@ -177,7 +174,7 @@ public LogVerbosity getLogVerbosity() { * @return value */ public int invalidateElementsAfterReadErrors() { - return this.invalidateElementsAfterReadErrors; + return this.config.invalidateElementsAfterReadErrors; } @Override diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java index 7bf6e9e249d..80c29c5f234 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/AbstractOpenemsModbusComponent.java @@ -110,17 +110,7 @@ protected void activate(String id) { protected boolean activate(ComponentContext context, String id, String alias, boolean enabled, int unitId, ConfigurationAdmin cm, String modbusReference, String modbusId) throws OpenemsException { super.activate(context, id, alias, enabled); - // update filter for 'Modbus' - if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), "Modbus", modbusId)) { - return true; - } - this.unitId = unitId; - var modbus = this.modbus.get(); - if (this.isEnabled() && modbus != null) { - modbus.addProtocol(this.id(), this.getModbusProtocol()); - modbus.retryModbusCommunication(this.id()); - } - return false; + return this.activateOrModified(unitId, cm, modbusReference, modbusId); } @Override @@ -147,19 +137,41 @@ protected void activate(ComponentContext context, String id, String alias, boole * @param modbusId The ID of the Modbus bridge. Typically * 'config.modbus_id()' * @return true if the target filter was updated. You may use it to abort the - * activate() method. + * modified() method. * @throws OpenemsException on error */ protected boolean modified(ComponentContext context, String id, String alias, boolean enabled, int unitId, ConfigurationAdmin cm, String modbusReference, String modbusId) throws OpenemsException { super.modified(context, id, alias, enabled); + return this.activateOrModified(unitId, cm, modbusReference, modbusId); + } + + @Override + protected void modified(ComponentContext context, String id, String alias, boolean enabled) { + throw new IllegalArgumentException("Use the other modified() for Modbus components!"); + } + + /** + * Common tasks for @Activate and @Modified. + * + * @param unitId Unit-ID of the Modbus target + * @param cm An instance of ConfigurationAdmin. Receive it + * using @Reference + * @param modbusReference The name of the @Reference setter method for the + * Modbus bridge - e.g. 'Modbus' if you have a + * setModbus()-method + * @param modbusId The ID of the Modbus bridge. Typically + * 'config.modbus_id()' + * @return true if the target filter was updated. You may use it to abort the + * activate() or modified() method. + */ + private boolean activateOrModified(int unitId, ConfigurationAdmin cm, String modbusReference, String modbusId) { // update filter for 'Modbus' - if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), "Modbus", modbusId)) { + if (OpenemsComponent.updateReferenceFilter(cm, this.servicePid(), modbusReference, modbusId)) { return true; } this.unitId = unitId; var modbus = this.modbus.get(); - modbus.removeProtocol(this.id()); if (this.isEnabled() && modbus != null) { modbus.addProtocol(this.id(), this.getModbusProtocol()); modbus.retryModbusCommunication(this.id()); @@ -167,11 +179,6 @@ protected boolean modified(ComponentContext context, String id, String alias, bo return false; } - @Override - protected void modified(ComponentContext context, String id, String alias, boolean enabled) { - throw new IllegalArgumentException("Use the other activate() for Modbus components!"); - } - @Override protected void deactivate() { super.deactivate(); diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java new file mode 100644 index 00000000000..9f6e98b6d45 --- /dev/null +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/Config.java @@ -0,0 +1,60 @@ +package io.openems.edge.bridge.modbus.api; + +import java.util.function.Supplier; + +import org.slf4j.Logger; + +public class Config { + + public final String id; + public final String alias; + public final boolean enabled; + public final int invalidateElementsAfterReadErrors; + public final LogHandler log; + + public Config(String id, String alias, boolean enabled, LogVerbosity logVerbosity, + int invalidateElementsAfterReadErrors) { + this.id = id; + this.alias = alias; + this.enabled = enabled; + this.invalidateElementsAfterReadErrors = invalidateElementsAfterReadErrors; + this.log = new LogHandler(this, logVerbosity); + } + + public static class LogHandler { + public final LogVerbosity verbosity; + + private final Config config; + + private LogHandler(Config config, LogVerbosity logVerbosity) { + this.config = config; + this.verbosity = logVerbosity; + } + + /** + * Logs messages for + * {@link LogVerbosity#READS_AND_WRITES_DURATION_TRACE_EVENTS}. + * + * @param logger the {@link Logger} + * @param message the String message + */ + public void trace(Logger logger, Supplier message) { + if (this.isTrace()) { + logger.info("[" + this.config.id + "] " + message.get()); + } + } + + /** + * Return true if {@link LogVerbosity#READS_AND_WRITES_DURATION_TRACE_EVENTS} is + * active. + * + * @return true for trace-log + */ + public boolean isTrace() { + return switch (this.verbosity) { + case NONE, DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE -> false; + case READS_AND_WRITES_DURATION_TRACE_EVENTS -> true; + }; + } + } +} \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java index ace3a2c4da4..c2b284dd80f 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/ModbusComponent.java @@ -20,7 +20,13 @@ public interface ModbusComponent extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - MODBUS_COMMUNICATION_FAILED(Doc.of(Level.FAULT) // + + /* + * If ModbusCommunicationFault would be a FaultState, check it explicitly in + * Generic Ess ErrorHandler, as the battery could still have a communication + * fault while starting the battery + */ + MODBUS_COMMUNICATION_FAILED(Doc.of(Level.WARNING) // .debounce(10, Debounce.SAME_VALUES_IN_A_ROW_TO_CHANGE) // .text("Modbus Communication failed")) // ; diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java index 68aed664b5d..2558852fc4d 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/ModbusWorker.java @@ -1,12 +1,12 @@ package io.openems.edge.bridge.modbus.api.worker; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import io.openems.common.worker.AbstractImmediateWorker; import io.openems.edge.bridge.modbus.api.BridgeModbus; -import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.Config.LogHandler; import io.openems.edge.bridge.modbus.api.ModbusComponent; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.bridge.modbus.api.element.ModbusElement; @@ -52,18 +52,19 @@ public class ModbusWorker extends AbstractImmediateWorker { * @param cycleDelayChannel sets the * {@link BridgeModbus.ChannelId#CYCLE_DELAY} * channel - * @param logVerbosity the configured {@link LogVerbosity} + * @param logHandler a {@link Supplier} for the + * {@link LogHandler} */ public ModbusWorker(Function execute, Consumer invalidate, Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel, - AtomicReference logVerbosity) { + Supplier logHandler) { this.execute = execute; this.invalidate = invalidate; - this.defectiveComponents = new DefectiveComponents(logVerbosity); - this.tasksSupplier = new TasksSupplierImpl(); + this.defectiveComponents = new DefectiveComponents(logHandler); + this.tasksSupplier = new TasksSupplierImpl(logHandler); this.cycleTasksManager = new CycleTasksManager(this.tasksSupplier, this.defectiveComponents, - cycleTimeIsTooShortChannel, cycleDelayChannel, logVerbosity); + cycleTimeIsTooShortChannel, cycleDelayChannel, logHandler); } @Override @@ -123,7 +124,7 @@ private void markComponentAsDefective(ModbusComponent component, boolean isDefec * @param protocol the ModbusProtocol */ public void addProtocol(String sourceId, ModbusProtocol protocol) { - this.tasksSupplier.addProtocol(sourceId, protocol); + this.tasksSupplier.addProtocol(sourceId, protocol, this.invalidate); this.defectiveComponents.remove(sourceId); // Cleanup } @@ -133,7 +134,7 @@ public void addProtocol(String sourceId, ModbusProtocol protocol) { * @param sourceId Component-ID of the source */ public void removeProtocol(String sourceId) { - this.tasksSupplier.removeProtocol(sourceId); + this.tasksSupplier.removeProtocol(sourceId, this.invalidate); this.defectiveComponents.remove(sourceId); // Cleanup } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java index fbee14394ac..03222a82a04 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManager.java @@ -1,12 +1,12 @@ package io.openems.edge.bridge.modbus.api.worker.internal; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.Config.LogHandler; import io.openems.edge.bridge.modbus.api.task.Task; import io.openems.edge.bridge.modbus.api.task.WaitTask; import io.openems.edge.bridge.modbus.api.worker.ModbusWorker; @@ -26,7 +26,7 @@ public class CycleTasksManager { private final TasksSupplier tasksSupplier; private final DefectiveComponents defectiveComponents; private final Consumer cycleTimeIsTooShortChannel; - private final AtomicReference logVerbosity; + private final Supplier logHandler; private final WaitDelayHandler waitDelayHandler; private final WaitTask.Mutex waitMutexTask = new WaitTask.Mutex(); @@ -35,22 +35,15 @@ public class CycleTasksManager { public CycleTasksManager(TasksSupplier tasksSupplier, DefectiveComponents defectiveComponents, Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel, - AtomicReference logVerbosity) { + Supplier logHandler) { this.tasksSupplier = tasksSupplier; this.defectiveComponents = defectiveComponents; this.cycleTimeIsTooShortChannel = cycleTimeIsTooShortChannel; - this.logVerbosity = logVerbosity; - + this.logHandler = logHandler; this.waitDelayHandler = new WaitDelayHandler(() -> this.onWaitDelayTaskFinished(), cycleDelayChannel); } - protected CycleTasksManager(TasksSupplier tasksSupplier, DefectiveComponents defectiveComponents, - Consumer cycleTimeIsTooShortChannel, Consumer cycleDelayChannel) { - this(tasksSupplier, defectiveComponents, cycleTimeIsTooShortChannel, cycleDelayChannel, - new AtomicReference<>(LogVerbosity.NONE)); - } - - private static enum StateMachine { + protected static enum StateMachine { INITIAL_WAIT, // READ_BEFORE_WRITE, // WAIT_FOR_WRITE, // @@ -62,24 +55,31 @@ private static enum StateMachine { private StateMachine state = StateMachine.FINISHED; + /** + * Gets the current state. + * + * @return the {@link StateMachine} + */ + protected StateMachine getState() { + return this.state; + } + /** * Called on BEFORE_PROCESS_IMAGE event. */ public synchronized void onBeforeProcessImage() { // Calculate Delay - var waitDelayHandlerLog = this.waitDelayHandler.onBeforeProcessImage(this.isTraceLog()); + final var waitDelayHandlerLog = this.waitDelayHandler.onBeforeProcessImage(this.logHandler.get().isTrace()); // Evaluate Cycle-Time-Is-Too-Short, invalidate time measurement and stop early var cycleTimeIsTooShort = this.state != StateMachine.FINISHED; this.cycleTimeIsTooShortChannel.accept(cycleTimeIsTooShort); if (cycleTimeIsTooShort) { this.waitDelayHandler.timeIsInvalid(); - if (this.isTraceLog()) { - this.log.info("State: " + this.state + " unchanged" // - + " (in onBeforeProcessImage)" // - + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // - + waitDelayHandlerLog); - } + this.traceLog(() -> "State: " + this.state + " unchanged" // + + " (in onBeforeProcessImage)" // + + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // + + waitDelayHandlerLog); return; } @@ -90,18 +90,19 @@ public synchronized void onBeforeProcessImage() { this.cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents); // On defectiveComponents invalidate time measurement - if (this.cycleTasks.containsDefectiveComponent(this.defectiveComponents)) { + final var containsDefectiveComponents = this.cycleTasks.containsDefectiveComponent(this.defectiveComponents); + if (containsDefectiveComponents) { this.waitDelayHandler.timeIsInvalid(); - waitDelayHandlerLog += " DEFECTIVE_COMPONENT"; } // Initialize next Cycle - if (this.isTraceLog()) { - this.log.info("State: " + this.state + " -> " + StateMachine.INITIAL_WAIT // - + " (in onBeforeProcessImage)" // - + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // - + waitDelayHandlerLog); - } + this.traceLog(() -> "State: " + this.state + " -> " + StateMachine.INITIAL_WAIT // + + " (in onBeforeProcessImage)" // + + " Delay [" + this.waitDelayHandler.getWaitDelayTask().initialDelay + "] " // + + waitDelayHandlerLog // + + (containsDefectiveComponents // + ? " DEFECTIVE_COMPONENT" + : "")); this.state = StateMachine.INITIAL_WAIT; // Interrupt wait @@ -112,9 +113,7 @@ public synchronized void onBeforeProcessImage() { * Called on EXECUTE_WRITE event. */ public synchronized void onExecuteWrite() { - if (this.isTraceLog()) { - this.log.info("State: " + this.state + " -> " + StateMachine.WRITE + " (onExecuteWrite)"); - } + this.traceLog(() -> "State: " + this.state + " -> " + StateMachine.WRITE + " (onExecuteWrite)"); this.state = StateMachine.WRITE; this.waitMutexTask.release(); @@ -128,7 +127,8 @@ public synchronized void onExecuteWrite() { */ public Task getNextTask() { if (this.cycleTasks == null) { - return this.waitMutexTask; + // Fallback to avoid NPE on race condition + this.cycleTasks = this.tasksSupplier.getCycleTasks(this.defectiveComponents); } var previousState = this.state; // drop before release @@ -187,8 +187,8 @@ public Task getNextTask() { } }; - if (this.state != previousState && this.isTraceLog()) { - this.log.info("State: " + previousState + " -> " + this.state + " (getNextTask)"); + if (this.state != previousState) { + this.traceLog(() -> "State: " + previousState + " -> " + this.state + " (getNextTask)"); } return nextTask; } @@ -207,16 +207,11 @@ private synchronized void onWaitDelayTaskFinished() { }; if (this.state != previousState) { - if (this.isTraceLog()) { - this.log.info("State: " + previousState + " -> " + this.state + " (onWaitDelayTaskFinished)"); - } + this.traceLog(() -> "State: " + previousState + " -> " + this.state + " (onWaitDelayTaskFinished)"); } } - private boolean isTraceLog() { - return switch (this.logVerbosity.get()) { - case READS_AND_WRITES_DURATION_TRACE_EVENTS -> true; - case NONE, DEBUG_LOG, READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE -> false; - }; + private void traceLog(Supplier message) { + this.logHandler.get().trace(this.log, message); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java index 6fa218f805f..80cb95937df 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponents.java @@ -4,12 +4,12 @@ import java.time.Instant; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.openems.edge.bridge.modbus.api.LogVerbosity; +import io.openems.edge.bridge.modbus.api.Config.LogHandler; import io.openems.edge.common.type.TypeUtils; public class DefectiveComponents { @@ -23,24 +23,16 @@ private static record NextTry(Instant timestamp, int count) { } private final Clock clock; - private final AtomicReference logVerbosity; + private final Supplier logHandler; private final Map nextTries = new HashMap<>(); - public DefectiveComponents(AtomicReference logVerbosity) { - this(Clock.systemDefaultZone(), logVerbosity); + public DefectiveComponents(Supplier logHandler) { + this(Clock.systemDefaultZone(), logHandler); } - protected DefectiveComponents() { - this(Clock.systemDefaultZone(), new AtomicReference<>(LogVerbosity.READS_AND_WRITES_DURATION_TRACE_EVENTS)); - } - - protected DefectiveComponents(Clock clock) { - this(clock, new AtomicReference<>(LogVerbosity.READS_AND_WRITES_DURATION_TRACE_EVENTS)); - } - - protected DefectiveComponents(Clock clock, AtomicReference logVerbosity) { + protected DefectiveComponents(Clock clock, Supplier logHandler) { this.clock = clock; - this.logVerbosity = logVerbosity; + this.logHandler = logHandler; } /** @@ -54,15 +46,11 @@ public synchronized void add(String componentId) { this.nextTries.compute(componentId, (k, v) -> { var count = (v == null) ? 1 : v.count + 1; var wait = Math.min(INCREASE_WAIT_SECONDS * count, MAX_WAIT_SECONDS); - if (this.isTraceLog()) { - final String log; - if (count == 1) { - log = "Add [" + componentId + "] to defective Components."; - } else { - log = "Increase wait for defective Component [" + componentId + "]."; - } - this.log.info(log + " Wait [" + wait + "s]" + " Count [" + count + "]"); - } + this.traceLog(() -> // + (count == 1 // + ? "Add [" + componentId + "] to defective Components." // + : "Increase wait for defective Component [" + componentId + "].") + " Wait [" + wait + "s]" + + " Count [" + count + "]"); return new NextTry(Instant.now(this.clock).plusSeconds(wait), count); }); } @@ -74,8 +62,8 @@ public synchronized void add(String componentId) { */ public synchronized void remove(String componentId) { TypeUtils.assertNull("DefectiveComponents remove() takes no null values", componentId); - if (this.nextTries.remove(componentId) != null && this.isTraceLog()) { - this.log.info("Remove [" + componentId + "] from defective Components."); + if (this.nextTries.remove(componentId) != null) { + this.traceLog(() -> "Remove [" + componentId + "] from defective Components."); } } @@ -105,12 +93,7 @@ public synchronized Boolean isDueForNextTry(String componentId) { return now.isAfter(nextTry.timestamp); } - private boolean isTraceLog() { - return switch (this.logVerbosity.get()) { - case READS_AND_WRITES, READS_AND_WRITES_DURATION, READS_AND_WRITES_VERBOSE, - READS_AND_WRITES_DURATION_TRACE_EVENTS -> - true; - case NONE, DEBUG_LOG -> false; - }; + private void traceLog(Supplier message) { + this.logHandler.get().trace(this.log, message); } } \ No newline at end of file diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java index e7c6a4bcb02..2f836ee0c14 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImpl.java @@ -5,9 +5,16 @@ import java.util.LinkedList; import java.util.Map; import java.util.Queue; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.edge.bridge.modbus.api.Config.LogHandler; import io.openems.edge.bridge.modbus.api.ModbusProtocol; +import io.openems.edge.bridge.modbus.api.element.ModbusElement; import io.openems.edge.bridge.modbus.api.task.ReadTask; import io.openems.edge.bridge.modbus.api.task.Task; import io.openems.edge.bridge.modbus.api.task.WriteTask; @@ -20,6 +27,13 @@ */ public class TasksSupplierImpl implements TasksSupplier { + private final Logger log = LoggerFactory.getLogger(TasksSupplierImpl.class); + private final Supplier logHandler; + + public TasksSupplierImpl(Supplier logHandler) { + this.logHandler = logHandler; + } + /** * Source-ID -> TasksManager for {@link Task}s. */ @@ -31,22 +45,45 @@ public class TasksSupplierImpl implements TasksSupplier { private final Queue> nextLowPriorityTasks = new LinkedList<>(); /** - * Adds the protocol. - * - * @param sourceId Component-ID of the source - * @param protocol the ModbusProtocol + * Adds (or replaces) the protocol identified by its sourceId. + * + *

+ * If a protocol with the same sourceId existed before, + * {@link #removeProtocol(String, Consumer)} is called internally first. + * + * @param sourceId Component-ID of the source + * @param protocol the ModbusProtocol + * @param invalidate invalidates the given {@link ModbusElement}s after read + * errors */ - public synchronized void addProtocol(String sourceId, ModbusProtocol protocol) { + public synchronized void addProtocol(String sourceId, ModbusProtocol protocol, + Consumer invalidate) { + this.removeProtocol(sourceId, invalidate); // remove if sourceId exists + + this.traceLog(() -> "Add Protocol for " // + + "[" + sourceId + "] with " // + + "[" + protocol.getTaskManager().countTasks() + "] tasks"); this.taskManagers.put(sourceId, protocol.getTaskManager()); } /** - * Removes the protocol. + * Removes the protocol and invalidates all {@link ModbusElement}s. * - * @param sourceId Component-ID of the source + * @param sourceId Component-ID of the source + * @param invalidate invalidates the given {@link ModbusElement}s after read + * errors */ - public synchronized void removeProtocol(String sourceId) { - this.taskManagers.remove(sourceId); + public synchronized void removeProtocol(String sourceId, Consumer invalidate) { + var taskManager = this.taskManagers.remove(sourceId); + if (taskManager == null) { + return; + } + + this.traceLog(() -> "Remove Protocol for " // + + "[" + sourceId + "] with " // + + "[" + taskManager.countTasks() + "] tasks"); + taskManager.getTasks() // + .forEach(t -> invalidate.accept(t.getElements())); this.nextLowPriorityTasks.removeIf(t -> t.a() == sourceId); } @@ -84,7 +121,7 @@ public synchronized CycleTasks getCycleTasks(DefectiveComponents defectiveCompon componentTasks.clear(); } }); - return new CycleTasks(// + var result = new CycleTasks(// tasks.values().stream().flatMap(LinkedList::stream) // .filter(ReadTask.class::isInstance).map(ReadTask.class::cast) // // Sort HIGH priority to the end @@ -93,6 +130,11 @@ public synchronized CycleTasks getCycleTasks(DefectiveComponents defectiveCompon tasks.values().stream().flatMap(LinkedList::stream) // .filter(WriteTask.class::isInstance).map(WriteTask.class::cast) // .collect(Collectors.toCollection(LinkedList::new))); + + this.traceLog(() -> "Getting " // + + "[" + result.reads().size() + "] read and " // + + "[" + result.writes().size() + "] write tasks for this Cycle"); + return result; } /** @@ -128,4 +170,8 @@ public synchronized int getTotalNumberOfTasks() { .mapToInt(m -> m.countTasks()) // .sum(); } + + private void traceLog(Supplier message) { + this.logHandler.get().trace(this.log, message); + } } diff --git a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java index 1d508bb9363..017e7e7bd6f 100644 --- a/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java +++ b/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/test/DummyModbusBridge.java @@ -11,6 +11,7 @@ import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.BridgeModbus; import io.openems.edge.bridge.modbus.api.BridgeModbusTcp; +import io.openems.edge.bridge.modbus.api.Config; import io.openems.edge.bridge.modbus.api.LogVerbosity; import io.openems.edge.bridge.modbus.api.ModbusProtocol; import io.openems.edge.common.channel.Channel; @@ -35,7 +36,7 @@ public DummyModbusBridge(String id, LogVerbosity logVerbosity) { for (Channel channel : this.channels()) { channel.nextProcessImage(); } - super.activate(null, id, "", true, logVerbosity, 2); + super.activate(null, new Config(id, "", true, logVerbosity, 2)); } /** diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java index 276dc8dcce6..e9400baf670 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java @@ -1,6 +1,5 @@ package io.openems.edge.bridge.modbus; -import org.junit.Ignore; import org.junit.Test; import com.ghgande.j2mod.modbus.procimg.Register; @@ -35,7 +34,6 @@ public class BridgeModbusTcpImplTest { private static final ChannelAddress MODBUS_COMMUNICATION_FAILED = new ChannelAddress(DEVICE_ID, "ModbusCommunicationFailed"); - @Ignore @Test public void test() throws Exception { final ThrowingRunnable sleep = () -> Thread.sleep(CYCLE_TIME); @@ -57,9 +55,7 @@ public void test() throws Exception { * Instantiate Modbus-Bridge */ var sut = new BridgeModbusTcpImpl(); - var device = new MyModbusComponent(DEVICE_ID, sut, UNIT_ID); var test = new ComponentTest(sut) // - .addComponent(device) // .activate(MyConfigTcp.create() // .setId(MODBUS_ID) // .setIp("127.0.0.1") // @@ -67,6 +63,7 @@ public void test() throws Exception { .setInvalidateElementsAfterReadErrors(1) // .setLogVerbosity(LogVerbosity.NONE) // .build()); + test.addComponent(new MyModbusComponent(DEVICE_ID, sut, UNIT_ID)); /* * Successfully read Register @@ -80,30 +77,18 @@ public void test() throws Exception { .output(MODBUS_COMMUNICATION_FAILED, false)); // /* - * Reading Register fails after debounce of 10 + * Remove Protocol and unset channel values */ - processImage.removeRegister(register100); - for (var i = 0; i < 9; i++) { - test.next(new TestCase() // - .onAfterProcessImage(sleep)); - } - test // - .next(new TestCase() // - .onAfterProcessImage(sleep) // - .output(MODBUS_COMMUNICATION_FAILED, false)) // - .next(new TestCase() // - .onAfterProcessImage(sleep) // - .output(MODBUS_COMMUNICATION_FAILED, true)); + sut.removeProtocol(DEVICE_ID); - /* - * Successfully read Register - */ - processImage.addRegister(100, register100); test // + .next(new TestCase() // + .onAfterProcessImage(sleep)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .output(REGISTER_100, 123) // + .output(REGISTER_100, null) // .output(MODBUS_COMMUNICATION_FAILED, false)); // + } finally { if (slave != null) { slave.close(); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java index 0d764fdecef..e57a4b32917 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/CycleTasksManagerTest.java @@ -1,15 +1,21 @@ package io.openems.edge.bridge.modbus.api.worker.internal; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.FINISHED; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.WAIT_BEFORE_READ; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManager.StateMachine.WRITE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.function.Consumer; +import java.util.function.Supplier; import org.junit.Before; import org.junit.Test; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.bridge.modbus.DummyModbusComponent; +import io.openems.edge.bridge.modbus.api.Config; +import io.openems.edge.bridge.modbus.api.LogVerbosity; import io.openems.edge.bridge.modbus.api.task.WaitTask; import io.openems.edge.bridge.modbus.api.worker.DummyReadTask; import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask; @@ -21,6 +27,8 @@ public class CycleTasksManagerTest { }; public static final Consumer CYCLE_DELAY = (cycleDelay) -> { }; + private static final Config CONFIG = new Config("foo", "bar", true, LogVerbosity.NONE, 1); + public static final Supplier LOG_HANDLER = () -> CONFIG.log; private static DummyReadTask RT_H_1; private static DummyReadTask RT_H_2; @@ -48,9 +56,10 @@ public void testIdealConditions() throws OpenemsException, InterruptedException .writes(WT_1) // .build(); var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2); - var defectiveComponents = new DefectiveComponents(); + var defectiveComponents = new DefectiveComponents(LOG_HANDLER); - var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, // + CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER); // Cycle 1 sut.onBeforeProcessImage(); @@ -117,6 +126,32 @@ public void testIdealConditions() throws OpenemsException, InterruptedException // task.execute(null); -> this would block in single-threaded JUnit test } + @Test + public void testExecuteWriteBeforeNextProcessImage() throws OpenemsException, InterruptedException { + var cycle1 = CycleTasks.create() // + .reads(RT_L_1, RT_H_1, RT_H_2) // + .writes(WT_1) // + .build(); + var cycle2 = CycleTasks.create() // + .reads(RT_L_2, RT_H_1, RT_H_2) // + .writes(WT_1) // + .build(); + var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2); + var defectiveComponents = new DefectiveComponents(LOG_HANDLER); + + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, // + CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER); + + sut.getNextTask(); + assertEquals(FINISHED, sut.getState()); + sut.onExecuteWrite(); + sut.getNextTask(); + assertEquals(WRITE, sut.getState()); + sut.onBeforeProcessImage(); + sut.getNextTask(); + assertEquals(WAIT_BEFORE_READ, sut.getState()); + } + @Test public void testDefective() throws OpenemsException, InterruptedException { var component = new DummyModbusComponent(); @@ -131,10 +166,11 @@ public void testDefective() throws OpenemsException, InterruptedException { .writes(WT_1) // .build(); var tasksSupplier = new DummyTasksSupplier(cycle1, cycle2); - var defectiveComponents = new DefectiveComponents(); + var defectiveComponents = new DefectiveComponents(LOG_HANDLER); defectiveComponents.add(component.id()); - var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, // + CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER); // Cycle 1 sut.onBeforeProcessImage(); @@ -162,9 +198,10 @@ public void testNoTasks() throws OpenemsException, InterruptedException { var cycle1 = CycleTasks.create() // .build(); var tasksSupplier = new DummyTasksSupplier(cycle1); - var defectiveComponents = new DefectiveComponents(); + var defectiveComponents = new DefectiveComponents(LOG_HANDLER); - var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY); + var sut = new CycleTasksManager(tasksSupplier, defectiveComponents, // + CYCLE_TIME_IS_TOO_SHORT, CYCLE_DELAY, LOG_HANDLER); // Cycle 1 sut.onBeforeProcessImage(); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java index 3f8d57ea014..615037eb9de 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java @@ -1,5 +1,6 @@ package io.openems.edge.bridge.modbus.api.worker.internal; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -17,7 +18,7 @@ public class DefectiveComponentsTest { @Test public void testIsDueForNextTry() { var clock = new TimeLeapClock(); - var sut = new DefectiveComponents(clock); + var sut = new DefectiveComponents(clock, LOG_HANDLER); assertNull(sut.isDueForNextTry(CMP)); sut.add(CMP); @@ -29,7 +30,7 @@ public void testIsDueForNextTry() { @Test public void testAddRemove() { var clock = new TimeLeapClock(); - var sut = new DefectiveComponents(clock); + var sut = new DefectiveComponents(clock, LOG_HANDLER); sut.add(CMP); clock.leap(30_001, ChronoUnit.MILLIS); @@ -40,7 +41,7 @@ public void testAddRemove() { @Test public void testIsKnownw() { - var sut = new DefectiveComponents(); + var sut = new DefectiveComponents(LOG_HANDLER); sut.add(CMP); assertTrue(sut.isKnown(CMP)); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java index 0af815bda47..1b016400acf 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.bridge.modbus.api.worker.internal; +import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -11,6 +12,7 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.test.TimeLeapClock; +import io.openems.common.utils.FunctionUtils; import io.openems.edge.bridge.modbus.DummyModbusComponent; import io.openems.edge.bridge.modbus.api.worker.DummyReadTask; import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask; @@ -36,13 +38,13 @@ public void before() { @Test public void testFull() throws OpenemsException { var clock = new TimeLeapClock(); - var defectiveComponents = new DefectiveComponents(clock); - var sut = new TasksSupplierImpl(); + var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER); + var sut = new TasksSupplierImpl(LOG_HANDLER); var component = new DummyModbusComponent(); var protocol = component.getModbusProtocol(); protocol.addTasks(RT_H_1, RT_H_2, RT_L_1, RT_L_2, WT_1); - sut.addProtocol(component.id(), protocol); + sut.addProtocol(component.id(), protocol, FunctionUtils::doNothing); // 1st Cycle var tasks = sut.getCycleTasks(defectiveComponents); @@ -82,19 +84,19 @@ public void testFull() throws OpenemsException { assertEquals(4, tasks.reads().size() + tasks.writes().size()); // Finish - sut.removeProtocol(component.id()); + sut.removeProtocol(component.id(), FunctionUtils::doNothing); } @Test public void testHighOnly() throws OpenemsException { var clock = new TimeLeapClock(); - var defectiveComponents = new DefectiveComponents(clock); - var sut = new TasksSupplierImpl(); + var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER); + var sut = new TasksSupplierImpl(LOG_HANDLER); var component = new DummyModbusComponent(); var protocol = component.getModbusProtocol(); protocol.addTasks(RT_H_1, RT_H_2, WT_1); - sut.addProtocol(component.id(), protocol); + sut.addProtocol(component.id(), protocol, FunctionUtils::doNothing); var tasks = sut.getCycleTasks(defectiveComponents); assertEquals(3, tasks.reads().size() + tasks.writes().size()); diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java index c10ee0d08f2..aa31e104d1f 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java @@ -3,6 +3,7 @@ import io.openems.common.OpenemsConstants; import io.openems.common.channel.AccessMode; import io.openems.common.channel.PersistencePriority; +import io.openems.common.channel.Unit; import io.openems.common.oem.OpenemsEdgeOem; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; @@ -12,6 +13,7 @@ import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.common.modbusslave.ModbusSlaveTable; +import io.openems.edge.common.modbusslave.ModbusType; public interface Meta extends ModbusSlave { @@ -29,7 +31,18 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ VERSION(Doc.of(OpenemsType.STRING) // .persistencePriority(PersistencePriority.HIGH)), - + /** + * System Time: seconds since 1st January 1970 00:00:00 UTC. + * + *

    + *
  • Interface: Meta + *
  • Type: Long + *
+ */ + SYSTEM_TIME_UTC(Doc.of(OpenemsType.LONG) // + .unit(Unit.SECONDS) // + .text("System Time: seconds since 1st January 1970 00:00:00 UTC") // + .persistencePriority(PersistencePriority.VERY_LOW)), /** * Edge currency. * @@ -73,6 +86,7 @@ public static ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode, Openem .string16(51, "Manufacturer Version", oem.getManufacturerVersion()) // .string16(67, "Manufacturer Serial Number", oem.getManufacturerSerialNumber()) // .string16(83, "Manufacturer EMS Serial Number", oem.getManufacturerEmsSerialNumber()) // + .channel(99, ChannelId.SYSTEM_TIME_UTC, ModbusType.UINT64) // .build()); } diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java index 670a58a3ebc..0a8f7239042 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordChannel.java @@ -41,6 +41,7 @@ public ModbusRecordChannel(int offset, ModbusType type, ChannelId channelId, Acc case STRING16 -> ModbusRecordString16.BYTE_LENGTH; case ENUM16, UINT16 -> ModbusRecordUint16.BYTE_LENGTH; case UINT32 -> ModbusRecordUint32.BYTE_LENGTH; + case UINT64 -> ModbusRecordUint64.BYTE_LENGTH; }; this.writeValueBuffer = new Byte[byteLength]; } @@ -140,6 +141,11 @@ public byte[] getValue(OpenemsComponent component) { case READ_ONLY, READ_WRITE -> ModbusRecordUint32.toByteArray(value); case WRITE_ONLY -> ModbusRecordUint32.UNDEFINED_VALUE; }; + case UINT64 -> // + switch (this.accessMode) { + case READ_ONLY, READ_WRITE -> ModbusRecordUint64.toByteArray(value); + case WRITE_ONLY -> ModbusRecordUint64.UNDEFINED_VALUE; + }; }; } @@ -190,6 +196,7 @@ public void writeValue(int index, byte byte1, byte byte2) { case STRING16 -> ""; // TODO implement String conversion case ENUM16, UINT16 -> buff.getShort(); case UINT32 -> buff.getInt(); + case UINT64 -> buff.getLong(); }; // Forward Value to ApiWorker diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java index 7158dd50e88..9a5b376f6c3 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordConstant.java @@ -1,5 +1,8 @@ package io.openems.edge.common.modbusslave; +import java.util.function.Consumer; +import java.util.function.Function; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,4 +52,44 @@ public AccessMode getAccessMode() { return AccessMode.READ_ONLY; } + /** + * Generates a common toString() method for implementations of + * {@link ModbusRecordConstant}. + * + * @param the type of the value + * @param name the name of the implementation class + * @param callback a {@link StringBuilder} callback + * @param value the actual value + * @param toHexString the toHexString() method + * @return a {@link String} + */ + protected String generateToString(String name, Consumer callback, T value, + Function toHexString) { + var b = new StringBuilder() // + .append(name) // + .append(" ["); + if (callback != null) { + callback.accept(b); + } + b.append("value="); + if (value != null) { + b.append(value); + if (toHexString != null) { + b.append("/0x").append(toHexString.apply(value)); + } + } else { + b.append("UNDEFINED"); + } + return b.append(", type=").append(this.getType()) // + .append("]") // + .toString(); + } + + protected String generateToString(String name, T value, Function toHexString) { + return this.generateToString(name, null, value, toHexString); + } + + protected String generateToString(String name, T value) { + return this.generateToString(name, null, value, null); + } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java index 11a846349de..47cfe30253d 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordCycleValue.java @@ -64,21 +64,14 @@ public void updateValue(T component) { @Override public byte[] getValue(OpenemsComponent component) { - switch (this.getType()) { - case FLOAT32: - return ModbusRecordFloat32.toByteArray(this.value); - case FLOAT64: - return ModbusRecordFloat64.toByteArray(this.value); - case STRING16: - return ModbusRecordString16.toByteArray(this.value); - case ENUM16: - case UINT16: - return ModbusRecordUint16.toByteArray(this.value); - case UINT32: - return ModbusRecordUint32.toByteArray(this.value); - } - assert true; - return new byte[0]; + return switch (this.getType()) { + case FLOAT32 -> ModbusRecordFloat32.toByteArray(this.value); + case FLOAT64 -> ModbusRecordFloat64.toByteArray(this.value); + case STRING16 -> ModbusRecordString16.toByteArray(this.value); + case ENUM16, UINT16 -> ModbusRecordUint16.toByteArray(this.value); + case UINT32 -> ModbusRecordUint32.toByteArray(this.value); + case UINT64 -> ModbusRecordUint64.toByteArray(this.value); + }; } @Override diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java index 591bfbd7d9f..61cdce90776 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat32.java @@ -20,7 +20,7 @@ public ModbusRecordFloat32(int offset, String name, Float value) { @Override public String toString() { - return "ModbusRecordFloat32 [value=" + this.value + ", type=" + this.getType() + "]"; + return this.generateToString("ModbusRecordFloat32", this.value); } /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java index 6cb32c747cf..34613a120c0 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordFloat64.java @@ -21,7 +21,7 @@ public ModbusRecordFloat64(int offset, String name, Double value) { @Override public String toString() { - return "ModbusRecordFloat64 [value=" + this.value + ", type=" + this.getType() + "]"; + return this.generateToString("ModbusRecordFloat64", this.value); } /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java index 532e9e07c28..f5cdfb6466b 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordString16.java @@ -20,7 +20,7 @@ public ModbusRecordString16(int offset, String name, String value) { @Override public String toString() { - return "ModbusRecordString16 [value=" + this.value + ", type=" + this.getType() + "]"; + return this.generateToString("ModbusRecordString16", this.value); } /** @@ -30,6 +30,9 @@ public String toString() { * @return the byte array */ public static byte[] toByteArray(String value) { + if (value == null) { + return UNDEFINED_VALUE; + } var result = new byte[BYTE_LENGTH]; var converted = value.getBytes(StandardCharsets.US_ASCII); System.arraycopy(converted, 0, result, 0, Math.min(BYTE_LENGTH, converted.length)); diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java index dc92046984c..3e2e751cde0 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16.java @@ -20,8 +20,7 @@ public ModbusRecordUint16(int offset, String name, Short value) { @Override public String toString() { - return "ModbusRecordUInt16 [value=" + this.value + "/0x" + Integer.toHexString(this.value) + ", type=" - + this.getType() + "]"; + return generateToString("ModbusRecordUInt16", this.value, v -> Integer.toHexString(v)); } /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java index 03fb4a11ecf..e66ceb2641e 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16BlockLength.java @@ -11,8 +11,9 @@ public ModbusRecordUint16BlockLength(int offset, String blockName, short length) @Override public String toString() { - return "ModbusRecordUint16BlockLength [blockName=" + this.blockName + ", value=" + this.value + "/0x" - + Integer.toHexString(this.value) + ", type=" + this.getType() + "]"; + return generateToString("ModbusRecordUint16BlockLength", + b -> b.append("blockName=").append(this.blockName).append(", "), this.value, + v -> Integer.toHexString(v)); } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java index 14780d4fc35..e1e093c1280 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint16Hash.java @@ -11,8 +11,8 @@ public ModbusRecordUint16Hash(int offset, String text) { @Override public String toString() { - return "ModbusRecordUint16Hash [text=" + this.text + ", value=" + this.value + "/0x" - + Integer.toHexString(this.value) + ", type=" + this.getType() + "]"; + return generateToString("ModbusRecordUint16Hash", b -> b.append("text=").append(this.text).append(", "), + this.value, v -> Integer.toHexString(v & 0xffff)); } @Override diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java index 0e0a9b05e4a..23bae440c32 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint32.java @@ -20,8 +20,7 @@ public ModbusRecordUint32(int offset, String name, Integer value) { @Override public String toString() { - return "ModbusRecordUInt32 [value=" + this.value + "/0x" + Integer.toHexString(this.value) + ", type=" - + this.getType() + "]"; + return generateToString("ModbusRecordUInt32", this.value, Integer::toHexString); } /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java new file mode 100644 index 00000000000..87ccc8e779d --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64.java @@ -0,0 +1,59 @@ +package io.openems.edge.common.modbusslave; + +import java.nio.ByteBuffer; + +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.type.TypeUtils; + +public class ModbusRecordUint64 extends ModbusRecordConstant { + + public static final byte[] UNDEFINED_VALUE = { // + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, // + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; + + public static final int BYTE_LENGTH = 8; + + protected final Long value; + + public ModbusRecordUint64(int offset, String name, Long value) { + super(offset, name, ModbusType.UINT64, toByteArray(value)); + this.value = value; + } + + @Override + public String toString() { + return this.generateToString("ModbusRecordUInt64", this.value, Long::toHexString); + } + + /** + * Convert to byte array. + * + * @param value the value + * @return the byte array + */ + public static byte[] toByteArray(long value) { + return ByteBuffer.allocate(BYTE_LENGTH).putLong(value).array(); + } + + /** + * Convert to byte array. + * + * @param value the value + * @return the byte array + */ + public static byte[] toByteArray(Object value) { + if (value == null || value instanceof io.openems.common.types.OptionsEnum + && ((io.openems.common.types.OptionsEnum) value).isUndefined()) { + return UNDEFINED_VALUE; + } + return toByteArray((long) TypeUtils.getAsType(OpenemsType.LONG, value)); + } + + @Override + public String getValueDescription() { + return this.value != null // + ? "\"" + Long.toString(this.value) + "\"" // + : ""; + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java new file mode 100644 index 00000000000..d6018217403 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusRecordUint64Reserved.java @@ -0,0 +1,14 @@ +package io.openems.edge.common.modbusslave; + +public class ModbusRecordUint64Reserved extends ModbusRecordUint64 { + + public ModbusRecordUint64Reserved(int offset) { + super(offset, "Reserved", null); + } + + @Override + public String toString() { + return "ModbusRecordUint64Reserved [type=" + this.getType() + "]"; + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java index da1d57c03af..f358ad8511b 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusSlaveNatureTable.java @@ -63,22 +63,12 @@ public Builder channel(int offset, ChannelId channelId, ModbusType type) { } else { // Channel did not pass filter -> show as Reserved switch (type) { - case FLOAT32: - this.float32Reserved(offset); - break; - case FLOAT64: - this.float64Reserved(offset); - break; - case STRING16: - this.string16Reserved(offset); - break; - case ENUM16: - case UINT16: - this.uint16Reserved(offset); - break; - case UINT32: - this.uint32Reserved(offset); - break; + case FLOAT32 -> this.float32Reserved(offset); + case FLOAT64 -> this.float64Reserved(offset); + case STRING16 -> this.string16Reserved(offset); + case ENUM16, UINT16 -> this.uint16Reserved(offset); + case UINT32 -> this.uint32Reserved(offset); + case UINT64 -> this.uint64Reserved(offset); } } return this; @@ -158,6 +148,18 @@ public Builder uint32Reserved(int offset) { return this; } + /** + * Add a Unsigned Int 64 Reserved value to the {@link ModbusSlaveNatureTable} + * {@link Builder}. + * + * @param offset the address offset + * @return myself + */ + public Builder uint64Reserved(int offset) { + this.add(new ModbusRecordUint64Reserved(offset)); + return this; + } + /** * Add a Float 32 value to the {@link ModbusSlaveNatureTable} {@link Builder}. * diff --git a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java index 5da35991871..df071d14fdc 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java +++ b/io.openems.edge.common/src/io/openems/edge/common/modbusslave/ModbusType.java @@ -4,6 +4,7 @@ public enum ModbusType { ENUM16(1, "enum16"), // UINT16(1, "uint16"), // UINT32(2, "uint32"), // + UINT64(4, "uint64"), // FLOAT32(2, "float32"), // FLOAT64(4, "float64"), // STRING16(16, "string16"); diff --git a/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java b/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java index c6a903df67c..b065cb50c18 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java +++ b/io.openems.edge.common/src/io/openems/edge/common/sum/Sum.java @@ -629,6 +629,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { CONSUMPTION_ACTIVE_ENERGY(Doc.of(OpenemsType.LONG) // .unit(Unit.CUMULATED_WATT_HOURS) // .persistencePriority(PersistencePriority.VERY_HIGH)), // + /** * Is there any Component Info/Warning/Fault that is getting ignored/hidden * because of the 'ignoreStateComponents' configuration setting?. @@ -716,6 +717,7 @@ public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode access .channel(113, ChannelId.ESS_DISCHARGE_POWER, ModbusType.FLOAT32) // .channel(115, ChannelId.GRID_MODE, ModbusType.ENUM16) // .channel(116, ChannelId.GRID_MODE_OFF_GRID_TIME, ModbusType.FLOAT32) // + .channel(118, ChannelId.ESS_CAPACITY, ModbusType.FLOAT32) // .build(); } diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java new file mode 100644 index 00000000000..197fbff8dcf --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat32Test.java @@ -0,0 +1,30 @@ +package io.openems.edge.common.modbusslave; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ModbusRecordFloat32Test { + + @Test + public void testValue() { + var sut = new ModbusRecordFloat32(0, "foo", 1234567.89F); + assertEquals("ModbusRecordFloat32 [value=1234567.9, type=float32]", sut.toString()); + assertEquals("\"1234567.9\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordFloat32(0, "bar", null); + assertEquals("ModbusRecordFloat32 [value=UNDEFINED, type=float32]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordFloat32Reserved(0); + assertEquals("ModbusRecordFloat32Reserved [type=float32]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java new file mode 100644 index 00000000000..70a21900022 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordFloat64Test.java @@ -0,0 +1,30 @@ +package io.openems.edge.common.modbusslave; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ModbusRecordFloat64Test { + + @Test + public void testValue() { + var sut = new ModbusRecordFloat64(0, "foo", 1234567.89); + assertEquals("ModbusRecordFloat64 [value=1234567.89, type=float64]", sut.toString()); + assertEquals("\"1234567.89\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordFloat64(0, "bar", null); + assertEquals("ModbusRecordFloat64 [value=UNDEFINED, type=float64]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordFloat64Reserved(0); + assertEquals("ModbusRecordFloat64Reserved [type=float64]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java new file mode 100644 index 00000000000..d6bea0edd02 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordString16Test.java @@ -0,0 +1,42 @@ +package io.openems.edge.common.modbusslave; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class ModbusRecordString16Test { + + @Test + public void testValue() { + var sut = new ModbusRecordString16(0, "foo", "bar"); + assertEquals("ModbusRecordString16 [value=bar, type=string16]", sut.toString()); + assertEquals("\"bar\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordString16(0, "bar", null); + assertEquals("ModbusRecordString16 [value=UNDEFINED, type=string16]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordString16Reserved(0); + assertEquals("ModbusRecordString16Reserved [type=string16]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testToByteArray() { + assertEquals("[72, 101, 108, 108, 111, "// + + "32, " // + + "87, 111, 114, 108, 100, " // + + "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + Arrays.toString(ModbusRecordString16.toByteArray((Object) "Hello World"))); + assertEquals("[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + Arrays.toString(ModbusRecordString16.toByteArray((Object) null))); + } +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java new file mode 100644 index 00000000000..7752f124cd2 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint16Test.java @@ -0,0 +1,52 @@ +package io.openems.edge.common.modbusslave; + +import static io.openems.common.test.DummyOptionsEnum.UNDEFINED; +import static io.openems.common.test.DummyOptionsEnum.VALUE_1; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class ModbusRecordUint16Test { + + @Test + public void testValue() { + var sut = new ModbusRecordUint16(0, "foo", (short) 12345); + assertEquals("ModbusRecordUInt16 [value=12345/0x3039, type=uint16]", sut.toString()); + assertEquals("\"12345\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordUint16(0, "bar", null); + assertEquals("ModbusRecordUInt16 [value=UNDEFINED, type=uint16]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testOptionsEnum() { + assertEquals("[-1, -1]", Arrays.toString(ModbusRecordUint16.toByteArray(UNDEFINED))); + assertEquals("[0, 1]", Arrays.toString(ModbusRecordUint16.toByteArray(VALUE_1))); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordUint16Reserved(0); + assertEquals("ModbusRecordUint16Reserved [type=uint16]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testBlockLength() { + assertEquals("ModbusRecordUint16BlockLength [blockName=block, value=12345/0x3039, type=uint16]", + new ModbusRecordUint16BlockLength(0, "block", (short) 12345).toString()); + } + + @Test + public void testHash() { + var sut = new ModbusRecordUint16Hash(0, "hash"); + assertEquals("ModbusRecordUint16Hash [text=hash, value=-16114/0xc10e, type=uint16]", sut.toString()); + assertEquals("\"0xc10e\"", sut.getValueDescription()); + } +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java new file mode 100644 index 00000000000..0f5504bf857 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint32Test.java @@ -0,0 +1,40 @@ +package io.openems.edge.common.modbusslave; + +import static io.openems.common.test.DummyOptionsEnum.UNDEFINED; +import static io.openems.common.test.DummyOptionsEnum.VALUE_1; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class ModbusRecordUint32Test { + + @Test + public void testValue() { + var sut = new ModbusRecordUint32(0, "foo", 123456789); + assertEquals("ModbusRecordUInt32 [value=123456789/0x75bcd15, type=uint32]", sut.toString()); + assertEquals("\"123456789\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordUint32(0, "bar", null); + assertEquals("ModbusRecordUInt32 [value=UNDEFINED, type=uint32]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testOptionsEnum() { + assertEquals("[-1, -1, -1, -1]", Arrays.toString(ModbusRecordUint32.toByteArray(UNDEFINED))); + assertEquals("[0, 0, 0, 1]", Arrays.toString(ModbusRecordUint32.toByteArray(VALUE_1))); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordUint32Reserved(0); + assertEquals("ModbusRecordUint32Reserved [type=uint32]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java new file mode 100644 index 00000000000..903a5139236 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/modbusslave/ModbusRecordUint64Test.java @@ -0,0 +1,40 @@ +package io.openems.edge.common.modbusslave; + +import static io.openems.common.test.DummyOptionsEnum.UNDEFINED; +import static io.openems.common.test.DummyOptionsEnum.VALUE_1; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class ModbusRecordUint64Test { + + @Test + public void testValue() { + var sut = new ModbusRecordUint64(0, "foo", 123456789L); + assertEquals("ModbusRecordUInt64 [value=123456789/0x75bcd15, type=uint64]", sut.toString()); + assertEquals("\"123456789\"", sut.getValueDescription()); + } + + @Test + public void testNull() { + var sut = new ModbusRecordUint64(0, "bar", null); + assertEquals("ModbusRecordUInt64 [value=UNDEFINED, type=uint64]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + + @Test + public void testOptionsEnum() { + assertEquals("[-1, -1, -1, -1, -1, -1, -1, -1]", Arrays.toString(ModbusRecordUint64.toByteArray(UNDEFINED))); + assertEquals("[0, 0, 0, 0, 0, 0, 0, 1]", Arrays.toString(ModbusRecordUint64.toByteArray(VALUE_1))); + } + + @Test + public void testReserved() { + var sut = new ModbusRecordUint64Reserved(0); + assertEquals("ModbusRecordUint64Reserved [type=uint64]", sut.toString()); + assertEquals("", sut.getValueDescription()); + } + +} diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java index aeb2e73a5ba..10b7a380eab 100644 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java +++ b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WebsocketClient.java @@ -13,6 +13,7 @@ import io.openems.common.websocket.AbstractWebsocketClient; import io.openems.common.websocket.OnClose; +import io.openems.common.websocket.WsData; public class WebsocketClient extends AbstractWebsocketClient { diff --git a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java b/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java deleted file mode 100644 index 2dfdd355c5b..00000000000 --- a/io.openems.edge.controller.api.backend/src/io/openems/edge/controller/api/backend/WsData.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.openems.edge.controller.api.backend; - -import org.java_websocket.WebSocket; - -public class WsData extends io.openems.common.websocket.WsData { - - public WsData(WebSocket ws) { - super(ws); - } - - @Override - public String toString() { - return "BackendApi.WsData []"; - } - -} diff --git a/io.openems.edge.controller.api.modbus/bnd.bnd b/io.openems.edge.controller.api.modbus/bnd.bnd index 420b544a143..87fd5d5a4a8 100644 --- a/io.openems.edge.controller.api.modbus/bnd.bnd +++ b/io.openems.edge.controller.api.modbus/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.common,\ io.openems.edge.controller.api,\ io.openems.edge.controller.api.common,\ + io.openems.edge.ess.api,\ io.openems.edge.timedata.api,\ io.openems.wrapper.fastexcel diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java index 52e5f67deae..435021672eb 100644 --- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java +++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/AbstractModbusTcpApi.java @@ -529,6 +529,6 @@ public boolean equals(Object other) { * @return component_channelId as String */ public static String formatChannelName(WriteChannel channel) { - return channel.getComponent().alias() + "_" + channel.channelId().name(); + return channel.getComponent().id() + "_" + channel.channelId().name(); } } diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java index 21f2447965c..b86d0a983bd 100644 --- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java +++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/jsonrpc/GetModbusProtocolExportXlsxResponse.java @@ -19,6 +19,7 @@ import io.openems.edge.common.modbusslave.ModbusRecordString16; import io.openems.edge.common.modbusslave.ModbusRecordUint16; import io.openems.edge.common.modbusslave.ModbusRecordUint32; +import io.openems.edge.common.modbusslave.ModbusRecordUint64; import io.openems.edge.common.modbusslave.ModbusType; /** @@ -148,25 +149,14 @@ private static void addUndefinedSheet(Workbook wb) { var nextRow = 2; for (ModbusType modbusType : ModbusType.values()) { - byte[] value = {}; - switch (modbusType) { - case FLOAT32: - value = ModbusRecordFloat32.UNDEFINED_VALUE; - break; - case FLOAT64: - value = ModbusRecordFloat64.UNDEFINED_VALUE; - break; - case STRING16: - value = ModbusRecordString16.UNDEFINED_VALUE; - break; - case ENUM16: - case UINT16: - value = ModbusRecordUint16.UNDEFINED_VALUE; - break; - case UINT32: - value = ModbusRecordUint32.UNDEFINED_VALUE; - break; - } + byte[] value = switch (modbusType) { + case FLOAT32 -> ModbusRecordFloat32.UNDEFINED_VALUE; + case FLOAT64 -> ModbusRecordFloat64.UNDEFINED_VALUE; + case STRING16 -> ModbusRecordString16.UNDEFINED_VALUE; + case ENUM16, UINT16 -> ModbusRecordUint16.UNDEFINED_VALUE; + case UINT32 -> ModbusRecordUint32.UNDEFINED_VALUE; + case UINT64 -> ModbusRecordUint64.UNDEFINED_VALUE; + }; nextRow++; ws.value(nextRow, 0, modbusType.toString()); ws.value(nextRow, 1, byteArrayToString(value)); diff --git a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java index 74055d005b1..6f1a47ad49c 100644 --- a/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java +++ b/io.openems.edge.controller.api.modbus/src/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImpl.java @@ -1,5 +1,8 @@ package io.openems.edge.controller.api.modbus.readwrite; +import static io.openems.edge.common.channel.ChannelId.channelIdCamelToUpper; +import static io.openems.edge.common.channel.ChannelId.channelIdUpperToCamel; + import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -27,20 +30,26 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.PersistencePriority; +import io.openems.common.channel.Unit; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.IntegerReadChannel; import io.openems.edge.common.channel.WriteChannel; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.jsonapi.ComponentJsonApi; import io.openems.edge.common.meta.Meta; +import io.openems.edge.common.modbusslave.ModbusSlave; +import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; +import io.openems.edge.common.modbusslave.ModbusSlaveTable; +import io.openems.edge.common.modbusslave.ModbusType; import io.openems.edge.controller.api.Controller; import io.openems.edge.controller.api.common.Status; import io.openems.edge.controller.api.common.WriteObject; import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi; import io.openems.edge.controller.api.modbus.ModbusTcpApi; +import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateActiveTime; @@ -52,20 +61,23 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class ControllerApiModbusTcpReadWriteImpl extends AbstractModbusTcpApi - implements ControllerApiModbusTcpReadWrite, ModbusTcpApi, Controller, OpenemsComponent, ComponentJsonApi, TimedataProvider { + implements ControllerApiModbusTcpReadWrite, ModbusTcpApi, Controller, OpenemsComponent, ComponentJsonApi, + TimedataProvider, ModbusSlave { private final Logger log = LoggerFactory.getLogger(ControllerApiModbusTcpReadWriteImpl.class); - + private final CalculateActiveTime calculateCumulatedActiveTime = new CalculateActiveTime(this, ControllerApiModbusTcpReadWrite.ChannelId.CUMULATED_ACTIVE_TIME); - + private final CalculateActiveTime calculateCumulatedInactiveTime = new CalculateActiveTime(this, ControllerApiModbusTcpReadWrite.ChannelId.CUMULATED_INACTIVE_TIME); - + private List writeChannels; - + + private List components = new ArrayList<>(); + private boolean isActive = false; - + @Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile Timedata timedata = null; @@ -83,10 +95,12 @@ public class ControllerApiModbusTcpReadWriteImpl extends AbstractModbusTcpApi ) protected void addComponent(OpenemsComponent component) { super.addComponent(component); + this.components.add(component); } protected void removeComponent(OpenemsComponent component) { super.removeComponent(component); + this.components.remove(component); } public ControllerApiModbusTcpReadWriteImpl() { @@ -105,7 +119,6 @@ private void activate(ComponentContext context, Config config) throws ModbusExce new ConfigRecord(config.id(), config.alias(), config.enabled(), this.metaComponent, config.component_ids(), config.apiTimeout(), config.port(), config.maxConcurrentConnections())); this.applyConfig(config); - this.handleTimeDataChannels(); } @Modified @@ -114,7 +127,6 @@ private void modified(ComponentContext context, Config config) throws OpenemsNam new ConfigRecord(config.id(), config.alias(), config.enabled(), this.metaComponent, config.component_ids(), config.apiTimeout(), config.port(), config.maxConcurrentConnections())); this.applyConfig(config); - this.handleTimeDataChannels(); } private void applyConfig(Config config) { @@ -126,12 +138,12 @@ private void applyConfig(Config config) { protected void deactivate() { super.deactivate(); } - + @Override public void run() throws OpenemsNamedException { this.isActive = false; super.run(); - + this.calculateCumulatedActiveTime.update(this.isActive); this.calculateCumulatedInactiveTime.update(!this.isActive); } @@ -166,7 +178,17 @@ private void configUpdate(String targetProperty, String requiredValue) { this.logError(this.log, "ERROR: " + e.getMessage()); } } - + + protected static String getChannelNameUpper(String componentId, + io.openems.edge.common.channel.ChannelId channelId) { + return channelIdCamelToUpper(componentId) + "_" + channelId.name(); + } + + protected static String getChannelNameCamel(String componentId, + io.openems.edge.common.channel.ChannelId channelId) { + return channelIdUpperToCamel(getChannelNameUpper(componentId, channelId)); + } + @Override protected Consumer, WriteObject>> handleWrites() { return entry -> { @@ -174,15 +196,19 @@ protected Consumer, WriteObject>> handleWrites() { WriteChannel channel = entry.getKey(); var writeObject = entry.getValue(); - String channelName = formatChannelName(channel); - var currentChannel = new ChannelIdImpl(channelName, - Doc.of(channel.getType()).persistencePriority(PersistencePriority.HIGH)); - if (!channels().stream().anyMatch(p -> p.channelId().name().equals(currentChannel.name()))) { - addChannel(currentChannel).setNextValue(writeObject.value()); - } else { - channel(currentChannel).setNextValue(writeObject.value()); + var channelNameCamel = getChannelNameCamel(channel.getComponent().id(), channel.channelId()); + + @SuppressWarnings("deprecation") + var logChannel = this._channel(channelNameCamel); + if (logChannel == null) { + var channelNameUpper = getChannelNameUpper(channel.getComponent().id(), channel.channelId()); + var currentChannel = new ChannelIdImpl(channelNameUpper, + Doc.of(channel.getType()).persistencePriority(PersistencePriority.HIGH)); + addChannel(currentChannel); + logChannel = channel(currentChannel); } - this.configUpdate("writeChannels", channel(currentChannel).channelId().id()); + logChannel.setNextValue(writeObject.value()); + this.configUpdate("writeChannels", logChannel.channelId().id()); }; } @@ -205,23 +231,28 @@ protected Runnable handleTimeouts() { public Timedata getTimedata() { return this.timedata; } - - /** - * Checks, if timedata channels are already set. - * If not, they will be created and added to current channels. - */ - protected void handleTimeDataChannels() { - var activeTimeChannel = new ChannelIdImpl("CUMULATED_ACTIVE_TIME", // - Doc.of(OpenemsType.DOUBLE).persistencePriority(PersistencePriority.HIGH)); - var inactiveTimeChannel = new ChannelIdImpl("CUMULATED_INACTIVE_TIME", // - Doc.of(OpenemsType.DOUBLE).persistencePriority(PersistencePriority.HIGH)); - - List timeChannels = Arrays.asList(activeTimeChannel, inactiveTimeChannel); - timeChannels.forEach(channel -> { - if (channels().stream().noneMatch(ch -> ch.channelId().id().equals(channel.id()))) { - addChannel(channel); - } - }); + + protected Integer getChannelValue(String componentId, io.openems.edge.common.channel.ChannelId channelId) { + @SuppressWarnings("deprecation") + var channel = this._channel(getChannelNameCamel(componentId, channelId)); + if (channel == null) { + return null; + } + return ((IntegerReadChannel) channel).value().get(); + } + + @Override + public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { + return new ModbusSlaveTable(// + OpenemsComponent.getModbusSlaveNatureTable(AccessMode.READ_ONLY), + ModbusSlaveNatureTable.of(ControllerApiModbusTcpReadWriteImpl.class, AccessMode.READ_ONLY, 300) + .cycleValue(0, this.id() + "/ Ess0ActivePowerLimit", Unit.WATT, "", ModbusType.FLOAT32, + t -> this.getChannelValue("ess0", + ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)) + .cycleValue(2, this.id() + "/Ess0ReactivePowerLimit", Unit.WATT, "", ModbusType.FLOAT32, + t -> this.getChannelValue("ess0", + ManagedSymmetricEss.ChannelId.SET_REACTIVE_POWER_EQUALS)) + .build()); } } diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java index 58ea9ca90c5..e3b97b5cb79 100644 --- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java +++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java @@ -1,13 +1,19 @@ package io.openems.edge.controller.api.modbus.readwrite; +import static io.openems.edge.controller.api.modbus.readwrite.ControllerApiModbusTcpReadWriteImpl.getChannelNameCamel; +import static io.openems.edge.controller.api.modbus.readwrite.ControllerApiModbusTcpReadWriteImpl.getChannelNameUpper; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; + import org.junit.Test; + import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.common.test.DummyCycle; import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.ManagedSymmetricEss; public class ControllerApiModbusTcpReadWriteImplTest { @@ -28,16 +34,16 @@ public void test() throws Exception { .next(new TestCase()) // ; } - + @Test public void testTimedataChannels() throws Exception { var controller = new ControllerApiModbusTcpReadWriteImpl(); // boolean channelNotFound = controller.channels().stream().noneMatch(// ch -> ch.channelId().id().equals("CumulatedActiveTime") // - || ch.channelId().id().equals("CumulatedInactiveTime")); // - assertFalse(channelNotFound); + || ch.channelId().id().equals("CumulatedInactiveTime")); // + assertFalse(channelNotFound); } - + @Test public void testAddFalseComponents() throws Exception { var controller = new ControllerApiModbusTcpReadWriteImpl(); // @@ -45,4 +51,20 @@ public void testAddFalseComponents() throws Exception { controller.getComponentNoModbusApiFaultChannel().nextProcessImage(); // assertTrue(controller.getComponentNoModbusApiFault().get()); // } + + @Test + public void testGetChannelNameUpper() { + assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", + getChannelNameUpper("ess0", ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)); + assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", + getChannelNameUpper("Ess0", ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)); + } + + @Test + public void testGetChannelNameCamel() { + assertEquals("Ess0SetActivePowerEquals", + getChannelNameCamel("ess0", ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)); + assertEquals("Ess0SetActivePowerEquals", + getChannelNameCamel("Ess0", ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)); + } } diff --git a/io.openems.edge.controller.api.mqtt/bnd.bnd b/io.openems.edge.controller.api.mqtt/bnd.bnd index ae32a25bd42..816ceaf6133 100644 --- a/io.openems.edge.controller.api.mqtt/bnd.bnd +++ b/io.openems.edge.controller.api.mqtt/bnd.bnd @@ -5,8 +5,8 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ - bcpkix;version='1.70',\ - bcprov;version='1.70',\ + bcpkix;version='1.77',\ + bcprov;version='1.77',\ io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ diff --git a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java index a92eca5bba8..d70d2299659 100644 --- a/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java +++ b/io.openems.edge.controller.api.websocket/src/io/openems/edge/controller/api/websocket/WsData.java @@ -142,14 +142,15 @@ public Optional getUser() { } @Override - public String toString() { - String tokenString; - if (this.sessionToken != null) { - tokenString = this.sessionToken.toString(); - } else { - tokenString = "UNKNOWN"; - } - return "WebsocketApi.WsData [sessionToken=" + tokenString + ", user=" + this.user + "]"; + public String toLogString() { + return new StringBuilder("WebsocketApi.WsData [sessionToken=") // + .append(this.sessionToken != null // + ? this.sessionToken.toString() // + : "UNKNOWN") // + .append(", user=") // + .append(this.user) // + .append("]") // + .toString(); } /** diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath b/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath new file mode 100644 index 00000000000..bbfbdbe40e7 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore b/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore new file mode 100644 index 00000000000..c2b941a96de --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.gitignore @@ -0,0 +1,2 @@ +/bin_test/ +/generated/ diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.project b/io.openems.edge.controller.ess.fastfrequencyreserve/.project new file mode 100644 index 00000000000..0140daa493d --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.controller.ess.fastfrequencyreserve + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..896a9a53a53 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd b/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd new file mode 100644 index 00000000000..bee07090a70 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/bnd.bnd @@ -0,0 +1,16 @@ +Bundle-Name: OpenEMS Edge Controller Fast Frequency Reserve +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + Java-WebSocket,\ + io.openems.common,\ + io.openems.edge.common,\ + io.openems.edge.controller.api,\ + io.openems.edge.ess.api,\ + io.openems.edge.meter.api,\ + +-testpath: \ + ${testpath} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md b/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md new file mode 100644 index 00000000000..23c71c2221a --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/doc/statemachine.md @@ -0,0 +1,18 @@ +# State-Machine + +```mermaid +graph TD +start --> Undefined +Undefined --> |condition: Inside set time, task : Charge to maintain soc| PreActivateState +PreActivateState --> |condition: Outside set time, task : do nothing| Undefined +PreActivateState --> |condition: grid freq > freqlimit, task : setpower 0Watt| ActivationTime +ActivationTime --> |condition: 1.7 sec, task : discharge setActivepower| SupportDuration +SupportDuration --> |condition: 30 sec, task : discharge setActivepower| DeactivationTime +DeactivationTime -->|condition: 1.7 sec, task : setpower 0Watt| BufferedTime +BufferedTime --> |condition: 10 sec, task : setpower for 0Watt| BufferedSupportTime +BufferedSupportTime --> |condition: 15 min, Charge to maintain soc| RecoveryTime +RecoveryTime --> PreActivateState +RecoveryTime -->|condition: Outside set time, task : do nothing| Undefined +``` + +View using Mermaid, e.g. https://mermaid-js.github.io/mermaid-live-editor \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc b/io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc new file mode 100644 index 00000000000..028257dcea9 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/readme.adoc @@ -0,0 +1,333 @@ += ESS Fast Frequency Reserve + +== 1.1 Introduction + +In electricity networks, the Fast Frequency Reserve (FFR) controller is providing power available to the system operator within a short interval to meet demand in case of a frequency drop, i.e. in case a generator goes down or there is another disruption to the supply. More details on link:https://en.wikipedia.org/wiki/Operating_reserve[Wikipedia]. + +This controller helps the Energy Storage System (ESS) to provide power, essentially battery discharge, when the measured "Grid frequency" is lower than that of a defined "Frequency limit". + +== 1.2 Controller Parameters + +- **mode**: mode of the controller, On or Off? +- **id**: the id for the controller +- **alias**: Alias for the controller +- **enabled**: enabled or not? +- **meterId**: the id of the meter +- **essId**: the id of the Ess +- **batteryInverterId**: the id of the battery inverter +- **preActivationTime**: A time before the activation time for charging the system(min). +- **schedule**: scheduling of the controller, via JSON see below for the example + +=== 1.2.1 The Example Schedule-JSON + +[source,json] +---- +[ + { + "startTimestamp": 1684792800, + "duration": 86400, + "dischargePowerSetPoint": 92000, + "frequencyLimit": 50000, + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + }, + { + "startTimestamp": 1684879200, + "duration": 86400, + "dischargePowerSetPoint": 6000, + "frequencyLimit": 50000, + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + } +] +---- + +=== 1.2.2 JSON Element details + +- `StartTimeStamp`: When the controller should be activated. +- `Duration`: How long is the controller to be activated? +- `frequency limit`: The controller continuously monitors and checks whether a Frequency limit or threshold is less than the +measured grid frequency. +- `DischargePower`: The Ess discharges from the batteries when generating capacity. +- `activationRunTime`: The time in milliseconds required for the reserve to fully activate. Short(700 ms) or Medium(1000 ms) or Long(1300 ms) activation Time. +- `supportDuration`: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized. Short(5 seconds) or Long(30 seconds) support duration. + +=== 1.2.3 Explanation of the Schedule +The Schedule JSON activates FFR for a full day (86400 seconds or 24 hours) with the following parameters: + +1. Schedule for 23rd May 2023 00:00:00 to 24th May: + - *Threshold frequency:* 49700 mHz + - *Discharge power:* 92000 W + - *Long activation time:* 1.3 seconds + - *Support duration:* 30 seconds + +2. Following Schedule for 24th May 2023 00:00:00 to 25th May: + - *Threshold frequency:* 49700 mHz + - *Discharge power:* 52000 W + - *Long activation time:* 1.3 seconds + - *Support duration:* 30 seconds + + +== 2.1 REST API for updating Fast Frequency Reserve controllers schedule locally + +note : The controller/ App should be activated to update the schedule, which can be done using online monitoring or apache felix. + +== 2.1.1 Overview + +This REST API allows you to update FFR schedule for the specified edge device. The API endpoint takes a JSON payload that specifies the schedule, including the start time, duration, discharge power set point, frequency limit, activation runtime, and support duration. + +== 2.1.2 Endpoint + +- *URL*: http://:8084/jsonrpc +- *Method*: POST +- *Content-Type*: application/json +- *Authorization*: Basic Authentication, username: x, password: owner + +== 2.1.3 Body + +The request body must be a JSON object with the following structure: + +[source,json] +---- +{ + "method": "componentJsonApi", + "params": { + "componentId": "ctrlFastFreqReserve0", + "payload": { + "method": "setActivateFastFreqReserve", + "params": { + "id": "edge0", + "schedule": [ + { + "startTimestamp": 1701871562, + "duration": 999, + "dischargePowerSetPoint": 6000, + "frequencyLimit": 502000, + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + } + ] + } + } + } +} +---- + +== 2.1.4 Request Parameters + +The request body for this REST API call is a JSON object with the following parameters: + +- *method*: The specific method to call within the component. In this case, it is `componentJsonApi`. +- *params*: The parameters associated with the method call. +- *componentId*: The unique identifier of the component that is receiving the request. +- *payload*: The specific data being sent to the component. See below + +=== 2.1.5 Payload Parameters + +Within the payload parameter, there is another JSON object that specifies the details of the activation request: + +- *method*: The method to call within the component to handle the activation request. In this case, it is `setActivateFastFreqReserve`. +- *params*: The parameters associated with the `setActivateFastFreqReserve` method. +- *id*: The unique identifier of the edge device for which the activation is being requested, locally is always `edge0`. +- *schedule*: An array of schedule items that define the activation pattern for the reserve. + +=== 2.1.6 Schedule Item Parameters + +Each schedule item within the schedule array specifies a specific activation period: + +- *startTimestamp*: The unix time stamp when the FFR should start activating. +- *duration*: The duration in milliseconds for which the reserve should remain active. +- *dischargePowerSetPoint*: The maximum power in kilowatts that the reserve should discharge during activation. +- *frequencyLimit*: The frequency threshold below which the reserve should be activated. +- *activationRunTime*: The time in milliseconds required for the reserve to fully activate. +- *supportDuration*: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized. + +=== 2.1.7 Example Python code + +[source,python] +---- +import requests +import json +from requests.auth import HTTPBasicAuth + +# API URL +url = 'http://10.0.10.178:8084/jsonrpc' + +# Authentication +auth = HTTPBasicAuth('x', 'owner') + +# Request headers +headers = { + 'Content-Type': 'application/json', +} + +# Request payload +payload = { + 'jsonrpc': '2.0', + 'id': '00000000-0000-0000-0000-000000000000', + 'method': 'componentJsonApi', + 'params': { + 'componentId': 'ctrlFastFreqReserve0', + 'payload': { + 'method': 'setActivateFastFreqReserve', + 'params': { + 'id': 'edge0', + 'schedule': [ + { + 'startTimestamp': 1701871562, + 'duration': 999, + 'dischargePowerSetPoint': 6000, + 'frequencyLimit': 502000, + 'activationRunTime': 'LONG_ACTIVATION_RUN', + 'supportDuration': 'LONG_SUPPORT_DURATION' + } + ] + } + } + } +} + +# Make the request +response = requests.post(url, auth=auth, headers=headers, json=payload) + +# Print the response +print(response.json()) +---- + + +== 3.1 REST API for Activating Fast Frequency Reserve controllers schedule using Backend +note : The controller/ App should be activated to update the schedule, which can be done using online monitoring or apache felix. + +== 3.1.1 Overview + +This REST API allows you to update FFR for a specific edge device. The API endpoint takes a JSON payload that updates activation schedule, including the start time, duration, discharge power set point, frequency limit, activation runtime, and support duration. + +== 3.1.2 Endpoint + +- *URL*: https://femecon.de/fems/rest/jsonrpc +- *Method*: POST +- *Content-Type*: application/json +- *Authorization*: Basic Authentication, username:foo.com, password:**** + +== 3.1.3 Body + +The request body must be a JSON object with the following structure: + +[source,json] +---- +{ + "method": "edgeRpc", + "params": { + "edgeId": "fems3734", + "payload": { + "method": "componentJsonApi", + "params": { + "componentId": "ctrlFastFreqReserve0", + "payload": { + "method": "setActivateFastFreqReserve", + "params": { + "id": "edge3734", + "schedule": [ + { + "startTimestamp": "1701767477", + "duration": "11000", + "dischargePowerSetPoint": "6000", + "frequencyLimit": "52000", + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + } + ] + } + } + } + } + } +} +---- + +== 3.1.4 Request Parameters + +- *method*: The JSONRPC method to call. In this case, it is `edgeRpc`. +- *params*: The JSONRPC parameters. +- *edgeId*: The ID of the edge device for which to activate the FFR. +- *payload*: The JSONRPC payload. + +== 3.1.5 Payload Parameters + +Within the payload parameter, there is another JSON object that specifies the details of the activation request: + +- *method*: The JSONRPC method to call within the component. In this case, it is `componentJsonApi`. +- *params*: The JSONRPC parameters for the `componentJsonApi` method. +- *componentId*: The ID of the component within which to call the method. In this case, it is `ctrlFastFreqReserve0`. +- *payload*: The JSONRPC payload for the method. + +== 3.1.6 Schedule Item Parameters + +Each schedule item within the schedule array specifies a specific activation period: + +- *startTimestamp*: The unix time stamp in milliseconds when the FFR should start activating. +- *duration*: The duration in milliseconds for which the reserve should remain active. +- *dischargePowerSetPoint*: The maximum power in kilowatts that the reserve should discharge during activation. +- *frequencyLimit*: The frequency threshold below which the reserve should be activated. +- *activationRunTime*: The time in milliseconds required for the reserve to fully activate. +- *supportDuration*: The time in milliseconds for which the reserve should continue providing support after the frequency has stabilized. + +== 3.1.7 Example Python code: + +[source,python] +---- +import requests +import json +from requests.auth import HTTPBasicAuth +import base64 +import os + +url = "https://fenecon.de/fems/rest/jsonrpc" + +username = os.getenv("FENECON_USERNAME") +password = os.getenv("FENECON_PASSWORD") + +headers = { + "Content-Type": "application/json", + "Authorization": "Basic " + base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8") +} + +body = { + "method": "edgeRpc", + "params": { + "edgeId": "fems3734", + "payload": { + "method": "componentJsonApi", + "params": { + "componentId": "ctrlFastFreqReserve0", + "payload": { + "method": "setActivateFastFreqReserve", + "params": { + "id": "edge3734", + "schedule": [ + { + "startTimestamp": "1701767477", + "duration": "11000", + "dischargePowerSetPoint": "6000", + "frequencyLimit": "52000", + "activationRunTime": "LONG_ACTIVATION_RUN", + "supportDuration": "LONG_SUPPORT_DURATION" + } + ] + } + } + } + } + } +} + +response = requests.post(url, headers=headers, data=json.dumps(body)) + +if response.status_code == 200: + print("Fast Frequency Reserve activated successfully") +else: + print("Error activating Fast Frequency Reserve:", response.text) +---- + + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.controller.ess.fastfrequencyreserve[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java new file mode 100644 index 00000000000..2ca1b8b52f3 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/Config.java @@ -0,0 +1,54 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; + +@ObjectClassDefinition(// + name = "Controller Ess Fast Frequency Reserve", // + description = "This Controller helps the energy storage system (ESS) generate capacity, essentially battery discharge. When the measured\n" + + "\"Grid frequency\" is lower than the predefined \"Frequency limit\".") // +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "ctrlFastFreqReserve0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Control Mode", description = "Set the type of control mode.") + ControlMode controlMode() default ControlMode.MANUAL_OFF; + + @AttributeDefinition(name = "Activation Schdule", description = "Schedule for the activation.") + String activationScheduleJson() default "[\n" // + + " {\n" // + + " \"startTimestamp\": 1684879200,\n" // + + " \"duration\": 86400,\n" // + + " \"dischargePowerSetPoint\": 92000,\n" // + + " \"frequencyLimit\": 49500,\n" // + + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\n" // + + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\n" // + + " }\n" // + + "]"; // + + @AttributeDefinition(name = "Ess-ID", description = "ID of Ess device.") + String ess_id(); + + @AttributeDefinition(name = "Grid-Meter-Id", description = "ID of the Grid-Meter.") + String meter_id(); + + @AttributeDefinition(name = "Pre activation time", description = "A time before the activation time for charging the system(min).") + int preActivationTime() default 0; + + @AttributeDefinition(name = "Ess target filter", description = "This is auto-generated by 'Ess-ID'.") + String ess_target() default "(enabled=true)"; + + @AttributeDefinition(name = "Meter target filter", description = "This is auto-generated by 'Grid-Meter-ID'.") + String meter_target() default "(enabled=true)"; + + String webconsole_configurationFactory_nameHint() default "Controller Ess Fast Frequency Reserve [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java new file mode 100644 index 00000000000..efff694c2ee --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserve.java @@ -0,0 +1,283 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import io.openems.common.channel.AccessMode; +import io.openems.common.channel.Level; +import io.openems.common.channel.PersistencePriority; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.channel.WriteChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.controller.api.Controller; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public interface ControllerFastFrequencyReserve extends Controller, OpenemsComponent { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + CONTROL_MODE(Doc.of(ControlMode.values()) // + .initialValue(ControlMode.MANUAL_OFF) // + .text("Configured Control Mode")), // + STATE_MACHINE(Doc.of(State.values()) // + .persistencePriority(PersistencePriority.HIGH)// + .text("Current State of State-Machine")), // + SCHEDULE_PARSE_FAILED(Doc.of(Level.FAULT) // + .text("Unable to parse Schedule")), // + NO_ACTIVE_SETPOINT(Doc.of(OpenemsType.BOOLEAN) // + .text("No active Set-Point given")), // + DISCHARGE_POWER_SET_POINT(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE)), + NO_FREQUENCY_LIMIT(Doc.of(OpenemsType.BOOLEAN) // + .text("No Frequency limit is given")), // + FREQUENCY_LIMIT(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE)), // + NO_START_TIMESTAMP(Doc.of(OpenemsType.BOOLEAN) // + .text("No start timestamp")), // + START_TIMESTAMP(Doc.of(OpenemsType.LONG) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE)), // + NO_DURATION(Doc.of(OpenemsType.BOOLEAN) // + .text("No duration")), // + DURATION(Doc.of(OpenemsType.INTEGER) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE)), // + ACTIVATION_TIME(Doc.of(ActivationTime.values())// + .accessMode(AccessMode.READ_WRITE)), // + SUPPORT_DURATIN(Doc.of(SupportDuration.values())// + .accessMode(AccessMode.READ_WRITE)), + LAST_TRIGGERED_TIME(Doc.of(OpenemsType.STRING) // + .persistencePriority(PersistencePriority.HIGH)// + .accessMode(AccessMode.READ_WRITE) // + .text("Last Triggered time in Human readable form")// + + ); + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + + /** + * Gets the Channel for {@link ChannelId#SUPPORT_DURATIN}. + * + * @return the Channel + */ + public default Channel getSupportDurationChannel() { + return this.channel(ChannelId.SUPPORT_DURATIN); + } + + /** + * Gets the SupportDuration, see {@link ChannelId#SUPPORT_DURATIN}. + * + * @return the Channel {@link Value} + */ + public default SupportDuration getSupportDuration() { + return this.getSupportDurationChannel().value().asEnum(); + } + + /** + * Gets the Channel for {@link ChannelId#ACTIVATION_TIME}. + * + * @return the Channel + */ + public default Channel getActivationTimeChannel() { + return this.channel(ChannelId.ACTIVATION_TIME); + } + + /** + * Gets the ActivationTime, see {@link ChannelId#ACTIVATION_TIME}. + * + * @return the Channel {@link Value} + */ + public default ActivationTime getActivationTime() { + return this.getActivationTimeChannel().value().asEnum(); + } + + /** + * Gets the WriteChannel {@link ChannelId#LAST_TRIGGERED_TIME}. + * + * @return the WriteChannel + */ + public default WriteChannel getLastTriggeredTimeChannel() { + return this.channel(ChannelId.LAST_TRIGGERED_TIME); + } + + /** + * Gets the getLastTriggeredTime, see {@link ChannelId#LAST_TRIGGERED_TIME}. + * + * @return the Channel {@link Value} + */ + public default Value getLastTriggeredTime() { + return this.getLastTriggeredTimeChannel().value(); + } + + /** + * Sets the LastTriggeredTimseStamp, see {@link ChannelId#LAST_TRIGGERED_TIME}. + * + * @param value the value to be set + */ + public default void setLastTriggeredTime(String value) { + this.getLastTriggeredTimeChannel().setNextValue(value); + } + + /** + * Gets the Channel {@link ChannelId#SCHEDULE_PARSE_FAILED}. + * + * @return the Channel + */ + public default StateChannel getScheduleParseFailedChannel() { + return this.channel(ChannelId.SCHEDULE_PARSE_FAILED); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#SCHEDULE_PARSE_FAILED} Channel. + * + * @param value the next value + */ + public default void _setScheduleParseFailed(boolean value) { + this.getScheduleParseFailedChannel().setNextValue(value); + } + + /** + * Gets the Channel {@link ChannelId#DISCHARGE_POWER_SET_POINT}. + * + * @return the Channel + */ + public default WriteChannel getDischargePowerSetPointChannel() { + return this.channel(ChannelId.DISCHARGE_POWER_SET_POINT); + } + + /** + * Gets the getDischargeActivePowerSetPoint, see + * {@link ChannelId#DISCHARGE_POWER_SET_POINT}. + * + * @return the Channel {@link Value} + */ + public default Value getDischargePowerSetPoint() { + return this.getDischargePowerSetPointChannel().value(); + } + + /** + * Gets the WriteChannel {@link ChannelId#FREQUENCY_LIMIT}. + * + * @return the WriteChannel + */ + public default WriteChannel getFrequencyLimitChannel() { + return this.channel(ChannelId.FREQUENCY_LIMIT); + } + + /** + * Gets the getFrequencyLimit, see {@link ChannelId#FREQUENCY_LIMIT}. + * + * @return the Channel {@link Value} + */ + public default Value getFrequencyLimit() { + return this.getFrequencyLimitChannel().value(); + } + + /** + * Gets the WriteChannel {@link ChannelId#DURATION}. + * + * @return the WriteChannel + */ + public default WriteChannel getDurationChannel() { + return this.channel(ChannelId.DURATION); + } + + /* + * Gets the getDuration, see {@link ChannelId#DURATION}. + * + * @return the Channel {@link Value} + */ + public default Value getDuration() { + return this.getDurationChannel().value(); + } + + /** + * Gets the WriteChannel {@link ChannelId#START_TIMESTAMP}. + * + * @return the WriteChannel + */ + public default WriteChannel getStartTimestampChannel() { + return this.channel(ChannelId.START_TIMESTAMP); + } + + /** + * Gets the getStartTimestamp, see {@link ChannelId#START_TIMESTAMP}. + * + * @return the Channel {@link Value} + */ + public default Value getStartTimestamp() { + return this.getStartTimestampChannel().value(); + } + + /** + * Gets the Channel for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getStateMachineChannel() { + return this.channel(ChannelId.STATE_MACHINE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel {@link Value} + */ + public default Value getStateMachine() { + return this.getStateMachineChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE} + * Channel. + * + * @param value the next value + */ + public default void _setStateMachine(State value) { + this.getStateMachineChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#CONTROL_MODE}. + * + * @return the Channel + */ + public default Channel getControlModeChannel() { + return this.channel(ChannelId.CONTROL_MODE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#CONTROL_MODE}. + * + * @return the Channel {@link Value} + */ + public default Value getControlMode() { + return this.getControlModeChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#CONTROL_MODE} + * Channel. + * + * @param value the next value + */ + public default void _setControlMode(ControlMode value) { + this.getControlModeChannel().setNextValue(value); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java new file mode 100644 index 00000000000..5e9bf35f124 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImpl.java @@ -0,0 +1,312 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import java.time.Instant; +import java.time.ZoneId; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; +import io.openems.common.session.Role; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.common.channel.WriteChannel; +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.ComponentManager; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.jsonapi.ComponentJsonApi; +import io.openems.edge.common.jsonapi.EdgeGuards; +import io.openems.edge.common.jsonapi.JsonApiBuilder; +import io.openems.edge.controller.api.Controller; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.Context; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "Controller.Ess.FastFrequencyReserve", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE // +) +public class ControllerFastFrequencyReserveImpl extends AbstractOpenemsComponent + implements ControllerFastFrequencyReserve, Controller, OpenemsComponent, ComponentJsonApi { + + private final Logger log = LoggerFactory.getLogger(ControllerFastFrequencyReserveImpl.class); + private final StateMachine stateMachine = new StateMachine(State.UNDEFINED); + + private Config config = null; + private List schedule = new CopyOnWriteArrayList<>(); + + private static final Function OBTAIN_DICHARGE_POWER = ActivateFastFreqReserveSchedule::dischargePowerSetPoint; + private static final Function OBTAIN_FREQ_LIMIT = ActivateFastFreqReserveSchedule::frequencyLimit; + private static final Function OBTAIN_STARTTIME_STAMP = ActivateFastFreqReserveSchedule::startTimestamp; + private static final Function OBTAIN_DURATION = ActivateFastFreqReserveSchedule::duration; + private static final Function OBTAIN_ACTIVATION_TIME = ActivateFastFreqReserveSchedule::activationRunTime; + private static final Function OBTAIN_SUPPORT_DURATION = ActivateFastFreqReserveSchedule::supportDuration; + + @Reference + private ConfigurationAdmin cm; + + @Reference + private ComponentManager componentManager; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private ManagedSymmetricEss ess; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private ElectricityMeter meter; + + public ControllerFastFrequencyReserveImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + Controller.ChannelId.values(), // + ControllerFastFrequencyReserve.ChannelId.values() // + ); + } + + @Activate + private void activate(ComponentContext context, Config config) throws OpenemsNamedException { + this.config = config; + super.activate(context, config.id(), config.alias(), config.enabled()); + this.updateConfig(); + } + + @Override + public void buildJsonApiRoutes(JsonApiBuilder builder) { + builder.handleRequest(SetActivateFastFreqReserveRequest.METHOD, // + endpoint -> { + endpoint.setGuards(EdgeGuards.roleIsAtleast(Role.OWNER)); + }, call -> { + this.handleSetActivateFastFreqReserveRequest( + SetActivateFastFreqReserveRequest.from(call.getRequest())); + + return new GenericJsonrpcResponseSuccess(call.getRequest().getId(), JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", "recieved") // + .build()); + }); + } + + /** + * Updates the configuration for the component, setting control mode, + * references, and activation schedule. + * + * @throws OpenemsNamedException On Exception. + */ + private void updateConfig() throws OpenemsNamedException { + this._setControlMode(this.config.controlMode()); + + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "ess", // + this.config.ess_id())) { + return; + } + if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "meter", // + this.config.meter_id())) { + return; + } + try { + if (!this.config.activationScheduleJson().trim().isEmpty()) { + final var scheduleElement = JsonUtils.parse(this.config.activationScheduleJson()); + final var scheduleArray = JsonUtils.getAsJsonArray(scheduleElement); + this.applySchedule(scheduleArray); + this._setScheduleParseFailed(false); + } + } catch (IllegalStateException | OpenemsNamedException e) { + this._setScheduleParseFailed(true); + this.logError(this.log, "Unable to parse Schedule: " + e.getMessage()); + } + } + + /** + * Updates the configuration for activating fast frequency reserve based on the + * provided request. + * + * @param request The request containing the schedule information. + */ + private void updateConfig(SetActivateFastFreqReserveRequest request) { + var scheduleString = SetActivateFastFreqReserveRequest.listToString(request.getSchedule()); + OpenemsComponent.updateConfigurationProperty(this.cm, this.servicePid(), "activationScheduleJson", + scheduleString); + } + + private void applySchedule(JsonArray jsonArray) throws OpenemsNamedException { + this.schedule = SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule.from(jsonArray); + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public void run() throws OpenemsNamedException { + switch (this.config.controlMode()) { + case MANUAL_ON -> { + this.getConfigParams(); + this.handleStatemachine(); + } + case MANUAL_OFF -> { + // Do nothing + } + } + this._setControlMode(this.config.controlMode()); + } + + private void getConfigParams() { + + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.DISCHARGE_POWER_SET_POINT, + OBTAIN_DICHARGE_POWER); + final var channelValue = this.getDischargePowerSetPoint(); + + // Avoid calling + if (!channelValue.isDefined()) { + return; + } + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.FREQUENCY_LIMIT, // + OBTAIN_FREQ_LIMIT); + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.DURATION, // + OBTAIN_DURATION); + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.START_TIMESTAMP, // + OBTAIN_STARTTIME_STAMP); + // TODO get it for the activation time and support time, But currently this + // tested for long activation time and long support time, other enums are for + // future + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.ACTIVATION_TIME, // + OBTAIN_ACTIVATION_TIME); + this.setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId.SUPPORT_DURATIN, // + OBTAIN_SUPPORT_DURATION); + } + + /** + * Sets the value for the specified {@code FastFrequencyReserve.ChannelId} based + * on the provided {@code Function}, only if needed. + * + * @param channelId The channel to set the value for. + * @param obtainFunction A {@code Function} to retrieve the corresponding value + * based on the provided schedule entry. + */ + private void setChannelValueIfNeeded(ControllerFastFrequencyReserve.ChannelId channelId, + Function obtainFunction) { + WriteChannel channel = this.channel(channelId); + var setPointFromChannel = channel.value(); + if (setPointFromChannel.isDefined()) { + return; + } + + var currentTime = this.componentManager.getClock().withZone(ZoneId.systemDefault()); + var now = Instant.now(currentTime).getEpochSecond(); + + for (var scheduleEntry : this.schedule) { + var endTime = scheduleEntry.startTimestamp() + scheduleEntry.duration(); + + // Configurable minutes, and convert into seconds + var preActivationTimeBeforeStartTime = this.config.preActivationTime() * 60; + if (now >= scheduleEntry.startTimestamp() - preActivationTimeBeforeStartTime && now <= endTime) { + channel.setNextValue(obtainFunction.apply(scheduleEntry)); + return; + } + } + channel.setNextValue(null); + return; + } + + private void handleStatemachine() { + if (this.checkGridMode()) { + return; + } + + var state = this.stateMachine.getCurrentState(); + this._setStateMachine(state); + + if (!this.areChannelsDefined()) { + return; + } + + var context = new Context(this, // + this.componentManager.getClock(), // + this.ess, // + this.meter, // + this.getStartTimestamp().get(), // + this.getDuration().get(), // + this.getDischargePowerSetPoint().get(), // + this.getFrequencyLimit().get(), // + // TODO if other version of FFR needed, need to test first with the Inverter + // Capabilities + this.getActivationTime(), // + this.getSupportDuration()); + + try { + this.stateMachine.run(context); + } catch (OpenemsNamedException e) { + this.logError(this.log, "StateMachine failed: " + e.getMessage()); + } + } + + /** + * Checks the grid mode and returns a boolean value based on the grid mode + * state. If the grid mode is "ON_GRID" or "UNDEFINED," it returns false and + * logs a warning message when the grid mode is "UNDEFINED." If the grid mode is + * "OFF_GRID," it returns true. + * + * @return true if the grid mode is "OFF_GRID," false otherwise. + */ + private boolean checkGridMode() { + return switch (this.ess.getGridMode()) { + case ON_GRID -> false; + case UNDEFINED -> { + this.logWarn(this.log, "Grid-Mode is [UNDEFINED]"); + yield false; + } + case OFF_GRID -> true; + }; + } + + private boolean areChannelsDefined() { + return Stream.of(// + this.getDischargePowerSetPoint(), // + this.getFrequencyLimit(), // + this.getDuration(), // + this.getStartTimestamp())// + .allMatch(Value::isDefined); + } + + private void handleSetActivateFastFreqReserveRequest(SetActivateFastFreqReserveRequest request) + throws OpenemsNamedException { + this.schedule = request.getSchedule(); + + // get current schedule + var currentSchedule = (String) this.getComponentContext()// + .getProperties()// + .get("activationScheduleJson"); + var currentScheduleArray = JsonUtils.getAsJsonArray(JsonUtils.parse(currentSchedule).getAsJsonArray()); + var currentScheduleList = ActivateFastFreqReserveSchedule.from(currentScheduleArray); + + if (this.schedule.size() == currentScheduleArray.size() && currentScheduleList.equals(this.schedule)) { + return; + } + this.updateConfig(request); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java new file mode 100644 index 00000000000..5d483a3ef44 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ActivationTime.java @@ -0,0 +1,30 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ActivationTime implements OptionsEnum { + SHORT_ACTIVATION_RUN(700, "Short activation time run, 700 in milliseconds"), // + MEDIUM_ACTIVATION_RUN(1000, "Medium activation time run, 1000 in milliseconds"), // + LONG_ACTIVATION_RUN(1300, "Long activation time run, 1300 in milliseconds"); + + private final int value; + private final String name; + + private ActivationTime(int value, String name) { + this.value = value; + this.name = name; + } + + public int getValue() { + return this.value; + } + + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return LONG_ACTIVATION_RUN; + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java new file mode 100644 index 00000000000..e203ad4afdd --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/ControlMode.java @@ -0,0 +1,32 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.enums; + +import io.openems.common.types.OptionsEnum; + +public enum ControlMode implements OptionsEnum { + MANUAL_ON(0, "Manual control for the ON signal, FFR is swtiched on"), // + MANUAL_OFF(1, "Manual control for the OFF signal, FFR is swtiched off") // + ; // + + private final int value; + private final String name; + + private ControlMode(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return MANUAL_OFF; + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java new file mode 100644 index 00000000000..040806aa262 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/enums/SupportDuration.java @@ -0,0 +1,32 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.enums; + +import io.openems.common.types.OptionsEnum; + +public enum SupportDuration implements OptionsEnum { + SHORT_SUPPORT_DURATION(5, "long support duration 5 seconds"), + LONG_SUPPORT_DURATION(30, "long support duration 30 seconds"); + + private final int value; + private final String name; + + private SupportDuration(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return LONG_SUPPORT_DURATION; + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java new file mode 100644 index 00000000000..4c422a365bc --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/jsonrpc/SetActivateFastFreqReserveRequest.java @@ -0,0 +1,228 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; + +/** + * Represents a JSON-RPC Request for 'setActivateFastFreqReserve'. + * + *
+ {
+ 	"jsonrpc": "2.0",
+ 	"id": "UUID",
+ 	"method": "setActivateFastFreqReserve",
+ 	"params": { 		
+ 		"schedule": [{
+ 			"startTimestamp": 1542464697,
+ 			"duration": 900,
+ 			"dischargeActivePowerSetPoint": 92000,
+ 			"frequencyLimit": 49500
+ 			"activationRunTime": "LONG_ACTIVATION_RUN",
+ 			"supportDuration": "LONG_SUPPORT_DURATION"
+ 		}]
+ 	}
+ }
+ * 
+ */ +public class SetActivateFastFreqReserveRequest extends JsonrpcRequest { + + /** + * Create {@link SetActivateFastFreqReserveRequest} from a template + * {@link JsonrpcRequest}. + * + * @param request the template {@link JsonrpcRequest} + * @return the {@link SetActivateFastFreqReserveRequest} + * @throws OpenemsNamedException on parse error + */ + public static SetActivateFastFreqReserveRequest from(JsonrpcRequest request) throws OpenemsNamedException { + final var params = request.getParams(); + final var edgeId = JsonUtils.getAsString(params, "id"); + final var scheduleArray = JsonUtils.getAsJsonArray(params, "schedule"); + final var schedule = ActivateFastFreqReserveSchedule.from(scheduleArray); + return new SetActivateFastFreqReserveRequest(request, edgeId, schedule); + } + + public static final String METHOD = "setActivateFastFreqReserve"; + + private final String edgeId; + private final List schedule; + + public SetActivateFastFreqReserveRequest(String edgeId) { + this(edgeId, new ArrayList<>()); + } + + private SetActivateFastFreqReserveRequest(String edgeId, List schedule) { + super(SetActivateFastFreqReserveRequest.METHOD); + this.edgeId = edgeId; + this.schedule = schedule; + } + + private SetActivateFastFreqReserveRequest(JsonrpcRequest request, String edgeId, + List schedule) { + super(request, SetActivateFastFreqReserveRequest.METHOD); + this.edgeId = edgeId; + this.schedule = schedule; + } + + /** + * Adds a new schedule entry for activating Fast Frequency Reserve. + * + * @param scheduleEntry The schedule entry to be added. + */ + public void addScheduleEntry(ActivateFastFreqReserveSchedule scheduleEntry) { + this.schedule.add(scheduleEntry); + } + + @Override + public JsonObject getParams() { + var schedule = new JsonArray(); + for (var se : this.schedule) { + schedule.add(se.toJson()); + } + return JsonUtils.buildJsonObject() // + .addProperty("id", this.getEdgeId()) // + .add("schedule", schedule) // + .build(); + } + + /** + * Gets the Edge-ID. + * + * @return Edge-ID + */ + public String getEdgeId() { + return this.edgeId; + } + + public List getSchedule() { + return this.schedule; + } + + /** + * Converts a list of ActivateFastFreqReserveSchedule objects to a formatted + * string. + * + * @param scheduleList The list of ActivateFastFreqReserveSchedule objects to + * convert. + * @return A string representation of the schedule list. + * @see ActivateFastFreqReserveSchedule#toString() + */ + public static String listToString(List scheduleList) { + return "["// + + scheduleList.stream()// + .map(ActivateFastFreqReserveSchedule::toString)// + .collect(Collectors.joining(", "))// + + "]"; + } + + public record ActivateFastFreqReserveSchedule(long startTimestamp, int duration, int dischargePowerSetPoint, + int frequencyLimit, ActivationTime activationRunTime, SupportDuration supportDuration) { + + /** + * Builds a list of ActivateFastFreqReserveSchedule from a JsonArray. + * + * @param jsonArray JsonArray + * @return list of {@link ActivateFastFreqReserveSchedule} + * @throws OpenemsNamedException on error + */ + public static List from(JsonArray jsonArray) throws OpenemsNamedException { + List schedule = new ArrayList<>(); + for (var jsonElement : jsonArray) { + var newSchedule = new ActivateFastFreqReserveSchedule( + JsonUtils.getAsLong(jsonElement, "startTimestamp"), // + JsonUtils.getAsInt(jsonElement, "duration"), + JsonUtils.getAsInt(jsonElement, "dischargePowerSetPoint"), + JsonUtils.getAsInt(jsonElement, "frequencyLimit"), + JsonUtils.getAsEnum(ActivationTime.class, jsonElement, "activationRunTime"), + JsonUtils.getAsEnum(SupportDuration.class, jsonElement, "supportDuration")); + + // Check for overlap with existing schedules before adding + if (!overlapsExistingSchedule(schedule, newSchedule)) { + schedule.add(newSchedule); + } + } + + schedule.sort(Comparator.comparing(ActivateFastFreqReserveSchedule::startTimestamp)); + return schedule; + } + + /** + * Checks whether a new schedule overlaps with existing schedules or is an exact + * duplicate. + * + * @param schedule List of existing schedules to compare against + * @param newSchedule The new schedule to check for overlap or duplication + * @return {@code true} if the new schedule overlaps with existing schedules or + * is an exact duplicate, {@code false} otherwise + */ + private static boolean overlapsExistingSchedule(List schedule, + ActivateFastFreqReserveSchedule newSchedule) { + for (ActivateFastFreqReserveSchedule existingSchedule : schedule) { + if (newSchedule.equals(existingSchedule)) { + // duplicate found + return true; + } + // Check for overlap + if (newSchedule.startTimestamp < (existingSchedule.startTimestamp + existingSchedule.duration) + && (newSchedule.startTimestamp + newSchedule.duration) > existingSchedule.startTimestamp) { + return true; + } + } + // No overlap or exact duplicate found + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + var that = (ActivateFastFreqReserveSchedule) o; + return this.startTimestamp == that.startTimestamp // + && this.duration == that.duration // + && this.dischargePowerSetPoint == that.dischargePowerSetPoint // + && this.frequencyLimit == that.frequencyLimit // + && this.activationRunTime.equals(that.activationRunTime) // + && this.supportDuration.equals(that.supportDuration); + } + + @Override + public String toString() { + return String.format( + "{\"startTimestamp\":%d, \"duration\":%d, \"dischargePowerSetPoint\":%d, \"frequencyLimit\":%d, \"activationRunTime\":\"%s\", \"supportDuration\":\"%s\"}", + this.startTimestamp, this.duration, this.dischargePowerSetPoint, this.frequencyLimit, + this.activationRunTime, this.supportDuration); + } + + /** + * Converts this ActivateFastFreqReserveSchedule object to a JsonObject. + * + * @return A JsonObject representing this schedule, where each field is mapped + * to a corresponding property with its value. + */ + public JsonObject toJson() { + return JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", this.startTimestamp()) // + .addProperty("duration", this.duration()) // + .addProperty("dischargeActivePowerSetPoint", this.dischargePowerSetPoint()) // + .addProperty("frequencyLimit", this.frequencyLimit()) // + .addProperty("activationRunTime", this.activationRunTime()) // + .addProperty("supportDuration", this.supportDuration()) // + .build(); + } + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java new file mode 100644 index 00000000000..bdf301e2827 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java @@ -0,0 +1,106 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.EnumUtils; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class ActivationTimeHandler extends StateHandler { + + private static final int ZERO_WATT_POWER = 0; // [0 W] + + protected Instant dipDetectedStartTime; + protected ActivationTimeState activationTimeState; + + private static enum SubState { + INSIDE_TIME_FRAME, // + HANDLE_WAITING_FREQ_DIP, // + HANDLE_FREQ_DIP, // + FINISH_ACTIVATION + } + + protected static record ActivationTimeState(SubState subState, Instant lastChange) { + } + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + this.activationTimeState = new ActivationTimeState(SubState.INSIDE_TIME_FRAME, Instant.now(context.clock)); + } + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + final var nextSubState = this.getNextSubState(context); + if (nextSubState != this.activationTimeState.subState) { + this.activationTimeState = new ActivationTimeState(nextSubState, Instant.now(context.clock)); + } + if (nextSubState == SubState.FINISH_ACTIVATION) { + return State.SUPPORT_DURATION; + } + + return State.ACTIVATION_TIME; + } + + private SubState getNextSubState(Context context) throws OpenemsNamedException { + return switch (this.activationTimeState.subState) { + case INSIDE_TIME_FRAME -> + this.isInsideTimeFrame(context) ? SubState.FINISH_ACTIVATION : SubState.HANDLE_WAITING_FREQ_DIP; + case HANDLE_WAITING_FREQ_DIP -> { + if (this.isFrequencyDipped(context)) { + context.ess.setActivePowerEquals(context.dischargePower); + var time = Instant.now(context.clock); + this.clockActivationTime(context, time); + this.dipDetectedStartTime = time; + yield SubState.HANDLE_FREQ_DIP; + } + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + yield SubState.HANDLE_WAITING_FREQ_DIP; + } + case HANDLE_FREQ_DIP -> { + context.ess.setActivePowerEquals(context.dischargePower); + var activationExpirationTime = Duration.between(this.dipDetectedStartTime, Instant.now(context.clock))// + .toMillis(); // + if (activationExpirationTime >= context.activationRunTime.getValue()) { + yield SubState.FINISH_ACTIVATION; + } + yield SubState.HANDLE_FREQ_DIP; + } + case FINISH_ACTIVATION -> SubState.FINISH_ACTIVATION; + }; + } + + /** + * Clocks the activation time and sets it in the context. + * + * @param context the context. + * @param time time in instant + */ + private void clockActivationTime(Context context, Instant time) { + context.setCycleStart(time); + } + + private boolean isFrequencyDipped(Context context) throws OpenemsException { + var meterFrequency = context.meter.getFrequency(); + if (!meterFrequency.isDefined()) { + throw new OpenemsException("meter has no frequency channel defined."); + } + return (meterFrequency.get() < context.freqLimit); + } + + private boolean isInsideTimeFrame(Context context) { + final var now = Instant.now(context.clock).getEpochSecond(); + final var startTimestamp = context.startTimestamp; + final var duration = context.duration; + return now >= startTimestamp + duration; + } + + @Override + protected String debugLog() { + return State.ACTIVATION_TIME.asCamelCase() + "-" + + EnumUtils.nameAsCamelCase(this.activationTimeState.subState()); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java new file mode 100644 index 00000000000..23d9ae25b3d --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/BufferedTimeBeforeRecoveryHandler.java @@ -0,0 +1,103 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.EnumUtils; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.ess.power.api.Phase; +import io.openems.edge.ess.power.api.Pwr; + +public class BufferedTimeBeforeRecoveryHandler extends StateHandler { + + private static final int ZERO_WATT_POWER = 0; + private static final int BUFFER_DURATION_THRESHOLD_SECONDS = 15; // [s] + private static final int RECOVERY_DURATION_THRESHOLD_MINUTES = 4; // [minute] + private static final double EIGHTEENX_PERCENT_OF_MAX_POWER = 0.18; // [%] + + protected Instant bufferedTimeBeforeRecoveryStartTime = Instant.MIN; + + protected static record BufferedTimeBeforeRecoveryState(SubState subState, Instant lastChange) { + } + + protected BufferedTimeBeforeRecoveryState bufferedTimeBeforeRecoveryState; + + private static enum SubState { + HOLD_BUFFERED_TIME_BEFORE_RECOVERY, // + BUFFERED_TIME_RECOVERY, // + FINISH_BUFFERED_TIME_BEFORE_RECOVERY + } + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + final var now = Instant.now(context.clock); + this.bufferedTimeBeforeRecoveryStartTime = now; + this.bufferedTimeBeforeRecoveryState = new BufferedTimeBeforeRecoveryState( + SubState.HOLD_BUFFERED_TIME_BEFORE_RECOVERY, now); + + } + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + final var nextSubState = this.getNextSubState(context); + + if (nextSubState != this.bufferedTimeBeforeRecoveryState.subState) { + this.bufferedTimeBeforeRecoveryState = new BufferedTimeBeforeRecoveryState(nextSubState, + Instant.now(context.clock)); + } + + if (nextSubState == SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY) { + return State.RECOVERY_TIME; + } + + return State.BUFFERED_TIME_BEFORE_RECOVERY; + } + + private SubState getNextSubState(Context context) throws OpenemsNamedException { + return switch (this.bufferedTimeBeforeRecoveryState.subState) { + case HOLD_BUFFERED_TIME_BEFORE_RECOVERY -> { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + var bufferedDurationExpiration = this.calculateBufferedDurationExpiration(context); + if (bufferedDurationExpiration >= BUFFER_DURATION_THRESHOLD_SECONDS) { + yield SubState.BUFFERED_TIME_RECOVERY; + } + yield SubState.HOLD_BUFFERED_TIME_BEFORE_RECOVERY; + } + case BUFFERED_TIME_RECOVERY -> { + var minPowerEss = this.calculateMinPower(context.ess); + context.ess.setActivePowerEquals(minPowerEss); + var bufferedRecoveryExpiration = this.calculateBufferedRecoveryExpiration(context); + if (bufferedRecoveryExpiration >= RECOVERY_DURATION_THRESHOLD_MINUTES) { + yield SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY; + } + yield SubState.BUFFERED_TIME_RECOVERY; + } + case FINISH_BUFFERED_TIME_BEFORE_RECOVERY -> SubState.FINISH_BUFFERED_TIME_BEFORE_RECOVERY; + }; + } + + private long calculateBufferedDurationExpiration(Context context) { + return Duration.between(this.bufferedTimeBeforeRecoveryStartTime, Instant.now(context.clock))// + .toSeconds(); + } + + private long calculateBufferedRecoveryExpiration(Context context) { + return Duration// + .between(context.getCycleStart(), Instant.now(context.clock))// + .toMinutes(); + } + + private int calculateMinPower(ManagedSymmetricEss ess) { + return (int) (ess.getPower().getMinPower(ess, Phase.ALL, Pwr.ACTIVE) * EIGHTEENX_PERCENT_OF_MAX_POWER); + } + + @Override + protected String debugLog() { + return State.BUFFERED_TIME_BEFORE_RECOVERY.asCamelCase() + "-" + + EnumUtils.nameAsCamelCase(this.bufferedTimeBeforeRecoveryState.subState()); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java new file mode 100644 index 00000000000..cbae7c9ec6e --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/Context.java @@ -0,0 +1,64 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import io.openems.edge.common.statemachine.AbstractContext; +import io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve; +import io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserveImpl; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; + +public class Context extends AbstractContext { + + protected final Clock clock; + protected final int dischargePower; + protected final long startTimestamp; + protected final int duration; + protected final int freqLimit; + protected final ActivationTime activationRunTime; + protected final SupportDuration supportDuration; + protected final ControllerFastFrequencyReserve parentController; + protected final ManagedSymmetricEss ess; + protected final ElectricityMeter meter; + + protected static Instant _cycleStart; + + public Context(ControllerFastFrequencyReserve fastFrequencyReserve, // + Clock clock, // + ManagedSymmetricEss ess, // + ElectricityMeter meter, // + long startTimestamp, // + int duration, // + int dischargePower, // + int freqLimit, // + ActivationTime activationRunTime, // + SupportDuration supportDuration) { + this.clock = clock; + this.parentController = fastFrequencyReserve; + this.startTimestamp = startTimestamp; + this.duration = duration; + this.dischargePower = dischargePower; + this.freqLimit = freqLimit; + this.ess = ess; + this.meter = meter; + this.activationRunTime = activationRunTime; + this.supportDuration = supportDuration; + } + + public Instant getCycleStart() { + return _cycleStart; + } + + public void setCycleStart(Instant cycleStart) { + LocalDateTime lastTriggered = LocalDateTime.ofInstant(cycleStart, ZoneId.systemDefault()); + String formattedDateTime = lastTriggered.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + this.parentController.setLastTriggeredTime(formattedDateTime); + _cycleStart = cycleStart; + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java new file mode 100644 index 00000000000..aa01cbddbbb --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/DeactivationTimeHandler.java @@ -0,0 +1,81 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.EnumUtils; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class DeactivationTimeHandler extends StateHandler { + + private static final int ZERO_WATT_POWER = 0; //[0 W] + protected Instant deactivationStateStartTime; + + private static enum SubState { + HOLD_DEACTIVATION, // + FINISH_DEACTIVATION_DURATION + } + + protected static record DeactivationTimeState(SubState subState, Instant lastChange) { + } + + protected DeactivationTimeState deactivationTimeState; + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + this.deactivationStateStartTime = Instant.now(context.clock); + this.deactivationTimeState = new DeactivationTimeState(SubState.HOLD_DEACTIVATION, Instant.now(context.clock)); + } + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + var nextSubState = this.getNextSubState(context); + if (nextSubState != this.deactivationTimeState.subState) { + this.deactivationTimeState = new DeactivationTimeState(nextSubState, Instant.now(context.clock)); + } + if (nextSubState == SubState.FINISH_DEACTIVATION_DURATION) { + return State.BUFFERED_TIME_BEFORE_RECOVERY; + } + return State.DEACTIVATION_TIME; + } + + private SubState getNextSubState(Context context) throws OpenemsNamedException { + return switch (this.deactivationTimeState.subState) { + + case HOLD_DEACTIVATION -> { + context.ess.setActivePowerEquals(ZERO_WATT_POWER); + var deactivationDurationExpiration = this.calculateDeactivationDurationExpiration(context); + if (deactivationDurationExpiration >= context.activationRunTime.getValue()) { + yield SubState.FINISH_DEACTIVATION_DURATION; + } + yield SubState.HOLD_DEACTIVATION; + } + case FINISH_DEACTIVATION_DURATION -> SubState.FINISH_DEACTIVATION_DURATION; + }; + } + + /** + * Calculates the expiration duration for the deactivation state. The expiration + * duration is the time elapsed between the deactivation state start time and + * the current time, measured in milliseconds. + * + * @param context the Context + * @return The expiration duration in milliseconds. + */ + private long calculateDeactivationDurationExpiration(Context context) { + return Duration.between(// + this.deactivationStateStartTime, // + Instant.now(context.clock))// + .toMillis(); + } + + @Override + protected String debugLog() { + return State.DEACTIVATION_TIME.asCamelCase() + "-" + + EnumUtils.nameAsCamelCase(this.deactivationTimeState.subState()); + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java new file mode 100644 index 00000000000..86b9d62083e --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/PreActivationHandler.java @@ -0,0 +1,53 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.ZonedDateTime; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.ess.power.api.Phase; +import io.openems.edge.ess.power.api.Pwr; + +public class PreActivationHandler extends StateHandler { + + private static final double EIGHTEENX_PERCENT_OF_MAX_POWER = 0.18; // [%] + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + var ess = context.ess; + int minPowerEss = this.calculateMinPower(ess); + + if (this.isActivationTime(context)) { + return State.ACTIVATION_TIME; + } else { + ess.setActivePowerEquals(minPowerEss); + return State.PRE_ACTIVATION_STATE; + } + } + + /** + * Calculates 18% of the minimum power of the given ess. + * + * @param ess The managed symmetric ess. + * @return 18% of the minimum power of the ess. + */ + private int calculateMinPower(ManagedSymmetricEss ess) { + return (int) (ess.getPower().getMinPower(ess, Phase.ALL, Pwr.ACTIVE) * EIGHTEENX_PERCENT_OF_MAX_POWER); + } + + /** + * Checks if the current time, as adjusted by the component manager's clock, is + * within the activation time window. + * + * + * @param context the context + * @return {@code true} if the current time is within the activation time + * window, {@code false} otherwise. + */ + private boolean isActivationTime(Context context) { + var currentEpochSecond = ZonedDateTime.now(context.clock).toEpochSecond(); + return currentEpochSecond >= context.startTimestamp + && currentEpochSecond <= context.startTimestamp + context.duration; + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java new file mode 100644 index 00000000000..d073c55f9cf --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/RecoveryTimeHandler.java @@ -0,0 +1,29 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; + +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class RecoveryTimeHandler extends StateHandler { + + public static final int RECOVERY_DURATION_SECONDS = 15 * 60; + + @Override + protected State runAndGetNextState(Context context) { + if (this.isItWithinDuration(context)) { + return State.ACTIVATION_TIME; + } + return State.RECOVERY_TIME; + } + + private boolean isItWithinDuration(Context context) { + var now = Instant.now(context.clock).getEpochSecond(); + var expiration = Duration// + .between(context.getCycleStart(), ZonedDateTime.now(context.clock))// + .toSeconds(); + return expiration > RECOVERY_DURATION_SECONDS || now >= context.startTimestamp + context.duration; + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java new file mode 100644 index 00000000000..c044760a26f --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/StateMachine.java @@ -0,0 +1,62 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import io.openems.common.types.OptionsEnum; +import io.openems.edge.common.statemachine.AbstractStateMachine; +import io.openems.edge.common.statemachine.StateHandler; + +public class StateMachine extends AbstractStateMachine { + + public enum State implements io.openems.edge.common.statemachine.State, OptionsEnum { + UNDEFINED(-1), // + PRE_ACTIVATION_STATE(10), // + ACTIVATION_TIME(20), // + SUPPORT_DURATION(30), // + DEACTIVATION_TIME(40), // + BUFFERED_TIME_BEFORE_RECOVERY(50), // + RECOVERY_TIME(60);// + + private final int value; + + private State(int value) { + this.value = value; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + + @Override + public String getName() { + return this.name(); + } + + @Override + public State[] getStates() { + return State.values(); + } + + } + + public StateMachine(State initialState) { + super(initialState); + } + + @Override + public StateHandler getStateHandler(State state) { + return switch (state) { + case ACTIVATION_TIME -> new ActivationTimeHandler(); + case BUFFERED_TIME_BEFORE_RECOVERY -> new BufferedTimeBeforeRecoveryHandler(); + case DEACTIVATION_TIME -> new DeactivationTimeHandler(); + case PRE_ACTIVATION_STATE -> new PreActivationHandler(); + case RECOVERY_TIME -> new RecoveryTimeHandler(); + case SUPPORT_DURATION -> new SupportDurationTimeHandler(); + case UNDEFINED -> new UndefinedHandler(); + }; + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java new file mode 100644 index 00000000000..a5d5131c8e0 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/SupportDurationTimeHandler.java @@ -0,0 +1,64 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class SupportDurationTimeHandler extends StateHandler { + + protected LocalDateTime supportDurationStartTime; + + private static enum SubState { + HOLD_SUPPORT, // + FINISH_SUPPORT_DURATION + } + + protected static record SupportDurationTimeState(SubState subState, Instant lastChange) { + } + + protected SupportDurationTimeState supportDurationTimeState; + + @Override + protected void onEntry(Context context) throws OpenemsNamedException { + context.ess.setActivePowerEquals(context.dischargePower); + this.supportDurationStartTime = LocalDateTime.now(context.clock); + this.supportDurationTimeState = new SupportDurationTimeState(SubState.HOLD_SUPPORT, Instant.now(context.clock)); + } + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + final var nextSubState = this.getNextSubState(context); + if (nextSubState != this.supportDurationTimeState.subState) { + this.supportDurationTimeState = new SupportDurationTimeState(nextSubState, Instant.now(context.clock)); + } + if (nextSubState == SubState.FINISH_SUPPORT_DURATION) { + return State.DEACTIVATION_TIME; + } + return State.SUPPORT_DURATION; + } + + private SubState getNextSubState(Context context) throws OpenemsNamedException { + return switch (this.supportDurationTimeState.subState) { + case HOLD_SUPPORT -> { + context.ess.setActivePowerEquals(context.dischargePower); + var supportDurationExpiration = this.calculateSupportDurationExpiration(context); + if (supportDurationExpiration >= context.supportDuration.getValue()) { + yield SubState.FINISH_SUPPORT_DURATION; + } + yield SubState.HOLD_SUPPORT; + } + case FINISH_SUPPORT_DURATION -> SubState.FINISH_SUPPORT_DURATION; + }; + } + + private long calculateSupportDurationExpiration(Context context) { + return Duration.between(// + this.supportDurationStartTime, // + LocalDateTime.now(context.clock))// + .getSeconds(); + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java new file mode 100644 index 00000000000..513a119914a --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/UndefinedHandler.java @@ -0,0 +1,40 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve.statemachine; + +import java.time.ZonedDateTime; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.statemachine.StateHandler; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; + +public class UndefinedHandler extends StateHandler { + + public static final int FIFTEEN_MINUTES_IN_SECONDS = 15 * 60; // 15 minutes in seconds + + @Override + protected State runAndGetNextState(Context context) throws OpenemsNamedException { + if (this.isPreActivationTime(context)) { + return State.PRE_ACTIVATION_STATE; + } else { + return State.UNDEFINED; + } + } + + /** + * Checks if the current time, as adjusted by the component manager's clock, is + * within the pre-activation time window. pre-activation time window is 15 + * minutes before the start time. + * + * @param context the context + * @return {@code true} if the current time is within the activation time + * window, {@code false} otherwise. + */ + private boolean isPreActivationTime(Context context) { + var currentDateTime = ZonedDateTime.now(context.clock); + var currentEpochSecond = currentDateTime.toEpochSecond(); + if (currentEpochSecond >= context.startTimestamp - FIFTEEN_MINUTES_IN_SECONDS + && currentEpochSecond <= context.startTimestamp + context.duration) { + return true; + } + return false; + } +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/.gitignore b/io.openems.edge.controller.ess.fastfrequencyreserve/test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java new file mode 100644 index 00000000000..c0db3e9a28d --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/JsonRpcTest.java @@ -0,0 +1,68 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import java.net.URI; +//import org.junit.Test; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule; + +/** + * This Test demonstrates the usage of the OpenEMS Backend-to-Backend API + * interface. To start the tests make sure to start OpenEMS Backend and activate + * the B2bWebsocket component via Apache Felix. Afterwards uncomment the "@Test" + * annotations below and execute the Tests. + */ +public class JsonRpcTest { + + private static final String URI = "ws://localhost:8076"; + private static final String USERNAME = "user"; + private static final String PASSWORD = "password"; + + private static TestClient prepareTestClient() throws URISyntaxException, InterruptedException { + Map httpHeaders = new HashMap<>(); + var auth = new String( + Base64.getEncoder().encode((JsonRpcTest.USERNAME + ":" + JsonRpcTest.PASSWORD).getBytes()), + StandardCharsets.UTF_8); + httpHeaders.put("Authorization", "Basic " + auth); + var client = new TestClient(new URI(JsonRpcTest.URI), httpHeaders); + client.startBlocking(); + return client; + } + + /** + * Tests the activation of Fast Frequency Reserve schedule. + * + * @throws URISyntaxException String could not be parsed as a URI reference. + * @throws InterruptedException interrupted exception. + */ + // @Test + public void testActivateFastFreqReserveSchedule() throws URISyntaxException, InterruptedException { + var client = JsonRpcTest.prepareTestClient(); + + var request = new SetActivateFastFreqReserveRequest("edge0"); + var now = System.currentTimeMillis() / 1000; + ActivateFastFreqReserveSchedule newEntry = new ActivateFastFreqReserveSchedule(// + now, // + 1000, // + 92000, // + 50000, // + ActivationTime.LONG_ACTIVATION_RUN, // + SupportDuration.LONG_SUPPORT_DURATION); + request.addScheduleEntry(newEntry); + + try { + var responseFuture = client.sendRequest(request); + System.out.println(responseFuture.get().toString()); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java new file mode 100644 index 00000000000..efc5087e1f3 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java @@ -0,0 +1,107 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.utils.ConfigUtils; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + private String essId; + private String meterId; + private ControlMode mode; + private String activationScheduleJson; + private int preActivationTime; + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setEssId(String essId) { + this.essId = essId; + return this; + } + + public Builder setMeterId(String meterId) { + this.meterId = meterId; + return this; + } + + public Builder setMode(ControlMode mode) { + this.mode = mode; + return this; + } + + public Builder setactivationScheduleJson(String schedule) { + this.activationScheduleJson = schedule; + return this; + } + + public Builder setPreActivationTime(int preActivationTime) { + this.preActivationTime = preActivationTime; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String ess_id() { + return this.builder.essId; + } + + @Override + public String meter_id() { + return this.builder.meterId; + } + + @Override + public String ess_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); + } + + @Override + public String meter_target() { + return ConfigUtils.generateReferenceTargetFilter(this.id(), this.meter_id()); + } + + @Override + public ControlMode controlMode() { + return this.builder.mode; + } + + @Override + public String activationScheduleJson() { + return this.builder.activationScheduleJson; + } + + @Override + public int preActivationTime() { + return this.builder.preActivationTime; + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest.java new file mode 100644 index 00000000000..942975af03b --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest.java @@ -0,0 +1,295 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.test.TimeLeapClock; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.common.sum.GridMode; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.test.DummyElectricityMeter; + +public class MyControllerTest { + + private static final String CTRL_ID = "ctrl0"; + private static final String ESS_ID = "ess0"; + private static final String METER_ID = "meter0"; + + private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID); + private static final DummyElectricityMeter METER = new DummyElectricityMeter(METER_ID); + + private static final ChannelAddress METER_FREQUENCY = new ChannelAddress(METER_ID, "Frequency"); + private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); + private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "SetActivePowerEquals"); + + @Test + public void testFfrController() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); + + final var cm = new DummyComponentManager(clock); + + new ControllerTest(new ControllerFastFrequencyReserveImpl()) // + + .addReference("componentManager", cm) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", ESS // + .withGridMode(GridMode.ON_GRID)// + .withMaxApparentPower(92000)// + .withAllowedChargePower(-92000)// + .withAllowedDischargePower(92000)) // + .addReference("meter", METER) // + .activate(MyConfig.create() // + .setEssId(ESS_ID) // + .setId(CTRL_ID) // + .setMeterId(METER_ID) // + .setMode(ControlMode.MANUAL_ON) // + .setPreActivationTime(15)// + .setactivationScheduleJson(JsonUtils.buildJsonArray() // + .add(JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", 1689242400) // Thu Jul 13 2023 10:00:00 GMT+0000 + .addProperty("duration", 86400) // + .addProperty("dischargePowerSetPoint", 92000) // + .addProperty("frequencyLimit", 49500) // + .addProperty("activationRunTime", ActivationTime.LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", SupportDuration.LONG_SUPPORT_DURATION) // + .build()) + .add(JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", 1689331500) // Fri Jul 14 2023 10:45:00 GMT+0000 + .addProperty("duration", 86400) // + .addProperty("dischargePowerSetPoint", 92000) // + .addProperty("frequencyLimit", 49500) // + .addProperty("activationRunTime", ActivationTime.LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", SupportDuration.LONG_SUPPORT_DURATION) // + .build()) + .build()// + .toString())// + .build()) + .next(new TestCase("1") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.UNDEFINED) // + .output(ESS_ACTIVE_POWER, null))// + .next(new TestCase("2") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.UNDEFINED) // + .output(ESS_ACTIVE_POWER, null))// + .next(new TestCase("3") // + .timeleap(clock, 1, ChronoUnit.HOURS) // + .input(METER_FREQUENCY, 50000))// + .next(new TestCase("4"))// + .next(new TestCase("5") // + .output(STATE_MACHINE, State.UNDEFINED)) + .next(new TestCase("6") // + .timeleap(clock, 10, ChronoUnit.MINUTES)// + .output(STATE_MACHINE, State.PRE_ACTIVATION_STATE)) + .next(new TestCase("7") // + .timeleap(clock, 10, ChronoUnit.MINUTES)) + .next(new TestCase("8") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("9") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("10") // + .input(METER_FREQUENCY, 49400)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output(ESS_ACTIVE_POWER, 92000)) + .next(new TestCase("11") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("12") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output(ESS_ACTIVE_POWER, 92000)) // + .next(new TestCase("13") // + .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.SECONDS) // + .output(STATE_MACHINE, State.SUPPORT_DURATION)) + .next(new TestCase("14") // + .output(STATE_MACHINE, State.DEACTIVATION_TIME).output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("15") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("16") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // + .output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("17") // + .timeleap(clock, 16, ChronoUnit.SECONDS) // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("18") // + .timeleap(clock, 12, ChronoUnit.MINUTES)) // + .next(new TestCase("19") // + .output(STATE_MACHINE, State.RECOVERY_TIME) // + .output(ESS_ACTIVE_POWER, -16560)) + .next(new TestCase("20") // + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("21") // + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("22") // + .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.MINUTES)) + .next(new TestCase("23") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("24") // + .timeleap(clock, 1, ChronoUnit.DAYS)) // + .next(new TestCase("25") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("26") // + .timeleap(clock, 4, ChronoUnit.HOURS)) // + .next(new TestCase("27") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("28")// + .input(METER_FREQUENCY, 49400)) + .next(new TestCase("29") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("30") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output(ESS_ACTIVE_POWER, 92000)) // + .next(new TestCase("31") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("32") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output(ESS_ACTIVE_POWER, 92000)) // + .next(new TestCase("33") // + .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.SECONDS) // + .output(STATE_MACHINE, State.SUPPORT_DURATION)) + .next(new TestCase("34") // + .output(STATE_MACHINE, State.DEACTIVATION_TIME).output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("35") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("36") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // + .output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("37") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("38") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("39") // + .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.MINUTES)) + .next(new TestCase("40") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("41") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("42") // + .timeleap(clock, 1, ChronoUnit.DAYS)); + } + + @Test + public void testInvalidJsonSchedule() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); + + final var cm = new DummyComponentManager(clock); + + new ControllerTest(new ControllerFastFrequencyReserveImpl()) // + .addReference("componentManager", cm) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", ESS // + .withGridMode(GridMode.ON_GRID)// + .withMaxApparentPower(92000)// + .withAllowedChargePower(-92000)// + .withAllowedDischargePower(92000)) // + .addReference("meter", METER) // + .activate(MyConfig.create() // + .setEssId(ESS_ID) // + .setId(CTRL_ID) // + .setMeterId(METER_ID) // + .setMode(ControlMode.MANUAL_ON) // + .setPreActivationTime(15)// + .setactivationScheduleJson("foo")// + .build()); + } + + @Test + public void testInvalidJsonSchedule1() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); + + final var cm = new DummyComponentManager(clock); + + new ControllerTest(new ControllerFastFrequencyReserveImpl()) // + .addReference("componentManager", cm) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", ESS // + .withGridMode(GridMode.ON_GRID)// + .withMaxApparentPower(92000)// + .withAllowedChargePower(-92000)// + .withAllowedDischargePower(92000)) // + .addReference("meter", METER) // + .activate(MyConfig.create() // + .setEssId(ESS_ID) // + .setId(CTRL_ID) // + .setMeterId(METER_ID) // + .setMode(ControlMode.MANUAL_ON) // + .setPreActivationTime(15)// + .setactivationScheduleJson("[foo]")// + .build()); + + } + + public static final String myJson = "[\r\n" + " " // + + "{\r\n" + " " // + + "\"startTimestamp\": \"1701738000\",\r\n"// + + " \"duration\": \"10800\",\r\n" // + + " \"dischargePowerSetPoint\": \"92000\",\r\n"// + + " \"frequencyLimit\": \"50000\",\r\n"// + + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\r\n"// + + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\r\n" // + + " },\r\n" // + + "{\r\n" + " " // + + "\"startTimestamp\": \"1701738000\",\r\n"// + + " \"duration\": \"10800\",\r\n" // + + " \"dischargePowerSetPoint\": \"92000\",\r\n"// + + " \"frequencyLimit\": \"50000\",\r\n"// + + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\r\n"// + + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\r\n" // + + " },\r\n" // + + " {\r\n"// + + " \"startTimestamp\": \"1701752400\",\r\n"// + + " \"duration\": \"10800\",\r\n"// + + " \"dischargePowerSetPoint\": \"92000\",\r\n"// + + " \"frequencyLimit\": \"50000\",\r\n"// + + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\r\n"// + + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\r\n" // + + " },\r\n" // + + " {\r\n"// + + " \"startTimestamp\": \"1701777600\",\r\n"// + + " \"duration\": \"10800\",\r\n"// + + " \"dischargePowerSetPoint\": \"92000\",\r\n"// + + " \"frequencyLimit\": \"50000\",\r\n"// + + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\r\n"// + + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\r\n" // + + " }\r\n" + "]";// + + @Test + public void testFromMethod() throws OpenemsNamedException { + + var scheduleElement = JsonUtils.parse(myJson); + var scheduleArray = JsonUtils.getAsJsonArray(scheduleElement); + + try { + List scheduleList = ActivateFastFreqReserveSchedule.from(scheduleArray); + + assertEquals(3, scheduleList.size()); + assertTrue(scheduleList.get(0).startTimestamp() <= scheduleList.get(1).startTimestamp()); + assertTrue(scheduleList.get(1).startTimestamp() <= scheduleList.get(2).startTimestamp()); + + } catch (OpenemsNamedException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest2.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest2.java new file mode 100644 index 00000000000..69fc5e7e463 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest2.java @@ -0,0 +1,185 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; + +import org.junit.Test; + +import io.openems.common.test.TimeLeapClock; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.common.sum.GridMode; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; +import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; +import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; +import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.test.DummyElectricityMeter; + +public class MyControllerTest2 { + + private static final String CTRL_ID = "ctrl0"; + private static final String ESS_ID = "ess0"; + private static final String METER_ID = "meter0"; + + private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID); + private static final DummyElectricityMeter METER = new DummyElectricityMeter(METER_ID); + + private static final ChannelAddress METER_FREQUENCY = new ChannelAddress(METER_ID, "Frequency"); + private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); + private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "SetActivePowerEquals"); + + @Test + public void test1() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); + final var cm = new DummyComponentManager(clock); + + new ControllerTest(new ControllerFastFrequencyReserveImpl()) // + .addReference("componentManager", cm) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("ess", ESS // + .withGridMode(GridMode.ON_GRID)// + .withMaxApparentPower(92000)// + .withAllowedChargePower(-92000)// + .withAllowedDischargePower(92000)) // + .addReference("meter", METER) // + .activate(MyConfig.create() // + .setEssId(ESS_ID) // + .setId(CTRL_ID) // + .setMeterId(METER_ID) // + .setMode(ControlMode.MANUAL_ON) // + .setPreActivationTime(15)// + .setactivationScheduleJson(JsonUtils.buildJsonArray() // + .add(JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", 1689242400) // Thu Jul 13 2023 10:00:00 GMT+0000 + .addProperty("duration", 86400) // + .addProperty("dischargePowerSetPoint", 92000) // + .addProperty("frequencyLimit", 49500) // + .addProperty("activationRunTime", ActivationTime.LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", SupportDuration.LONG_SUPPORT_DURATION) // + .build()) + .add(JsonUtils.buildJsonObject() // + .addProperty("startTimestamp", 1689331500) // Fri Jul 14 2023 10:45:00 GMT+0000 + .addProperty("duration", 86400) // + .addProperty("dischargePowerSetPoint", 92000) // + .addProperty("frequencyLimit", 49500) // + .addProperty("activationRunTime", ActivationTime.LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", SupportDuration.LONG_SUPPORT_DURATION) // + .build()) + .build()// + .toString())// + .build()) + .next(new TestCase("1") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.UNDEFINED) // + .output(ESS_ACTIVE_POWER, null))// + .next(new TestCase("2") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.UNDEFINED) // + .output(ESS_ACTIVE_POWER, null))// + .next(new TestCase("3") // + .timeleap(clock, 1, ChronoUnit.HOURS) // + .input(METER_FREQUENCY, 50000))// + .next(new TestCase("4"))// + .next(new TestCase("5") // + .output(STATE_MACHINE, State.UNDEFINED)) + .next(new TestCase("6") // + .timeleap(clock, 10, ChronoUnit.MINUTES)// + .output(STATE_MACHINE, State.PRE_ACTIVATION_STATE)) + .next(new TestCase("7") // + .timeleap(clock, 10, ChronoUnit.MINUTES)) + .next(new TestCase("8") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("9") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("10") // + .input(METER_FREQUENCY, 49400)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)// + .output(ESS_ACTIVE_POWER, 92000)) + .next(new TestCase("11") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("12") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output(ESS_ACTIVE_POWER, 92000)) // + .next(new TestCase("13") // + .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.SECONDS) // + .output(STATE_MACHINE, State.SUPPORT_DURATION)) + .next(new TestCase("14") // + .output(STATE_MACHINE, State.DEACTIVATION_TIME).output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("15") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("16") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // + .output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("17") // + .timeleap(clock, 16, ChronoUnit.SECONDS) // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("18") // + .timeleap(clock, 12, ChronoUnit.MINUTES)) // + .next(new TestCase("19") // + .output(STATE_MACHINE, State.RECOVERY_TIME) // + .output(ESS_ACTIVE_POWER, -16560)) + .next(new TestCase("20") // + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("21") // + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("22") // + .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.MINUTES)) + .next(new TestCase("23") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("24") // + .timeleap(clock, 1, ChronoUnit.DAYS)) // + .next(new TestCase("25") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("26") // + .timeleap(clock, 4, ChronoUnit.HOURS)) // + .next(new TestCase("27") // + .output(STATE_MACHINE, State.ACTIVATION_TIME)) + .next(new TestCase("28")// + .input(METER_FREQUENCY, 49400)) + .next(new TestCase("29") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("30") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output(ESS_ACTIVE_POWER, 92000)) // + .next(new TestCase("31") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("32") // + .output(STATE_MACHINE, State.SUPPORT_DURATION)// + .output(ESS_ACTIVE_POWER, 92000)) // + .next(new TestCase("33") // + .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.SECONDS) // + .output(STATE_MACHINE, State.SUPPORT_DURATION)) + .next(new TestCase("34") // + .output(STATE_MACHINE, State.DEACTIVATION_TIME).output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("35") // + .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .next(new TestCase("36") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // + .output(ESS_ACTIVE_POWER, 0)) + .next(new TestCase("37") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("38") // + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("39") // + .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.MINUTES)) + .next(new TestCase("40") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) + .next(new TestCase("41") // + .input(METER_FREQUENCY, 50000)// + .output(STATE_MACHINE, State.RECOVERY_TIME)) + .next(new TestCase("42") // + .timeleap(clock, 1, ChronoUnit.DAYS)); + } + +} diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java new file mode 100644 index 00000000000..991887efda7 --- /dev/null +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/TestClient.java @@ -0,0 +1,122 @@ +package io.openems.edge.controller.ess.fastfrequencyreserve; + +import java.net.URI; +import java.util.Map; + +import org.java_websocket.WebSocket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.common.websocket.AbstractWebsocketClient; +import io.openems.common.websocket.OnClose; +import io.openems.common.websocket.OnError; +import io.openems.common.websocket.OnNotification; +import io.openems.common.websocket.OnOpen; +import io.openems.common.websocket.OnRequest; +import io.openems.common.websocket.WsData; + +public class TestClient extends AbstractWebsocketClient { + + private final Logger log = LoggerFactory.getLogger(TestClient.class); + + private OnOpen onOpen; + private OnRequest onRequest; + private OnNotification onNotification; + private OnError onError; + private OnClose onClose; + + protected TestClient(URI serverUri, Map httpHeaders) { + super("JsonTest.Unittest", serverUri, httpHeaders); + this.onOpen = (ws, handshake) -> { + return null; + }; + this.onRequest = (ws, request) -> { + this.log.info("OnRequest: " + request); + return null; + }; + this.onNotification = (ws, notification) -> { + this.log.info("OnNotification: " + notification); + }; + this.onError = (ws, ex) -> { + this.log.info("onError: " + ex.getMessage()); + }; + this.onClose = (ws, code, reason, remote) -> { + this.log.info("onClose: " + reason); + }; + } + + @Override + public OnOpen getOnOpen() { + return this.onOpen; + } + + public void setOnOpen(OnOpen onOpen) { + this.onOpen = onOpen; + } + + @Override + public OnRequest getOnRequest() { + return this.onRequest; + } + + public void setOnRequest(OnRequest onRequest) { + this.onRequest = onRequest; + } + + @Override + public OnError getOnError() { + return this.onError; + } + + public void setOnError(OnError onError) { + this.onError = onError; + } + + @Override + public OnClose getOnClose() { + return this.onClose; + } + + public void setOnClose(OnClose onClose) { + this.onClose = onClose; + } + + @Override + protected OnNotification getOnNotification() { + return this.onNotification; + } + + public void setOnNotification(OnNotification onNotification) { + this.onNotification = onNotification; + } + + @Override + protected WsData createWsData(WebSocket ws) { + return new WsData(ws) { + @Override + public String toString() { + return "TestClient.WsData []"; + } + }; + } + + @Override + protected void logInfo(Logger log, String message) { + log.info(message); + } + + @Override + protected void logWarn(Logger log, String message) { + log.warn(message); + } + + @Override + protected void logError(Logger log, String message) { + log.error(message); + } + + @Override + protected void execute(Runnable command) { + command.run(); + } +} diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java index 2ee38fba17a..05dc14dc251 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedCharge.java @@ -192,6 +192,12 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { CONFIGURED_ESS_IS_NOT_MANAGED(Doc.of(Level.FAULT) // .text("The Energy Storage System is in read-only mode and does not allow to be controlled.")), // + /** + * Production values for prediction not available. + */ + NO_VALID_PRODUCTION_PREDICTION(Doc.of(Level.WARNING) // + .translationKey(ControllerEssGridOptimizedCharge.class, "noValidProductionPrediction")), // + /** * Cumulated seconds of the state delay charge. */ @@ -696,6 +702,25 @@ public default void _setConfiguredEssIsNotManaged(Boolean value) { this.getConfiguredEssIsNotManagedChannel().setNextValue(value); } + /** + * Gets the Channel for {@link ChannelId#NO_VALID_PRODUCTION_PREDICTION}. + * + * @return the Channel + */ + public default StateChannel noValidProductionPredictionChannel() { + return this.channel(ChannelId.NO_VALID_PRODUCTION_PREDICTION); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#NO_VALID_PRODUCTION_PREDICTION} Channel. + * + * @param value the next value + */ + public default void _setNoValidProductionPredictionChannel(Boolean value) { + this.noValidProductionPredictionChannel().setNextValue(value); + } + /** * Gets the Channel for {@link ChannelId#DELAY_CHARGE_TIME}. * diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java index aa727e503b4..cb1ffc764a6 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java @@ -145,12 +145,19 @@ private void updateConfig(Config config) { @Override public void run() throws OpenemsNamedException { + if (!this.ess.isManaged() && this.config.mode() != Mode.OFF) { this._setConfiguredEssIsNotManaged(true); return; } this._setConfiguredEssIsNotManaged(false); + if (!this.sum.getProductionActivePower().isDefined()) { + this._setNoValidProductionPredictionChannel(true); + return; + } + this._setNoValidProductionPredictionChannel(false); + // Updates the time channels. this.calculateTime(); diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties new file mode 100644 index 00000000000..2660bc97b77 --- /dev/null +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_de.properties @@ -0,0 +1,2 @@ +# ControllerEssGridOptimizedCharge +noValidProductionPrediction = Keine Erzeugungsprognose möglich. Bitte erfassen Sie die Erzeugung Ihrer Anlage über eine App oder wählen Sie in der netzdienlichen Beladung den Modus 'AUS'. diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties new file mode 100644 index 00000000000..e2195067037 --- /dev/null +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/translation_en.properties @@ -0,0 +1,2 @@ +# ControllerEssGridOptimizedCharge +noValidProductionPrediction = No production forecast available. Please log the production via an app or set the grid-optimized charge mode to 'OFF'. diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java index 03f197139e3..009f2fb33f2 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java @@ -184,6 +184,7 @@ public void automatic_default_predictions_at_midnight_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // @@ -233,6 +234,7 @@ public void automatic_default_predictions_at_midday_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // @@ -290,6 +292,7 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .build()) // .next(new TestCase() // .onAfterProcessImage(sleep) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // @@ -305,6 +308,7 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700)) // .next(new TestCase() // .onAfterProcessImage(sleep) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(ESS_SOC, 21) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) // @@ -312,14 +316,17 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // .next(new TestCase() // .onAfterProcessImage(sleep) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2677) // .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // .next(new TestCase() // .onAfterProcessImage(sleep) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2675) // .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // .next(new TestCase() // .onAfterProcessImage(sleep) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2673) // .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // ; @@ -360,6 +367,7 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // @@ -373,6 +381,7 @@ public void automatic_default_predictions_at_evening_test() throws Exception { // Value increases steadily by 0.25% of max apparent power 10_000 .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2025)) .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -383,6 +392,7 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2050)) .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -421,6 +431,7 @@ public void automatic_no_predictions_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // @@ -459,6 +470,7 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -7500) // .input(ESS_CAPACITY, 10_000) // .input(ESS_SOC, 20) // @@ -472,6 +484,7 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -12000) // .input(ESS_ACTIVE_POWER, -850) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -480,6 +493,7 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6200) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -7000) // .input(ESS_ACTIVE_POWER, -6200) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -488,6 +502,7 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6550) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -5000) // .input(ESS_ACTIVE_POWER, -6550) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -496,6 +511,7 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6050) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -8000) // .input(ESS_ACTIVE_POWER, -6050) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -506,6 +522,7 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .next(new TestCase() // // Difference between last limit and current lower than the ramp - ramp is not // applied + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -7000) // .input(ESS_ACTIVE_POWER, -7400) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -514,6 +531,7 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7750) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -6000) // .input(ESS_ACTIVE_POWER, -7750) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -551,6 +569,7 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .build()) // .next(new TestCase() // .input(METER_ACTIVE_POWER, -7500) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // .input(ESS_SOC, 100) // .input(ESS_MAX_APPARENT_POWER, 10_000) // @@ -564,6 +583,7 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(METER_ACTIVE_POWER, -12000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, -1000) // .input(ESS_SOC, 100) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -573,6 +593,7 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(METER_ACTIVE_POWER, -7000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, -6000) // .input(ESS_SOC, 100) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -582,6 +603,7 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(METER_ACTIVE_POWER, -5000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, -6000) // .input(ESS_SOC, 100) // .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5500) // @@ -590,6 +612,7 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(METER_ACTIVE_POWER, -8000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, -5500) // .input(ESS_SOC, 100) // .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6500) // @@ -600,6 +623,7 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { // Difference between last limit and current lower than the ramp - ramp is not // applied .input(METER_ACTIVE_POWER, -7000) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, -6300) // .input(ESS_SOC, 100) // .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6300) // @@ -607,6 +631,7 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6300) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -6000) // .input(ESS_ACTIVE_POWER, -6000) // .input(ESS_SOC, 100) // @@ -643,6 +668,7 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -7500) // .input(ESS_CAPACITY, 10_000) // .input(ESS_SOC, 20) // @@ -656,6 +682,7 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -12000) // .input(ESS_ACTIVE_POWER, -1000) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -664,6 +691,7 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6350) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -7000) // .input(ESS_ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -672,6 +700,7 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6350) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -5000) // .input(ESS_ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -680,6 +709,7 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -8000) // .input(ESS_ACTIVE_POWER, -5500) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -690,6 +720,7 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .next(new TestCase() // // Difference between last limit and current lower than the ramp - ramp is not // applied + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -7000) // .input(ESS_ACTIVE_POWER, -6300) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -698,6 +729,7 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -6000) // .input(ESS_ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -749,6 +781,7 @@ public void manual_midnight_test() throws Exception { .input(START_EPOCH_SECONDS, 1630566000) // .input(ESS_MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(TARGET_MINUTE, /* QuarterHour */ 1020) // .output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) // @@ -792,6 +825,7 @@ public void manual_midday_test() throws Exception { .setSellToGridLimitRampPercentage(5) // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // @@ -840,6 +874,7 @@ public void hybridEss_manual_midday_test() throws Exception { .setSellToGridLimitRampPercentage(5) // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // @@ -890,6 +925,7 @@ public void mode_off_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(METER_ACTIVE_POWER, -7500) // .input(ESS_MAX_APPARENT_POWER, 10_000) // .input(ESS_ACTIVE_POWER, 0) // @@ -938,6 +974,7 @@ public void no_capacity_left_test() throws Exception { .build()) // .next(new TestCase() // .input(METER_ACTIVE_POWER, 0) // + .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(ESS_ACTIVE_POWER, 0) // .input(ESS_CAPACITY, 10_000) // .input(ESS_SOC, 99) // diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Config.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Config.java index bfda174afa0..9edecbc6394 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Config.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/Config.java @@ -43,6 +43,12 @@ @AttributeDefinition(name = "Energy limit in this session in [Wh]", description = "Set the Energylimit in this Session in Wh. The charging station will only charge till this limit; '0' is no limit.") int energySessionLimit() default 0; + @AttributeDefinition(name = "Minimum charging time while charging with excess power", description = "Minimum time (Seconds) is applied to avoid continuous switching between charging and not charging") + int excessChargeHystersis() default 120; + + @AttributeDefinition(name = "Minimum pause time while charging with excess power", description = "Minimum time (Seconds) is applied to avoid continuous switching between charging and not charging") + int excessChargePauseHysteresis() default 30; + @AttributeDefinition(name = "Evcs target filter", description = "This is auto-generated by 'Evcs-ID'.") String evcs_target() default "(enabled=true)"; diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcs.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcs.java index 5a4a779dd4f..54bbb5e0be5 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcs.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcs.java @@ -1,13 +1,17 @@ package io.openems.edge.controller.evcs; -import io.openems.common.types.OpenemsType; +import static io.openems.common.channel.PersistencePriority.HIGH; +import static io.openems.common.types.OpenemsType.BOOLEAN; + import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; -public interface ControllerEvcs { +public interface ControllerEvcs extends OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - AWAITING_HYSTERESIS(Doc.of(OpenemsType.BOOLEAN)) // + AWAITING_HYSTERESIS(Doc.of(BOOLEAN) // + .persistencePriority(HIGH)) // ; // private final Doc doc; diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java index a4aeb209438..69e67eae105 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.time.Clock; +import java.time.Duration; +import java.time.Instant; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; @@ -25,6 +27,8 @@ import io.openems.edge.common.modbusslave.ModbusSlaveTable; import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.api.Controller; +import io.openems.edge.evcs.api.ChargeMode; +import io.openems.edge.evcs.api.ChargeState; import io.openems.edge.evcs.api.ManagedEvcs; @Designate(ocd = Config.class, factory = true) @@ -40,6 +44,16 @@ public class ControllerEvcsImpl extends AbstractOpenemsComponent implements Cont private final Logger log = LoggerFactory.getLogger(ControllerEvcsImpl.class); private final ChargingLowerThanTargetHandler chargingLowerThanTargetHandler; + private final Clock clock; + + // Time of last charge power change, used for the hysteresis + private Instant lastInitialCharge = Instant.MIN; + + // Time of last charge pause, used for the hysteresis + private Instant lastChargePause = Instant.MIN; + + // Last charge power, used for the hysteresis + private int lastChargePower = 0; @Reference private ConfigurationAdmin cm; @@ -62,6 +76,7 @@ protected ControllerEvcsImpl(Clock clock) { Controller.ChannelId.values(), // ControllerEvcs.ChannelId.values() // ); + this.clock = clock; this.chargingLowerThanTargetHandler = new ChargingLowerThanTargetHandler(clock); } @@ -205,6 +220,11 @@ public void run() throws OpenemsNamedException { } } + if (this.config.chargeMode().equals(ChargeMode.EXCESS_POWER)) { + // Apply hysteresis + nextChargePower = this.applyHysteresis(nextChargePower); + } + if (isClustered) { this.evcs.setChargePowerRequest(nextChargePower); } else { @@ -275,6 +295,82 @@ private static int calculateExcessPowerAfterEss(Sum sum, ManagedEvcs evcs) { return result > 0 ? result : 0; } + /** + * Applies the hysteresis to avoid too quick changes between a charge process + * and a pause. + * + * @param nextChargePower the next charge power limit + * @return next charge power or the last power if hysteresis is active + */ + private int applyHysteresis(int nextChargePower) { + int targetChargePower = nextChargePower; + boolean showWarning = false; + var now = Instant.now(this.clock); + + // Wait at least the EVCS-specific response time, required to increase and + // decrease the charging power + if (awaitLastChanges(this.evcs.getChargeState().asEnum())) { + // Still waiting for increasing, decreasing the power or undefined + return this.lastChargePower; + } + // TODO: Show info, test and check if bellow logic still needed or need to be + // different (Change only when we would change for xSeconds) + + // New charge power limit + if (this.lastChargePower <= 0 && nextChargePower > 0) { + var hysteresis = Duration.ofSeconds(this.config.excessChargePauseHysteresis()); + if (this.lastChargePause.plus(hysteresis).isBefore(now)) { + + // Start charing + this.lastInitialCharge = now; + } else { + // Wait for hysteresis + showWarning = true; + targetChargePower = this.lastChargePower; + } + } + + // Pause charging by limiting to zero + if (this.lastChargePower > 0 && nextChargePower <= 0) { + var hysteresis = Duration.ofSeconds(this.config.excessChargeHystersis()); + if (this.lastInitialCharge.plus(hysteresis).isBefore(now)) { + + // Pause charing + targetChargePower = 0; + this.lastChargePause = now; + } else { + // Wait for hysteresis + showWarning = true; + targetChargePower = this.lastChargePower; + } + } + + // Apply results + this.lastChargePower = targetChargePower; + this.channel(ControllerEvcs.ChannelId.AWAITING_HYSTERESIS).setNextValue(showWarning); + + return targetChargePower; + } + + /** + * Check if the evcs should wait for last changes. + * + *

+ * Since the charging stations and each car have their own response time until + * they charge at the set power, the controller waits until everything runs + * normally. + * + * @param chargeState current evcs charge state + * @return The cvcs should await or not + */ + private static boolean awaitLastChanges(ChargeState chargeState) { + if (chargeState.equals(ChargeState.INCREASING) || chargeState.equals(ChargeState.INCREASING)) { + // Still waiting for increasing, decreasing the power + return true; + } + return false; + } + @Override public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { return new ModbusSlaveTable(// diff --git a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java index 1c4f620d146..960a2c5789c 100644 --- a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java +++ b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java @@ -4,6 +4,7 @@ import java.time.Instant; import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; import org.junit.Test; @@ -15,6 +16,7 @@ import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.evcs.api.ChargeMode; +import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.test.DummyEvcsPower; import io.openems.edge.evcs.test.DummyManagedEvcs; @@ -42,6 +44,9 @@ public class ControllerEvcsImplTest { private static ChannelAddress evcs0SetPowerRequest = new ChannelAddress("evcs0", "SetChargePowerRequest"); private static ChannelAddress evcs0Status = new ChannelAddress("evcs0", "Status"); private static ChannelAddress evcs0MaximumHardwarePower = new ChannelAddress("evcs0", "MaximumHardwarePower"); + private static ChannelAddress evcs0MinimumHardwarePower = new ChannelAddress("evcs0", "MinimumHardwarePower"); + private static ChannelAddress evcsController0AwaitingHysteresis = new ChannelAddress("ctrlEvcs0", + "AwaitingHysteresis"); @Test public void excessChargeTest1() throws Exception { @@ -267,4 +272,82 @@ public void clusterTestDisabledCharging() throws Exception { .output(evcs0MaximumPower, null)) // ; } + + @Test + public void hysteresisTest() throws Exception { + + final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, + ZoneOffset.UTC); + + new ControllerTest(new ControllerEvcsImpl(clock)) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("sum", new DummySum()) // + .addReference("evcs", EVCS) // + .activate(MyConfig.create() // + .setId("ctrlEvcs0") // + .setEvcsId(EVCS_ID) // + .setEnableCharging(DEFAULT_ENABLE_CHARGING) // + .setChargeMode(DEFAULT_CHARGE_MODE) // + .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // + .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // + .setPriority(DEFAULT_PRIORITY) // + .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setExcessChargeHystersis(120) // + .setExcessChargePauseHysteresis(30) // + .build()) // + .next(new TestCase() // + .input(sumEssDischargePower, 0) // + .input(evcs0IsClustered, false) // + .input(sumGridActivePower, -6_000) // + .input(evcs0ChargePower, 0) // + .output(evcs0SetChargePowerLimit, 6_000)) // + .next(new TestCase() // + .input(sumEssDischargePower, 0) // + .input(sumGridActivePower, -200) // + .input(evcs0ChargePower, 5800) // + .output(evcs0SetChargePowerLimit, 6_000)) // + .next(new TestCase() // + .input(sumEssDischargePower, 0) // + .input(sumGridActivePower, 500) // + .input(evcs0ChargePower, 5800) // + .input(evcs0MinimumHardwarePower, Evcs.DEFAULT_MINIMUM_HARDWARE_POWER) + .output(evcs0SetChargePowerLimit, 5300)) + + // Active hysteresis + .next(new TestCase() // + .input(sumEssDischargePower, 0) // + .input(sumGridActivePower, 1000) // + .input(evcs0ChargePower, 5000) // + .output(evcs0SetChargePowerLimit, 5_300) // + .output(evcsController0AwaitingHysteresis, true)) // + .next(new TestCase() // + .timeleap(clock, 6, ChronoUnit.MINUTES)) // + + // Passed hysteresis + .next(new TestCase() // + .input(sumEssDischargePower, 0) // + .input(sumGridActivePower, 1000) // + .input(evcs0ChargePower, 5000) // + .output(evcs0SetChargePowerLimit, 0) // + .output(evcsController0AwaitingHysteresis, false)) // + + // Active hysteresis + .next(new TestCase() // + .input(sumEssDischargePower, 0) // + .input(sumGridActivePower, -5000) // + .input(evcs0ChargePower, 0) // + .output(evcs0SetChargePowerLimit, 0) // + .output(evcsController0AwaitingHysteresis, true)) + + .next(new TestCase() // + .timeleap(clock, 1, ChronoUnit.MINUTES)) // + + // New charge process starting after another 30 seconds + .next(new TestCase() // + .input(sumEssDischargePower, 0) // + .input(sumGridActivePower, -5000) // + .input(evcs0ChargePower, 0) // + .output(evcs0SetChargePowerLimit, 5000) // + .output(evcsController0AwaitingHysteresis, false)); // + } } diff --git a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/MyConfig.java b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/MyConfig.java index 5e8533f1b41..cf509aca506 100644 --- a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/MyConfig.java +++ b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/MyConfig.java @@ -17,6 +17,8 @@ protected static class Builder { private int defaultChargeMinPower = 0; private Priority priority = Priority.CAR; private int energySessionLimit = 0; + private int excessChargeHystersis = 120; + private int excessChargePauseHysteresis = 30; private Builder() { } @@ -71,6 +73,16 @@ public Builder setEnergySessionLimit(int energySessionLimit) { return this; } + public Builder setExcessChargeHystersis(int excessChargeHystersis) { + this.excessChargeHystersis = excessChargeHystersis; + return this; + } + + public Builder setExcessChargePauseHysteresis(int excessChargePauseHysteresis) { + this.excessChargePauseHysteresis = excessChargePauseHysteresis; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -137,6 +149,16 @@ public boolean debugMode() { return this.builder.debugMode; } + @Override + public int excessChargeHystersis() { + return this.builder.excessChargeHystersis; + } + + @Override + public int excessChargePauseHysteresis() { + return this.builder.excessChargePauseHysteresis; + } + @Override public String evcs_target() { return "(&(enabled=true)(!(service.pid=ctrlEvcs0))(|(id=" + this.evcs_id() + ")))"; diff --git a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHomeComponents.java b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHomeComponents.java index 72f62c234ba..a70a3242f1e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHomeComponents.java +++ b/io.openems.edge.core/src/io/openems/edge/app/integratedsystem/FeneconHomeComponents.java @@ -35,6 +35,24 @@ public static EdgeConfig.Component battery(// final ResourceBundle bundle, // final String batteryId, // final String modbusIdInternal // + ) { + return battery(bundle, batteryId, modbusIdInternal, "AUTO"); + } + + /** + * Creates a default battery component for a FENECON Home. + * + * @param bundle the translation bundle + * @param batteryId the id of the battery + * @param modbusIdInternal the id of the internal modbus bridge + * @param batteryStartStop the startStop target of the bridge + * @return the {@link Component} + */ + public static EdgeConfig.Component battery(// + final ResourceBundle bundle, // + final String batteryId, // + final String modbusIdInternal, // + final String batteryStartStop // ) { return new EdgeConfig.Component(batteryId, TranslationUtil.getTranslation(bundle, "App.IntegratedSystem.battery0.alias"), "Battery.Fenecon.Home", // @@ -43,7 +61,7 @@ public static EdgeConfig.Component battery(// .addProperty("batteryStartUpRelay", "io0/Relay4") // .addProperty("modbus.id", modbusIdInternal) // .addProperty("modbusUnitId", 1) // - .addProperty("startStop", "AUTO") // + .addProperty("startStop", batteryStartStop) // .build()); } @@ -181,7 +199,8 @@ public static EdgeConfig.Component modbusInternal(// final String modbusIdInternal // ) { return new EdgeConfig.Component(modbusIdInternal, - TranslationUtil.getTranslation(bundle, "App.IntegratedSystem.modbus0.alias"), "Bridge.Modbus.Serial", // + TranslationUtil.getTranslation(bundle, "App.IntegratedSystem.modbusToBattery.alias"), + "Bridge.Modbus.Serial", // JsonUtils.buildJsonObject() // .addProperty("enabled", true) // .addProperty("baudRate", 19200) // diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java index 31200d39881..9e0ee9fdeed 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/AwattarHourly.java @@ -4,6 +4,7 @@ import static io.openems.edge.app.common.props.CommonProps.defaultDef; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -169,7 +170,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/EntsoE.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/EntsoE.java index 0c2f6d2f10d..5c91317ae73 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/EntsoE.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/EntsoE.java @@ -2,6 +2,7 @@ import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -168,7 +169,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/GroupeE.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/GroupeE.java index b466c39ff10..7a868a62d1c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/GroupeE.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/GroupeE.java @@ -2,6 +2,7 @@ import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -151,7 +152,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/RabotCharge.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/RabotCharge.java index de9a446a214..3ad6b7df740 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/RabotCharge.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/RabotCharge.java @@ -3,6 +3,7 @@ import static io.openems.edge.core.appmanager.formly.enums.InputType.PASSWORD; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -178,7 +179,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StadtwerkHassfurt.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StadtwerkHassfurt.java index 8bf099ba335..09cd81281b6 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StadtwerkHassfurt.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StadtwerkHassfurt.java @@ -3,6 +3,7 @@ import static io.openems.edge.app.common.props.CommonProps.defaultDef; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -167,7 +168,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java index fbbeb951333..7c4c5f96b7b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/StromdaoCorrently.java @@ -2,6 +2,7 @@ import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -164,7 +165,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java index 3eeeb094bab..608b2a4ec4b 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java +++ b/io.openems.edge.core/src/io/openems/edge/app/timeofusetariff/Tibber.java @@ -3,6 +3,7 @@ import static io.openems.edge.core.appmanager.formly.enums.InputType.PASSWORD; import static io.openems.edge.core.appmanager.validator.Checkables.checkCommercial92; import static io.openems.edge.core.appmanager.validator.Checkables.checkHome; +import static io.openems.edge.core.appmanager.validator.Checkables.checkOr; import java.util.Map; import java.util.function.Function; @@ -197,7 +198,7 @@ public OpenemsAppCardinality getCardinality() { @Override protected ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setCompatibleCheckableConfigs(checkHome().or(checkCommercial92())); + .setCompatibleCheckableConfigs(checkOr(checkHome(), checkCommercial92())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java index b7c8349e4ed..87fad0519b6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java @@ -69,7 +69,10 @@ public void aggregate(ComponentConfiguration config, ComponentConfiguration oldC if (oldConfig != null) { var componentDiff = new ArrayList<>(oldConfig.components()); if (config != null) { - componentDiff.removeIf(t -> config.components().stream().anyMatch(c -> c.getId().equals(t.getId()))); + componentDiff.removeIf(t -> config.components().stream().anyMatch(c -> { + return c.getId().equals(t.getId()) // + && c.getFactoryId().equals(t.getFactoryId()); + })); } this.components2Delete.addAll(componentDiff); } @@ -94,8 +97,24 @@ public void create(User user, List otherAppConfigurations) thr if (foundComponentWithSameId != null) { // check if the found component has the same factory id if (!foundComponentWithSameId.getFactoryId().equals(comp.getFactoryId())) { - errors.add("Configuration of component with id '" + foundComponentWithSameId.getId() - + "' can not be rewritten. Because the component has a different factoryId."); + if (this.components2Delete.stream().anyMatch(t -> t.getId().equals(comp.getId()))) { + // if the component was intended to be deleted anyway delete it directly and + // create the new component directly afterwards + try { + this.deleteComponent(user, comp); + this.deletedComponents.add(comp.getId()); + this.components2Delete.removeIf(t -> t.getId().equals(comp.getId())); + this.createComponent(user, comp); + this.createdComponents.add(comp); + } catch (OpenemsNamedException e) { + final var error = "Component[" + comp.getFactoryId() + "] cant be created!"; + errors.add(error); + errors.add(e.getMessage()); + } + } else { + errors.add("Configuration of component with id '" + foundComponentWithSameId.getId() + + "' can not be rewritten. Because the component has a different factoryId."); + } continue; } @@ -179,8 +198,7 @@ public void delete(User user, List otherAppConfigurations) thr } try { - this.componentManager.handleDeleteComponentConfigRequest(user, - new DeleteComponentConfigRequest(comp.getId())); + this.deleteComponent(user, comp); this.deletedComponents.add(comp.getId()); } catch (OpenemsNamedException e) { errors.add(e.toString()); @@ -235,6 +253,10 @@ private final boolean anyChanges() { || !this.components2Delete.isEmpty(); } + private void deleteComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { + this.componentManager.handleDeleteComponentConfigRequest(user, new DeleteComponentConfigRequest(comp.getId())); + } + private void createComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { List properties = comp.getProperties().entrySet().stream() .map(t -> new Property(t.getKey(), t.getValue())).collect(Collectors.toList()); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties index b0e860ca53e..16414b2d0d5 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -246,8 +246,9 @@ App.IntegratedSystem.shadowManagementDisabled.label = Schattenmanagement deaktiv App.IntegratedSystem.shadowManagementDisabled.description = Nur wenn Optimierer verbaut sind, muss das Schattenmanagement deaktiviert werden App.IntegratedSystem.hasEssLimiter14a.label = Hat Limitierer für §14a -App.IntegratedSystem.modbus0.alias = Kommunikation mit der Batterie -App.IntegratedSystem.modbus0N.alias = Kommunikation mit den Batterien +App.IntegratedSystem.modbusToBattery.alias = Kommunikation mit der Batterie +App.IntegratedSystem.modbusToBatteryN.alias = Kommunikation mit den Batterien +App.IntegratedSystem.modbusToBattery0.alias = Kommunikation mit der Batterie {0} App.IntegratedSystem.modbus1.alias = Kommunikation mit dem Batterie-Wechselrichter App.IntegratedSystem.modbus1N.alias = Kommunikation mit dem Batterie-Wechselrichter {0} App.IntegratedSystem.modbus2.alias = externe RS485 Schnittstelle @@ -260,6 +261,7 @@ App.IntegratedSystem.batteryParallelClusterN.alias = Parallel-Cluster {0} App.IntegratedSystem.batteryInverter0.alias = Batterie-Wechselrichter App.IntegratedSystem.batteryInverterN.alias = Batterie-Wechselrichter {0} App.IntegratedSystem.ess0.alias = Speichersystem +App.IntegratedSystem.essCluster0.alias = Batteriespeicher-Cluster App.IntegratedSystem.predictor0.alias = Prognose App.IntegratedSystem.ctrlEssSurplusFeedToGrid0.alias = Überschusseinspeisung App.IntegratedSystem.emergencyMeter.alias = Notstromverbraucher @@ -325,7 +327,6 @@ App.FENECON.Industrial.L.ILK710.batteryFirmwareVersion.label = Battery Firmware App.FENECON.Industrial.S.io0 = Relais App.FENECON.Industrial.S.ess0.alias = Batteriespeicher App.FENECON.Industrial.S.essN.alias = Batteriespeicher {0} -App.FENECON.Industrial.S.essCluster0.alias = Batteriespeicher-Cluster App.FENECON.Industrial.S.hasGridMeter.label = Hat Netzzähler App.FENECON.Industrial.S.hasSelfConsumptionOptimization.label = Hat Eigenverbrauchsoptimierung App.FENECON.Industrial.S.modbusToGridMeter.alias = Kommunikation mit den Netzzähler diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties index 460116c4f56..2566a3fa36a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -246,8 +246,9 @@ App.IntegratedSystem.shadowManagementDisabled.label = Deactivate shadow manageme App.IntegratedSystem.shadowManagementDisabled.description = Only if optimisers are installed, shadow management must be deactivated App.IntegratedSystem.hasEssLimiter14a.label = Has limiter for §14a -App.IntegratedSystem.modbus0.alias = Communication with the battery -App.IntegratedSystem.modbus0N.alias = Communication with the batteries +App.IntegratedSystem.modbusToBattery.alias = Communication with the battery +App.IntegratedSystem.modbusToBatteryN.alias = Communication with the batteries +App.IntegratedSystem.modbusToBattery0.alias = Communication with the battery {0} App.IntegratedSystem.modbus1.alias = Communication with the battery inverter App.IntegratedSystem.modbus1N.alias = Communication with the battery inverter {0} App.IntegratedSystem.modbus2.alias = external RS485 interface @@ -260,6 +261,7 @@ App.IntegratedSystem.batteryParallelClusterN.alias = parallel-cluster {0} App.IntegratedSystem.batteryInverter0.alias = battery inverter App.IntegratedSystem.batteryInverterN.alias = battery inverter {0} App.IntegratedSystem.ess0.alias = Storage system +App.IntegratedSystem.essCluster0.alias = Storage system-Cluster App.IntegratedSystem.predictor0.alias = Forecast App.IntegratedSystem.ctrlEssSurplusFeedToGrid0.alias = Excess feed-in App.IntegratedSystem.emergencyMeter.alias = Emergency power consumers @@ -325,7 +327,6 @@ App.FENECON.Industrial.L.ILK710.batteryFirmwareVersion.label = Battery Firmware App.FENECON.Industrial.S.io0 = Relay App.FENECON.Industrial.S.ess0.alias = Battery Storage App.FENECON.Industrial.S.essN.alias = Battery Storage {0} -App.FENECON.Industrial.S.essCluster0.alias = Battery Storage-Cluster App.FENECON.Industrial.S.hasGridMeter.label = Has Grid-Meter App.FENECON.Industrial.S.hasSelfConsumptionOptimization.label = Has Self-consumption optimisation App.FENECON.Industrial.S.modbusToGridMeter.alias = Communication with the Grid-Meter diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java index 32783117bed..993cc7d84d3 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/Checkables.java @@ -1,6 +1,7 @@ package io.openems.edge.core.appmanager.validator; import java.util.Collections; +import java.util.Objects; import java.util.TreeMap; import io.openems.edge.core.appmanager.validator.ValidatorConfig.CheckableConfig; @@ -35,14 +36,25 @@ public static CheckableConfig checkCommercial92() { * * @param check1 the first check * @param check2 the second check + * @param other the additional checks to combine with 'or' operator * @return the {@link CheckableConfig} */ - public static CheckableConfig checkOr(CheckableConfig check1, CheckableConfig check2) { - return new ValidatorConfig.CheckableConfig(CheckOr.COMPONENT_NAME, + public static CheckableConfig checkOr(// + CheckableConfig check1, // + CheckableConfig check2, // + CheckableConfig... other // + ) { + var config = new ValidatorConfig.CheckableConfig(CheckOr.COMPONENT_NAME, new ValidatorConfig.MapBuilder<>(new TreeMap()) // - .put("check1", check1) // - .put("check2", check2) // + .put("check1", Objects.requireNonNull(check1)) // + .put("check2", Objects.requireNonNull(check2)) // .build()); + if (other != null && other.length > 0) { + for (var check : other) { + config = config.or(check); + } + } + return config; } /** diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java index 8ae2c8457f9..27843503c1e 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java @@ -1,5 +1,12 @@ package io.openems.edge.core.meta; +import static io.openems.common.utils.ThreadPoolUtils.shutdownAndAwaitTermination; + +import java.time.Instant; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -12,6 +19,7 @@ import io.openems.common.OpenemsConstants; import io.openems.common.channel.AccessMode; import io.openems.common.oem.OpenemsEdgeOem; +import io.openems.edge.common.channel.LongReadChannel; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.meta.Meta; @@ -27,6 +35,8 @@ }) public class MetaImpl extends AbstractOpenemsComponent implements Meta, OpenemsComponent, ModbusSlave { + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + @Reference private ConfigurationAdmin cm; @@ -45,6 +55,12 @@ public MetaImpl() { private void activate(ComponentContext context, Config config) { super.activate(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true); + // Update the Channel _meta/SystemTimeUtc after every second + final var systemTimeUtcChannel = this.channel(Meta.ChannelId.SYSTEM_TIME_UTC); + this.executor.scheduleAtFixedRate(() -> { + systemTimeUtcChannel.setNextValue(Instant.now().getEpochSecond()); + }, 0, 1000, TimeUnit.MILLISECONDS); + this.applyConfig(config); if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; @@ -64,6 +80,7 @@ private void modified(ComponentContext context, Config config) { @Override @Deactivate protected void deactivate() { + shutdownAndAwaitTermination(this.executor, 0); super.deactivate(); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java b/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java index 6bf8c875d46..ea45fc4a94a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java @@ -464,7 +464,6 @@ private void calculateState() { highestLevel = Level.INFO; } } - this.getStateChannel().setNextValue(highestLevel); } diff --git a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java index 9dae53bdbeb..581576f2275 100644 --- a/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java +++ b/io.openems.edge.edge2edge/src/io/openems/edge/edge2edge/common/AbstractEdge2Edge.java @@ -344,20 +344,14 @@ protected abstract io.openems.edge.common.channel.ChannelId getWriteChannelId( * @return the {@link AbstractModbusElement} */ private static ModbusElement generateModbusElement(ModbusType type, int address) { - switch (type) { - case ENUM16: - case UINT16: - return new UnsignedWordElement(address); - case UINT32: - return new UnsignedDoublewordElement(address); - case FLOAT32: - return new FloatDoublewordElement(address); - case FLOAT64: - return new UnsignedQuadruplewordElement(address); - case STRING16: - return new StringWordElement(address, 16); - } - return null; + return switch (type) { + case ENUM16, UINT16 -> new UnsignedWordElement(address); + case UINT32 -> new UnsignedDoublewordElement(address); + case UINT64 -> new UnsignedQuadruplewordElement(address); + case FLOAT32 -> new FloatDoublewordElement(address); + case FLOAT64 -> new UnsignedQuadruplewordElement(address); + case STRING16 -> new StringWordElement(address, 16); + }; } /** diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/Config.java b/io.openems.edge.energy/src/io/openems/edge/energy/Config.java index 5b1344d5a72..dad00cde174 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/Config.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/Config.java @@ -11,5 +11,8 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; + @AttributeDefinition(name = "Log-Verbosity", description = "The log verbosity") + LogVerbosity logVerbosity() default LogVerbosity.DEBUG_LOG; + String webconsole_configurationFactory_nameHint() default "Core Energy Scheduler"; } \ No newline at end of file diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java b/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java index 1f0e892b447..88da0a70170 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java @@ -73,35 +73,39 @@ public class EnergySchedulerImpl extends AbstractOpenemsComponent @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile Timedata timedata; + private Config config; + public EnergySchedulerImpl() { super(// OpenemsComponent.ChannelId.values(), // EnergyScheduler.ChannelId.values() // ); // Prepare Optimizer and Context - this.optimizer = new Optimizer(() -> { - if (this.timeOfUseTariff == null) { - throw new OpenemsException("TimeOfUseTariff is not available"); - } - var ctrl = this.schedulables.stream() // - .filter(TimeOfUseTariffControllerImpl.class::isInstance) // - .map(TimeOfUseTariffControllerImpl.class::cast) // - .findFirst().orElse(null); - if (ctrl == null) { - throw new OpenemsException("TimeOfUseTariffController is not available"); - } - var esh = ctrl.getEnergyScheduleHandler(); - // NOTE: This is a workaround while we refactor TimeOfUseTariffController - // This code assumes that the `EnergySchedulable` is a - // `TimeOfUseTariffController` - return GlobalContext.create() // - .setClock(this.componentManager.getClock()) // - .setEnergyScheduleHandler(esh) // - .setSum(this.sum) // - .setPredictorManager(this.predictorManager) // - .setTimeOfUseTariff(this.timeOfUseTariff) // - .build(); - }); + this.optimizer = new Optimizer(// + () -> this.config.logVerbosity(), // + () -> { + if (this.timeOfUseTariff == null) { + throw new OpenemsException("TimeOfUseTariff is not available"); + } + var ctrl = this.schedulables.stream() // + .filter(TimeOfUseTariffControllerImpl.class::isInstance) // + .map(TimeOfUseTariffControllerImpl.class::cast) // + .findFirst().orElse(null); + if (ctrl == null) { + throw new OpenemsException("TimeOfUseTariffController is not available"); + } + var esh = ctrl.getEnergyScheduleHandler(); + // NOTE: This is a workaround while we refactor TimeOfUseTariffController + // This code assumes that the `EnergySchedulable` is a + // `TimeOfUseTariffController` + return GlobalContext.create() // + .setClock(this.componentManager.getClock()) // + .setEnergyScheduleHandler(esh) // + .setSum(this.sum) // + .setPredictorManager(this.predictorManager) // + .setTimeOfUseTariff(this.timeOfUseTariff) // + .build(); + }); } @Activate @@ -121,6 +125,7 @@ private void modified(ComponentContext context, Config config) throws OpenemsNam } private synchronized boolean applyConfig(Config config) { + this.config = config; if (OpenemsComponent.validateSingleton(this.cm, SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return false; } @@ -140,6 +145,15 @@ protected void deactivate() { super.deactivate(); } + @Override + public String debugLog() { + if (this.config == null || this.config.logVerbosity() == LogVerbosity.NONE) { + return null; + } + // TODO add debug log + return null; + } + @Override public void buildJsonApiRoutes(JsonApiBuilder builder) { builder.handleRequest(GetScheduleRequest.METHOD, call -> handleGetScheduleRequest(// diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java b/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java new file mode 100644 index 00000000000..130bd15e512 --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java @@ -0,0 +1,13 @@ +package io.openems.edge.energy; + +public enum LogVerbosity { + NONE, + /** + * Show basic information in Controller.Debug.Log. + */ + DEBUG_LOG, + /** + * Trace. + */ + TRACE; +} \ No newline at end of file diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java index 030b0c3e4dc..3188e15f364 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java @@ -15,6 +15,7 @@ import java.time.ZonedDateTime; import java.util.Map.Entry; import java.util.TreeMap; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,7 +25,9 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.function.ThrowingSupplier; import io.openems.common.test.TimeLeapClock; +import io.openems.common.utils.FunctionUtils; import io.openems.common.worker.AbstractImmediateWorker; +import io.openems.edge.energy.LogVerbosity; import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.energy.optimizer.Simulator.Period; @@ -36,12 +39,15 @@ public class Optimizer extends AbstractImmediateWorker { private final Logger log = LoggerFactory.getLogger(Optimizer.class); + private final Supplier logVerbosity; private final ThrowingSupplier globalContext; private final TreeMap schedule = new TreeMap<>(); private Params params = null; - public Optimizer(ThrowingSupplier globalContext) { + public Optimizer(Supplier logVerbosity, + ThrowingSupplier globalContext) { + this.logVerbosity = logVerbosity; this.globalContext = globalContext; initializeRandomRegistryForProduction(); @@ -51,7 +57,7 @@ public Optimizer(ThrowingSupplier globalContext @Override public void forever() throws InterruptedException, OpenemsException { - this.log.info("# Start next run of Optimizer"); + this.traceLog(() -> "Start next run of Optimizer"); this.createParams(); // this possibly takes forever @@ -89,7 +95,7 @@ public void forever() throws InterruptedException, OpenemsException { var remainingExecutionLimit = Duration .between(Instant.now(globalContext.clock()), start.plusSeconds(executionLimitSeconds)).getSeconds(); if (remainingExecutionLimit > 0) { - this.log.info("Sleep [" + remainingExecutionLimit + "s] till next run of Optimizer"); + this.traceLog(() -> "Sleep [" + remainingExecutionLimit + "s] till next run of Optimizer"); sleep(remainingExecutionLimit * 1000); } } @@ -113,7 +119,7 @@ private void createParams() throws InterruptedException { } } catch (OpenemsException e) { - this.log.info("# Stuck trying to get Params. " + e.getMessage()); + this.traceLog(() -> "Stuck trying to get Params. " + e.getMessage()); this.params = null; synchronized (this.schedule) { this.schedule.clear(); @@ -142,4 +148,11 @@ public ImmutableSortedMap getSchedule() { return ImmutableSortedMap.copyOf(this.schedule); } } + + private void traceLog(Supplier message) { + switch (this.logVerbosity.get()) { + case NONE, DEBUG_LOG -> FunctionUtils.doNothing(); + case TRACE -> this.log.info("OPTIMIZER " + message.get()); + } + } } diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java index 10331208f63..bacd9a10260 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java @@ -1,6 +1,7 @@ package io.openems.edge.energy; import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.edge.energy.LogVerbosity.TRACE; import static io.openems.edge.energy.TestData.CONSUMPTION_PREDICTION_QUARTERLY; import static io.openems.edge.energy.TestData.HOURLY_PRICES_SUMMER; import static io.openems.edge.energy.TestData.PRODUCTION_PREDICTION_QUARTERLY; @@ -74,10 +75,7 @@ public static EnergySchedulerImpl create(Clock clock) throws Exception { .activate(MyConfig.create() // .setId(CTRL_ID) // .setEnabled(false) // - .setEssId("ess0") // - .setEssMaxChargePower(5000) // - .setMaxChargePowerFromGrid(10000) // - .setLimitChargePowerFor14aEnWG(false) // + .setLogVerbosity(TRACE) // .build()) // .next(new TestCase()); return sut; diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java b/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java index 2aeb9dc7bd3..1fbea17e506 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java @@ -8,10 +8,7 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id; private boolean enabled; - private String essId; - private int essMaxChargePower; - private int maxChargePowerFromGrid; - private boolean limitChargePowerFor14aEnWG; + private LogVerbosity logVerbosity; private Builder() { } @@ -26,23 +23,8 @@ public Builder setEnabled(boolean enabled) { return this; } - public Builder setEssId(String essId) { - this.essId = essId; - return this; - } - - public Builder setEssMaxChargePower(int essMaxChargePower) { - this.essMaxChargePower = essMaxChargePower; - return this; - } - - public Builder setMaxChargePowerFromGrid(int maxChargePowerFromGrid) { - this.maxChargePowerFromGrid = maxChargePowerFromGrid; - return this; - } - - public Builder setLimitChargePowerFor14aEnWG(boolean limitChargePowerFor14aEnWG) { - this.limitChargePowerFor14aEnWG = limitChargePowerFor14aEnWG; + public Builder setLogVerbosity(LogVerbosity logVerbosity) { + this.logVerbosity = logVerbosity; return this; } @@ -71,4 +53,9 @@ private MyConfig(Builder builder) { public boolean enabled() { return this.builder.enabled; } + + @Override + public LogVerbosity logVerbosity() { + return this.builder.logVerbosity; + } } \ No newline at end of file diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/HybridEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/HybridEss.java index 3cf5e8d2e5b..cdeb54a7736 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/HybridEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/HybridEss.java @@ -209,6 +209,8 @@ public default void _setDcDischargeEnergy(long value) { public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) { return ModbusSlaveNatureTable.of(HybridEss.class, accessMode, 100) // .channel(0, ChannelId.DC_DISCHARGE_POWER, ModbusType.UINT16) // + .channel(1, ChannelId.DC_CHARGE_ENERGY, ModbusType.FLOAT64) // + .channel(5, ChannelId.DC_DISCHARGE_ENERGY, ModbusType.FLOAT64) // .build(); } } diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java index 616c112b519..36565ec140f 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/ManagedSymmetricEss.java @@ -223,7 +223,7 @@ public void accept(ManagedSymmetricEss ess, Integer value) throws OpenemsNamedEx * failed. * */ - APPLY_POWER_FAILED(Doc.of(Level.FAULT) // + APPLY_POWER_FAILED(Doc.of(Level.WARNING) // .persistencePriority(PersistencePriority.HIGH) // .text("Applying the Active/Reactive Power failed")); diff --git a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java index 4c8c476ec44..4f14108fd5e 100644 --- a/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java +++ b/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java @@ -215,6 +215,7 @@ public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode access .channel(8, ChannelId.MAX_CELL_VOLTAGE, ModbusType.FLOAT32) // .channel(10, ChannelId.MIN_CELL_TEMPERATURE, ModbusType.FLOAT32) // .channel(12, ChannelId.MAX_CELL_TEMPERATURE, ModbusType.FLOAT32) // + .channel(14, ChannelId.CAPACITY, ModbusType.FLOAT32) // .build(); } diff --git a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/Data.java b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/Data.java index f0d1830e97b..8b99b973c60 100644 --- a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/Data.java +++ b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/Data.java @@ -30,12 +30,12 @@ public class Data { /** * Holds all Inverters, always roughly sorted by weight. */ - private final List inverters = new ArrayList<>(); + private final List inverters = new CopyOnWriteArrayList<>(); /** * Holds all Ess. */ - private final List esss = new ArrayList<>(); + private final List esss = new CopyOnWriteArrayList<>(); private final List constraints = new CopyOnWriteArrayList<>(); private final Coefficients coefficients = new Coefficients(); diff --git a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/EssPowerImpl.java b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/EssPowerImpl.java index 3f8a079d10f..3cea0e39de1 100644 --- a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/EssPowerImpl.java +++ b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/EssPowerImpl.java @@ -216,13 +216,19 @@ private int getActivePowerExtrema(ManagedSymmetricEss ess, Phase phase, Pwr pwr, @Override public void handleEvent(Event event) { - switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_WRITE: - this.solver.solve(this.config.strategy()); - break; - case EdgeEventConstants.TOPIC_CYCLE_AFTER_WRITE: - this.data.initializeCycle(); - break; + try { + switch (event.getTopic()) { + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_WRITE // + -> this.solver.solve(this.config.strategy()); + + case EdgeEventConstants.TOPIC_CYCLE_AFTER_WRITE // + -> this.data.initializeCycle(); + } + + } catch (Exception e) { + this.logError(this.log, + "Error during handleEvent(). " + e.getClass().getSimpleName() + ": " + e.getMessage()); + e.printStackTrace(); } } diff --git a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/data/LinearSolverUtil.java b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/data/LinearSolverUtil.java index 3bc672205eb..12d5182f1b9 100644 --- a/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/data/LinearSolverUtil.java +++ b/io.openems.edge.ess.core/src/io/openems/edge/ess/core/power/data/LinearSolverUtil.java @@ -1,5 +1,9 @@ package io.openems.edge.ess.core.power.data; +import static org.apache.commons.math3.optim.linear.Relationship.EQ; +import static org.apache.commons.math3.optim.linear.Relationship.GEQ; +import static org.apache.commons.math3.optim.linear.Relationship.LEQ; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -9,7 +13,6 @@ import io.openems.edge.ess.power.api.Coefficients; import io.openems.edge.ess.power.api.Constraint; -import io.openems.edge.ess.power.api.LinearCoefficient; public class LinearSolverUtil { @@ -22,28 +25,29 @@ public class LinearSolverUtil { */ public static List convertToLinearConstraints(Coefficients coefficients, List constraints) { - List result = new ArrayList<>(); + final var result = new ArrayList(); for (Constraint c : constraints) { - if (c.getValue().isPresent()) { - var cos = generateEmptyCoefficientsArray(coefficients.getNoOfCoefficients()); - for (LinearCoefficient co : c.getCoefficients()) { - // TODO verify, that ESS is enabled - cos[co.getCoefficient().getIndex()] = co.getValue(); - } - org.apache.commons.math3.optim.linear.Relationship relationship = null; - switch (c.getRelationship()) { - case EQUALS: - relationship = org.apache.commons.math3.optim.linear.Relationship.EQ; - break; - case GREATER_OR_EQUALS: - relationship = org.apache.commons.math3.optim.linear.Relationship.GEQ; - break; - case LESS_OR_EQUALS: - relationship = org.apache.commons.math3.optim.linear.Relationship.LEQ; - break; + final var value = c.getValue(); + if (value.isEmpty()) { + continue; + } + + final var cos = generateEmptyCoefficientsArray(coefficients.getNoOfCoefficients()); + for (var co : c.getCoefficients()) { + var index = co.getCoefficient().getIndex(); + if (index >= cos.length) { // check for race conditions + continue; } - result.add(new LinearConstraint(cos, relationship, c.getValue().get())); + cos[index] = co.getValue(); } + + final var relationship = switch (c.getRelationship()) { + case EQUALS -> EQ; + case GREATER_OR_EQUALS -> GEQ; + case LESS_OR_EQUALS -> LEQ; + }; + + result.add(new LinearConstraint(cos, relationship, value.get())); } return result; } diff --git a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/data/LinearSolverUtilTest.java b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/data/LinearSolverUtilTest.java new file mode 100644 index 00000000000..4f45a67fb89 --- /dev/null +++ b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/data/LinearSolverUtilTest.java @@ -0,0 +1,44 @@ +package io.openems.edge.ess.core.power.data; + +import static io.openems.edge.ess.core.power.data.ConstraintUtil.createSimpleConstraint; +import static io.openems.edge.ess.power.api.Relationship.EQUALS; +import static io.openems.edge.ess.power.api.Relationship.GREATER_OR_EQUALS; +import static io.openems.edge.ess.power.api.Relationship.LESS_OR_EQUALS; + +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.edge.ess.power.api.Coefficients; +import io.openems.edge.ess.power.api.Constraint; +import io.openems.edge.ess.power.api.LinearCoefficient; +import io.openems.edge.ess.power.api.Phase; +import io.openems.edge.ess.power.api.Pwr; +import io.openems.edge.ess.power.api.Relationship; + +public class LinearSolverUtilTest { + + @Test(expected = OpenemsException.class) + public void testCoefficientOfThrowsException() throws OpenemsException { + createSimpleConstraint(new Coefficients(), // + "Dummy#1", "ess0", Phase.ALL, Pwr.ACTIVE, EQUALS, 0); + } + + @Test + public void testConvertToLinearConstraints() throws OpenemsException { + final var coefficients = new Coefficients(); + coefficients.initialize(false, Set.of("ess0")); + var constraints = List.of(// + createSimpleConstraint(coefficients, // + "Dummy EQUALS", "ess0", Phase.ALL, Pwr.ACTIVE, EQUALS, 0), // + createSimpleConstraint(coefficients, // + "Dummy GREATER_OR_EQUALS", "ess0", Phase.ALL, Pwr.ACTIVE, GREATER_OR_EQUALS, 0), // + createSimpleConstraint(coefficients, // + "Dummy LESS_OR_EQUALS", "ess0", Phase.ALL, Pwr.ACTIVE, LESS_OR_EQUALS, 0), // + new Constraint("Dummy empty value", new LinearCoefficient[0], Relationship.EQUALS)); + LinearSolverUtil.convertToLinearConstraints(coefficients, constraints); + } + +} diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/AbstractGenericManagedEss.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/AbstractGenericManagedEss.java index 8f8c44bf659..669ceaa08f0 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/AbstractGenericManagedEss.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/AbstractGenericManagedEss.java @@ -211,22 +211,10 @@ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { @Override public StartStop getStartStopTarget() { - switch (this.startStopConfig) { - case AUTO: - // read StartStop-Channel - return this.startStopTarget.get(); - - case START: - // force START - return StartStop.START; - - case STOP: - // force STOP - return StartStop.STOP; - } - - assert false; - return StartStop.UNDEFINED; // can never happen + return switch (this.startStopConfig) { + case AUTO -> this.startStopTarget.get(); + case START -> StartStop.START; + case STOP -> StartStop.STOP; + }; } - } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/GenericManagedEss.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/GenericManagedEss.java index 83035585f53..62ba61395fe 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/GenericManagedEss.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/common/GenericManagedEss.java @@ -29,14 +29,28 @@ public interface GenericManagedEss extends ManagedSymmetricEss, StartStoppable, */ public static int RETRY_COMMAND_MAX_ATTEMPTS = 30; + /** + * Retry set-command after x Seconds, e.g. for starting battery or + * battery-inverter. + */ + public static int TIMEOUT = 300; + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - MAX_BATTERY_START_ATTEMPTS_FAULT(Doc.of(Level.FAULT) // + TIMEOUT_START_BATTERY(Doc.of(Level.FAULT) // + .text("Start battery timeout passed!")), // + TIMEOUT_START_BATTERY_INVERTER(Doc.of(Level.FAULT) // + .text("Start battery inverter timeout passed!")), // + TIMEOUT_STOP_BATTERY(Doc.of(Level.FAULT) // + .text("Stop battery timeout passed!")), // + TIMEOUT_STOP_BATTERY_INVERTER(Doc.of(Level.FAULT) // + .text("Stop battery inverter timeout passed!")), // + MAX_BATTERY_START_ATTEMPTS_FAULT(Doc.of(Level.WARNING) // .text("The maximum number of Battery start attempts failed")), // - MAX_BATTERY_STOP_ATTEMPTS_FAULT(Doc.of(Level.FAULT) // + MAX_BATTERY_STOP_ATTEMPTS_FAULT(Doc.of(Level.WARNING) // .text("The maximum number of Battery stop attempts failed")), // - MAX_BATTERY_INVERTER_START_ATTEMPTS_FAULT(Doc.of(Level.FAULT) // + MAX_BATTERY_INVERTER_START_ATTEMPTS_FAULT(Doc.of(Level.WARNING) // .text("The maximum number of Battery-Inverter start attempts failed")), // - MAX_BATTERY_INVERTER_STOP_ATTEMPTS_FAULT(Doc.of(Level.FAULT) // + MAX_BATTERY_INVERTER_STOP_ATTEMPTS_FAULT(Doc.of(Level.WARNING) // .text("The maximum number of Battery-Inverter stop attempts failed")); // private final Doc doc; @@ -68,7 +82,7 @@ public default StateChannel getMaxBatteryStartAttemptsFaultChannel() { } /** - * Gets the {@link StateChannel} for + * Gets the StateChannel value for * {@link ChannelId#MAX_BATTERY_START_ATTEMPTS_FAULT}. * * @return the Channel {@link Value} @@ -97,7 +111,7 @@ public default StateChannel getMaxBatteryStopAttemptsFaultChannel() { } /** - * Gets the {@link StateChannel} for + * Gets the StateChannel value for * {@link ChannelId#MAX_BATTERY_STOP_ATTEMPTS_FAULT}. * * @return the Channel {@link Value} @@ -127,7 +141,7 @@ public default StateChannel getMaxBatteryInverterStartAttemptsFaultChannel() { } /** - * Gets the {@link StateChannel} for + * Gets the StateChannel value for * {@link ChannelId#MAX_BATTERY_INVERTER_START_ATTEMPTS_FAULT}. * * @return the Channel {@link Value} @@ -157,7 +171,7 @@ public default StateChannel getMaxBatteryInverterStopAttemptsFaultChannel() { } /** - * Gets the {@link StateChannel} for + * Gets the StateChannel value for * {@link ChannelId#MAX_BATTERY_INVERTER_STOP_ATTEMPTS_FAULT}. * * @return the Channel {@link Value} @@ -176,4 +190,118 @@ public default void _setMaxBatteryInverterStopAttemptsFault(boolean value) { this.getMaxBatteryInverterStopAttemptsFaultChannel().setNextValue(value); } + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_START_BATTERY}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStartBatteryChannel() { + return this.channel(ChannelId.TIMEOUT_START_BATTERY); + } + + /** + * Gets the StateChannel value for {@link ChannelId#TIMEOUT_START_BATTERY}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStartBattery() { + return this.getTimeoutStartBatteryChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_START_BATTERY} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStartBattery(boolean value) { + this.getTimeoutStartBatteryChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_START_BATTERY_INVERTER}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStartBatteryInverterhannel() { + return this.channel(ChannelId.TIMEOUT_START_BATTERY_INVERTER); + } + + /** + * Gets the StateChannel value for + * {@link ChannelId#TIMEOUT_START_BATTERY_INVERTER}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStartBatteryInverter() { + return this.getTimeoutStartBatteryInverterhannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_START_BATTERY_INVERTER} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStartBatteryInverter(boolean value) { + this.getTimeoutStartBatteryInverterhannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_STOP_BATTERY_INVERTER}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStopBatteryInverterChannel() { + return this.channel(ChannelId.TIMEOUT_STOP_BATTERY_INVERTER); + } + + /** + * Gets the StateChannel value for + * {@link ChannelId#TIMEOUT_STOP_BATTERY_INVERTER}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStopBatteryInverter() { + return this.getTimeoutStopBatteryInverterChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_STOP_BATTERY_INVERTER} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStopBatteryInverter(boolean value) { + this.getTimeoutStopBatteryInverterChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#TIMEOUT_STOP_BATTERY}. + * + * @return the Channel + */ + public default StateChannel getTimeoutStopBatteryChannel() { + return this.channel(ChannelId.TIMEOUT_STOP_BATTERY); + } + + /** + * Gets the StateChannel value for {@link ChannelId#TIMEOUT_STOP_BATTERY}. + * + * @return the Channel {@link Value} + */ + public default Value getTimeoutStopBattery() { + return this.getTimeoutStopBatteryChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on + * {@link ChannelId#TIMEOUT_STOP_BATTERY} Channel. + * + * @param value the next value + */ + public default void _setTimeoutStopBattery(boolean value) { + this.getTimeoutStopBatteryChannel().setNextValue(value); + } + } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/offgrid/EssGenericOffGrid.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/offgrid/EssGenericOffGrid.java index a3492209db6..06a66872089 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/offgrid/EssGenericOffGrid.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/offgrid/EssGenericOffGrid.java @@ -24,7 +24,7 @@ public interface EssGenericOffGrid extends GenericManagedEss, OffGridEss, Manage public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(StateMachine.OffGridState.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")), // ; diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetric.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetric.java index 7fe3be0fe45..7cef132c384 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetric.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetric.java @@ -4,7 +4,9 @@ import io.openems.common.channel.AccessMode; import io.openems.common.channel.Level; +import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; @@ -40,6 +42,53 @@ public Doc doc() { } } + /** + * Gets the Channel for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getStateMachineChannel() { + return this.channel(ChannelId.STATE_MACHINE); + } + + /** + * Gets the StateMachine channel value for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel {@link Value} + */ + public default Value getStateMachine() { + return this.getStateMachineChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE} + * Channel. + * + * @param value the next value + */ + public default void _setStateMachine(State value) { + this.getStateMachineChannel().setNextValue(value); + } + + /** + * Gets the Channel for {@link ChannelId#RUN_FAILED}. + * + * @return the Channel + */ + public default Channel getRunFailedChannel() { + return this.channel(ChannelId.RUN_FAILED); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#RUN_FAILED} + * Channel. + * + * @param value the next value + */ + public default void _setRunFailed(boolean value) { + this.getRunFailedChannel().setNextValue(value); + } + @Override public default ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { return new ModbusSlaveTable(// diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImpl.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImpl.java index 40a3a6371af..b3d5ffaf083 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImpl.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImpl.java @@ -105,23 +105,20 @@ protected void deactivate() { @Override protected void handleStateMachine() { // Store the current State - this.channel(EssGenericManagedSymmetric.ChannelId.STATE_MACHINE) - .setNextValue(this.stateMachine.getCurrentState()); + this._setStateMachine(this.stateMachine.getCurrentState()); // Initialize 'Start-Stop' Channel this._setStartStop(StartStop.UNDEFINED); // Prepare Context - var context = new Context(this, this.getBattery(), this.getBatteryInverter()); + var context = new Context(this, this.getBattery(), this.getBatteryInverter(), this.componentManager.getClock()); // Call the StateMachine try { this.stateMachine.run(context); - - this.channel(EssGenericManagedSymmetric.ChannelId.RUN_FAILED).setNextValue(false); - + this._setRunFailed(false); } catch (OpenemsNamedException e) { - this.channel(EssGenericManagedSymmetric.ChannelId.RUN_FAILED).setNextValue(true); + this._setRunFailed(true); this.logError(this.log, "StateMachine failed: " + e.getMessage()); } } @@ -179,7 +176,6 @@ public boolean isManaged() { @Override public void setStartStop(StartStop value) { if (this.startStopTarget.getAndSet(value) != value) { - // Set only if value changed this.stateMachine.forceNextState(UNDEFINED); } } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/Context.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/Context.java index eed94199380..5a25ded906e 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/Context.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/Context.java @@ -1,5 +1,7 @@ package io.openems.edge.ess.generic.symmetric.statemachine; +import java.time.Clock; + import io.openems.edge.battery.api.Battery; import io.openems.edge.batteryinverter.api.ManagedSymmetricBatteryInverter; import io.openems.edge.common.statemachine.AbstractContext; @@ -9,10 +11,51 @@ public class Context extends AbstractContext { protected final Battery battery; protected final ManagedSymmetricBatteryInverter batteryInverter; + protected final Clock clock; - public Context(GenericManagedEss parent, Battery battery, ManagedSymmetricBatteryInverter batteryInverter) { + public Context(GenericManagedEss parent, Battery battery, ManagedSymmetricBatteryInverter batteryInverter, + Clock clock) { super(parent); this.battery = battery; this.batteryInverter = batteryInverter; + this.clock = clock; + } + + /** + * Generic ess has faults. + * + *

+ * Check for any faults in the generic ess and its dependent battery or battery + * inverter. + * + * @return true on any failure + */ + public boolean hasEssFaults() { + return this.getParent().hasFaults() || this.battery.hasFaults() || this.batteryInverter.hasFaults(); + } + + /** + * Is generic ess started. + * + *

+ * Generic ess is started when battery and battery-inverter started. + * + * @return true if battery and battery-inverter started + */ + public boolean isEssStarted() { + return this.battery.isStarted() && this.batteryInverter.isStarted(); + } + + /** + * Is generic ess stopped. + * + *

+ * Generic ess is stopped when at least the battery stopped. In many cases the + * BatteryInverter is not able to not stop. + * + * @return true if the system stopped. + */ + public boolean isEssStopped() { + return this.battery.isStopped(); } -} \ No newline at end of file +} diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/ErrorHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/ErrorHandler.java index 6a2dda3271f..c64884874d3 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/ErrorHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/ErrorHandler.java @@ -1,48 +1,44 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.edge.common.startstop.StartStop; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class ErrorHandler extends StateHandler { - private static final int WAIT_TIME_IN_SECONDS = 120; - - private Instant entryAt = Instant.MIN; - private int startAttemptCounter = 0; - @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.entryAt = Instant.now(); - this.startAttemptCounter++; - // Try to stop systems - context.battery.setStartStop(StartStop.STOP); - context.batteryInverter.setStartStop(StartStop.STOP); - } - - @Override - protected void onExit(Context context) throws OpenemsNamedException { - var ess = context.getParent(); - - ess._setMaxBatteryStartAttemptsFault(false); - ess._setMaxBatteryStopAttemptsFault(false); - ess._setMaxBatteryInverterStartAttemptsFault(false); - ess._setMaxBatteryInverterStopAttemptsFault(false); + context.batteryInverter.stop(); + context.battery.stop(); } @Override public State runAndGetNextState(Context context) { - if (Duration.between(this.entryAt, Instant.now()).getSeconds() > WAIT_TIME_IN_SECONDS - * Math.pow(16, this.startAttemptCounter)) { - // Try again + final var ess = context.getParent(); + final var battery = context.battery; + final var batteryInverter = context.batteryInverter; + // TODO error handling + + /* + * Wait at least for stopping the battery and check for ess, battery, + * battery-inverter faults + * + * If ModbusCommunicationFault would be a FaultState, check it explicitly. The + * battery could still have a communication fault while starting the battery. + */ + if (!ess.hasFaults() && !batteryInverter.hasFaults() && !battery.hasFaults() && context.battery.isStopped()) { return State.UNDEFINED; } return State.ERROR; } + @Override + protected void onExit(Context context) throws OpenemsNamedException { + final var ess = context.getParent(); + ess._setTimeoutStartBattery(false); + ess._setTimeoutStopBattery(false); + ess._setTimeoutStartBatteryInverter(false); + ess._setTimeoutStopBatteryInverter(false); + } } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryHandler.java index f1f881f1163..3b12dcea87e 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryHandler.java @@ -1,54 +1,36 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Timeout; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class StartBatteryHandler extends StateHandler { - private Instant lastAttempt = Instant.MIN; - private int attemptCounter = 0; + private final Timeout timeout = Timeout.ofSeconds(GenericManagedEss.TIMEOUT); @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.lastAttempt = Instant.MIN; - this.attemptCounter = 0; - var ess = context.getParent(); - ess._setMaxBatteryStartAttemptsFault(false); + this.timeout.start(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsNamedException { - var ess = context.getParent(); + final var ess = context.getParent(); + final var battery = context.battery; - if (context.battery.isStarted()) { + if (battery.isStarted()) { return State.START_BATTERY_INVERTER; } - var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now()) - .getSeconds() > GenericManagedEss.RETRY_COMMAND_SECONDS; - if (!isMaxStartTimePassed) { - // Still waiting... - return State.START_BATTERY; + // Is max allowed start time passed ? + if (this.timeout.elapsed(context.clock)) { + ess._setTimeoutStartBattery(true); + return State.ERROR; } - if (this.attemptCounter > GenericManagedEss.RETRY_COMMAND_MAX_ATTEMPTS) { - // Too many tries - ess._setMaxBatteryStartAttemptsFault(true); - return State.UNDEFINED; - - } else { - // Trying to start Battery - context.battery.start(); - - this.lastAttempt = Instant.now(); - this.attemptCounter++; - return State.START_BATTERY; - } + battery.start(); + return State.START_BATTERY; } - } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryInverterHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryInverterHandler.java index 5b394a9f5de..128ab668d21 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryInverterHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartBatteryInverterHandler.java @@ -1,54 +1,40 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Timeout; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class StartBatteryInverterHandler extends StateHandler { - private Instant lastAttempt = Instant.MIN; - private int attemptCounter = 0; + private final Timeout timeout = Timeout.ofSeconds(GenericManagedEss.TIMEOUT); @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.lastAttempt = Instant.MIN; - this.attemptCounter = 0; - var ess = context.getParent(); - ess._setMaxBatteryInverterStartAttemptsFault(false); + this.timeout.start(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsNamedException { - var ess = context.getParent(); + final var ess = context.getParent(); + final var inverter = context.batteryInverter; - if (context.batteryInverter.isStarted()) { - return State.STARTED; + if (context.hasEssFaults()) { + return State.ERROR; } - var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now()) - .getSeconds() > GenericManagedEss.RETRY_COMMAND_SECONDS; - if (!isMaxStartTimePassed) { - // Still waiting... - return State.START_BATTERY_INVERTER; + if (inverter.isStarted()) { + return State.STARTED; } - if (this.attemptCounter > GenericManagedEss.RETRY_COMMAND_MAX_ATTEMPTS) { - // Too many tries - ess._setMaxBatteryInverterStartAttemptsFault(true); - return State.UNDEFINED; - - } else { - // Trying to start Battery - context.batteryInverter.start(); - - this.lastAttempt = Instant.now(); - this.attemptCounter++; - return State.START_BATTERY_INVERTER; + // Is max allowed start time passed ? + if (this.timeout.elapsed(context.clock)) { + ess._setTimeoutStartBatteryInverter(true); + return State.ERROR; } - } + inverter.start(); + return State.START_BATTERY_INVERTER; + } } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartedHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartedHandler.java index 57ee53a0f41..e356b41c160 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartedHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StartedHandler.java @@ -8,24 +8,18 @@ public class StartedHandler extends StateHandler { @Override public State runAndGetNextState(Context context) { - var ess = context.getParent(); + final var ess = context.getParent(); - if (ess.hasFaults()) { - return State.UNDEFINED; + if (context.hasEssFaults()) { + return State.ERROR; } - if (!context.battery.isStarted()) { - return State.UNDEFINED; - } - - if (!context.batteryInverter.isStarted()) { - return State.UNDEFINED; + if (!context.isEssStarted()) { + return State.ERROR; } // Mark as started ess._setStartStop(StartStop.START); - return State.STARTED; } - } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryHandler.java index 4dc84ac8898..d1e40a2ca3f 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryHandler.java @@ -1,54 +1,40 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Timeout; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class StopBatteryHandler extends StateHandler { - private Instant lastAttempt = Instant.MIN; - private int attemptCounter = 0; + private final Timeout timeout = Timeout.ofSeconds(GenericManagedEss.TIMEOUT); @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.lastAttempt = Instant.MIN; - this.attemptCounter = 0; - var ess = context.getParent(); - ess._setMaxBatteryStopAttemptsFault(false); + this.timeout.start(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsNamedException { - var ess = context.getParent(); + final var ess = context.getParent(); + final var battery = context.battery; - if (context.battery.isStopped()) { - return State.STOPPED; + if (context.hasEssFaults()) { + return State.ERROR; } - var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now()) - .getSeconds() > GenericManagedEss.RETRY_COMMAND_SECONDS; - if (!isMaxStartTimePassed) { - // Still waiting... - return State.STOP_BATTERY; + if (battery.isStopped()) { + return State.STOPPED; } - if (this.attemptCounter > GenericManagedEss.RETRY_COMMAND_MAX_ATTEMPTS) { - // Too many tries - ess._setMaxBatteryStopAttemptsFault(true); - return State.UNDEFINED; - - } else { - // Trying to stop Battery - context.battery.stop(); - - this.lastAttempt = Instant.now(); - this.attemptCounter++; - return State.STOP_BATTERY; + // Is max allowed start time passed ? + if (this.timeout.elapsed(context.clock)) { + ess._setTimeoutStopBattery(true); + return State.ERROR; } - } + battery.stop(); + return State.STOP_BATTERY; + } } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryInverterHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryInverterHandler.java index b6747fc14b6..e426221f92b 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryInverterHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StopBatteryInverterHandler.java @@ -1,54 +1,39 @@ package io.openems.edge.ess.generic.symmetric.statemachine; -import java.time.Duration; -import java.time.Instant; - import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.Timeout; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; public class StopBatteryInverterHandler extends StateHandler { - private Instant lastAttempt = Instant.MIN; - private int attemptCounter = 0; + private final Timeout timeout = Timeout.ofSeconds(GenericManagedEss.TIMEOUT); @Override protected void onEntry(Context context) throws OpenemsNamedException { - this.lastAttempt = Instant.MIN; - this.attemptCounter = 0; - var ess = context.getParent(); - ess._setMaxBatteryInverterStopAttemptsFault(false); + this.timeout.start(context.clock); } @Override public State runAndGetNextState(Context context) throws OpenemsNamedException { - var ess = context.getParent(); + final var ess = context.getParent(); + + if (context.hasEssFaults()) { + return State.ERROR; + } if (context.batteryInverter.isStopped()) { return State.STOP_BATTERY; } - var isMaxStartTimePassed = Duration.between(this.lastAttempt, Instant.now()) - .getSeconds() > GenericManagedEss.RETRY_COMMAND_SECONDS; - if (!isMaxStartTimePassed) { - // Still waiting... - return State.STOP_BATTERY_INVERTER; + // Is max allowed start time passed ? + if (this.timeout.elapsed(context.clock)) { + ess._setTimeoutStopBatteryInverter(true); + return State.ERROR; } - if (this.attemptCounter > GenericManagedEss.RETRY_COMMAND_MAX_ATTEMPTS) { - // Too many tries - ess._setMaxBatteryInverterStopAttemptsFault(true); - return State.UNDEFINED; - - } else { - // Trying to stop Battery Inverter - context.batteryInverter.stop(); - this.lastAttempt = Instant.now(); - this.attemptCounter++; - return State.STOP_BATTERY_INVERTER; - - } + context.batteryInverter.stop(); + return State.STOP_BATTERY_INVERTER; } - } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StoppedHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StoppedHandler.java index f098749f006..867a9bbe7c5 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StoppedHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/StoppedHandler.java @@ -8,23 +8,17 @@ public class StoppedHandler extends StateHandler { @Override public State runAndGetNextState(Context context) { - var ess = context.getParent(); + final var ess = context.getParent(); - if (ess.hasFaults()) { - return State.UNDEFINED; + if (context.hasEssFaults()) { + return State.ERROR; } - if (!context.battery.isStopped()) { - return State.UNDEFINED; + if (!context.isEssStopped()) { + return State.ERROR; } - if (!context.batteryInverter.isStopped()) { - return State.UNDEFINED; - } - - // Mark as stopped ess._setStartStop(StartStop.STOP); - return State.STOPPED; } diff --git a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/UndefinedHandler.java b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/UndefinedHandler.java index 2075544f3ed..44d8c454b91 100644 --- a/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/UndefinedHandler.java +++ b/io.openems.edge.ess.generic/src/io/openems/edge/ess/generic/symmetric/statemachine/UndefinedHandler.java @@ -7,33 +7,17 @@ public class UndefinedHandler extends StateHandler { @Override public State runAndGetNextState(Context context) { - var ess = context.getParent(); - switch (ess.getStartStopTarget()) { - case UNDEFINED: - // Stuck in UNDEFINED State - return State.UNDEFINED; - - case START: - // force START - if (ess.hasFaults()) { - // TODO should we consider also Battery-Inverter and Battery Faults? - // TODO should the Modbus-Device also be on error, when then Modbus-Bridge is on - // error? - - // Has Faults -> error handling - return State.ERROR; - } else { - // No Faults -> start - return State.START_BATTERY; + final var ess = context.getParent(); + return switch (ess.getStartStopTarget()) { + case UNDEFINED -> State.UNDEFINED; + case START -> { + if (ess.hasFaults() || context.batteryInverter.hasFaults()) { + yield State.ERROR; } - - case STOP: - // force STOP - return State.STOP_BATTERY_INVERTER; + yield State.START_BATTERY; } - - assert false; - return State.UNDEFINED; // can never happen + case STOP -> State.STOP_BATTERY_INVERTER; + }; } } diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java index ac2258c43ba..10a90e7e888 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java @@ -133,4 +133,36 @@ public void testDebugLog() throws Exception { assertEquals("Started|SoC:60 %|L:0 W|Allowed:-56000;46550", sut.debugLog()); } + @Test + public void testTimeout() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + new ComponentTest(new EssGenericManagedSymmetricImpl()) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID)) // + .addReference("battery", new DummyBattery(BATTERY_ID)) // + .activate(MyConfig.create() // + .setId(ESS_ID) // + .setStartStopConfig(StartStopConfig.START) // + .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setBatteryId(BATTERY_ID) // + .build()) // + .next(new TestCase() // + .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .next(new TestCase() // + .output(ESS_STATE_MACHINE, State.START_BATTERY)) // + .next(new TestCase("Start the Battery") // + .input(BATTERY_START_STOP, StartStop.START)) // + .next(new TestCase() // + .output(ESS_STATE_MACHINE, State.START_BATTERY_INVERTER)) // + .next(new TestCase()// + .input(BATTERY_INVERTER_START_STOP, StartStop.STOP)// + .timeleap(clock, 350, ChronoUnit.SECONDS)// + )// + .next(new TestCase() // + .output(ESS_STATE_MACHINE, State.ERROR)) // + .next(new TestCase() // + .output(ESS_STATE_MACHINE, State.ERROR)) // + ; + } } diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java index 4029d0495fd..60b9b2b7623 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java @@ -66,8 +66,8 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { RAW_METER_SERIALNUMBER(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "meter", "serialnumber"), // RAW_METER_TYPE(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "meter", "type"), // - METER_NOT_AVAILABLE(Doc.of(Level.INFO) // - .text("No meter values available. The communication cable of the internal meter may be loose.")), // + METER_NOT_AVAILABLE(Doc.of(Level.WARNING) // + .translationKey(EvcsHardyBarth.class, "noMeterAvailable")), // RAW_METER_AVAILABLE(new BooleanDoc()// .onChannelSetNextValue((hardyBarth, value) -> { var notAvailable = value.get() == null ? null : !value.get(); diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_de.properties b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_de.properties new file mode 100644 index 00000000000..8c5792bf010 --- /dev/null +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_de.properties @@ -0,0 +1 @@ +noMeterAvailable = Keine Zählerwerte verfügbar. Das Kommunikationskabel des (internen) Zählers ist möglicherweise lose. \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_en.properties b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_en.properties new file mode 100644 index 00000000000..4263f535f21 --- /dev/null +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/translation_en.properties @@ -0,0 +1 @@ +noMeterAvailable = No meter values available. The communication cable of the (internal) meter may be loose. \ No newline at end of file diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java index e392f6cec85..1b4e637e250 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java @@ -119,8 +119,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { DIP_SWITCH_ERROR_2_6_NOT_SET_FOR_STATIC_IP(Doc.of(Level.FAULT) // .debounce(5, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // .text("A static ip is configured. The Dip-Switch 2.6. must be on")), // - DIP_SWITCH_ERROR_2_6_SET_FOR_DYNAMIC_IP(Doc.of(Level.FAULT) // - .debounce(5, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // + DIP_SWITCH_ERROR_2_6_SET_FOR_DYNAMIC_IP(Doc.of(OpenemsType.BOOLEAN) // .text("A dynamic ip is configured. Either the Dip-Switch 2.6. must be off or a static ip has to be configured")), // DIP_SWITCH_INFO_2_5_SET_FOR_MASTER_SLAVE_COMM(Doc.of(Level.INFO) // .debounce(5, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverter.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverter.java index fd50c862023..c8e725dca44 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverter.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverter.java @@ -16,7 +16,7 @@ public interface GoodWeBatteryInverter public static enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATE_MACHINE(Doc.of(State.values()) // .text("Current State of State-Machine")), // - RUN_FAILED(Doc.of(Level.FAULT) // + RUN_FAILED(Doc.of(Level.WARNING) // .text("Running the Logic failed")); // private final Doc doc; diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java index 57f17030ddd..1b40ca0444b 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImpl.java @@ -115,7 +115,7 @@ protected void setModbus(BridgeModbus modbus) { protected static record BatteryData(Integer chargeMaxCurrent, Integer voltage) { } - private Config config; + private Config config = null; public GoodWeBatteryInverterImpl() throws OpenemsNamedException { super(// @@ -167,7 +167,7 @@ protected void deactivate() { @Override public void handleEvent(Event event) { - if (!this.isEnabled()) { + if (this.config == null || !this.isEnabled()) { return; } super.handleEvent(event); diff --git a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java index aa090eaa2b3..8fd557e1ffc 100644 --- a/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java +++ b/io.openems.edge.goodwe/src/io/openems/edge/goodwe/common/GoodWe.java @@ -254,12 +254,12 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .unit(Unit.KILOWATT_HOURS)), // // Error Message 35189 - STATE_0(Doc.of(Level.FAULT) // + STATE_0(Doc.of(Level.WARNING) // .text("The Ground Fault Circuit Interrupter (GFCI) detecting circuit is abnormal " // + "| Interne Fehlerstrom-Schutzeinrichtung (RCD Einheit) wurde ausgelöst " // + "| Bitte überprüfen Sie den Netzanschluss sowie ggf. Backup-Lasten")), // - STATE_1(Doc.of(Level.FAULT) // + STATE_1(Doc.of(Level.WARNING) // .text("The output current sensor is abnormal " // + "| Der Ausgangs-Stromsensor liefert unplausible Werte " // + "| Bitte überprüfen Sie die Installation")), // @@ -269,12 +269,12 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_2(Doc.of(Level.WARNING) // .text("Warning Code 1")), // - STATE_3(Doc.of(Level.FAULT) // + STATE_3(Doc.of(Level.WARNING) // .text("DCI Consistency Failure " // + "| Werte der Impedanzmessung (DCI Einheit) sind widersprüchlich/unplausibel " // + "| Bitte überprüfen Sie den Netzanschluss")), // - STATE_4(Doc.of(Level.FAULT) // + STATE_4(Doc.of(Level.WARNING) // .text("Ground Fault Circuit Interrupter (GFCI) Consistency Failure " // + "| Werte der internen Fehlerstrom-Schutzeinrichtung (RCD) sind widersprüchlich/unplausibel " // + "| Bitte überprüfen Sie den Netzanschluss")), // @@ -284,29 +284,29 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_5(Doc.of(Level.WARNING) // .text("Warning Code 2")), // - STATE_6(Doc.of(Level.FAULT) // + STATE_6(Doc.of(Level.WARNING) // .text("Ground Fault Circuit Interrupter (GFCI) Device Failure " // + "| Interne Fehlerstrom-Schutzeinrichtung (RCD Einheit) befindet sich im Fehlerzustand " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_7(Doc.of(Level.FAULT) // + STATE_7(Doc.of(Level.WARNING) // .text("Relay Device Failure " // + "| Interne Relais befinden sich im Fehlerzustand " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_8(Doc.of(Level.FAULT) // + STATE_8(Doc.of(Level.WARNING) // .text("AC HCT Failure " // + "| Die HCT Einheit befindet sich im Fehlerzustand " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_9(Doc.of(Level.FAULT) // + STATE_9(Doc.of(Level.WARNING) // .text("Utility Loss " // + "| Netzausfall wurde erkannt " // + "| Bitte überprüfen Sie ob das Kommunikationsmodul richtig gesteckt ist")), // // TODO: Use new-lines or html-lists when the UI and edge log are able to handle // them - STATE_10(Doc.of(Level.FAULT) // + STATE_10(Doc.of(Level.WARNING) // .text("Ground I Failure " // + "| Erdungsfehler " // + "| Ggf. N und PE Leiter sind nicht richtig mit dem Netzanschluss des Wechselrichters verbunden. " // @@ -320,7 +320,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId + "| Ggf. übersteigt die Leerlauf- oder Betriebsspannung der PV-Module den für diesen Wechselrichter zulässigen Bereich. " // + "Ggf. liegt ein PV-Kriechstrom zur Erde an")), // - STATE_12(Doc.of(Level.FAULT) // + STATE_12(Doc.of(Level.WARNING) // .text("Internal Fan Failure " // + "| Der interne Lüfter meldet einen Defekt")), // @@ -331,13 +331,13 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId + "Ggf. Luftstrom durch den Kühlkörper für Normalbetrieb unzureichend (Aufstellbedingungen beachten!). " + "Ggf. Behinderung des Luftstroms, z.B. Kühlkörper wurde abgedeckt")), // - STATE_14(Doc.of(Level.FAULT) // + STATE_14(Doc.of(Level.WARNING) // .text("Utility Phase Failure " // + "| Phasenfehler " // + "| Ãœberprüfen Sie das Drehfeld am Wechselrichter. " // + "Ggf. Kommunikationsadapter (ET+) nicht (richtig) gesteckt")), // - STATE_15(Doc.of(Level.FAULT) // + STATE_15(Doc.of(Level.WARNING) // .text("PV Over Voltage " // + "| Ãœberspannung PV " // + "| Bitte überprüfen Sie die Installation")), // @@ -346,13 +346,13 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("External Fan Failure " // + "| Externer Lüfter befindet sich im Fehlerzustand")), // - STATE_17(Doc.of(Level.FAULT) // + STATE_17(Doc.of(Level.WARNING) // .text("Vac Failure " // + "| Spannungsfehler " // + "| Die anliegende Spannung am \"On-Grid\" Anschluss befindet sich außerhalb der gültigen Parameter (für DE siehe VDE AR N 4105). " // + "Ggf. Kommunikationsmodul nicht (richtig) gesteckt")), // - STATE_18(Doc.of(Level.FAULT) // + STATE_18(Doc.of(Level.WARNING) // .text("Isolation resistance of PV-plant too low " // + "| Isolationsfehler auf PV-Strings " // + "| Bitte überprüfen Sie die Installation")), // @@ -362,7 +362,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId + "| DC-Strom Einspeisung auf \"On-Grid\" Seite ist zu hoch " // + "| Bitte überprüfen Sie die Installation und angeschlossene Verbraucher bzw. Erzeuger")), // - STATE_20(Doc.of(Level.FAULT) // + STATE_20(Doc.of(Level.WARNING) // .text("Back-Up Over Load " // + "| Ãœberlastung Backup-Anschluss " // + "| Bitte beachten Sie die im Datenblatt angegebenen Maximal-Lasten")), // @@ -372,12 +372,12 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_21(Doc.of(Level.WARNING) // .text("Warning Code 3")), // - STATE_22(Doc.of(Level.FAULT) // + STATE_22(Doc.of(Level.WARNING) // .text("Difference between Master and Slave frequency too high " // + "| Frequenz zwischen Master und Slave weicht zu stark ab " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_23(Doc.of(Level.FAULT) // + STATE_23(Doc.of(Level.WARNING) // .text("Difference between Master and Slave voltage too high " // + "| Spannung zwischen Master und Slave weicht zu stark ab " // + "| Bitte führen Sie einen Geräteneustart aus")), // @@ -387,7 +387,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId STATE_24(Doc.of(Level.WARNING) // .text("Warning Code 4")), // - STATE_25(Doc.of(Level.FAULT) // + STATE_25(Doc.of(Level.WARNING) // .text("Relay Check Failure " // + "| Selbsttest der Relais ist Fehlgeschlagen " // + "| Ggf. sind N und PE-Leiter nicht richtig mit den Anschlussklemmen des Wechselrichters verbunden. " // @@ -409,17 +409,17 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId + "| Kommunikation zwischen der ARM und DSP Einheit ist fehlgeschlagen " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_29(Doc.of(Level.FAULT) // + STATE_29(Doc.of(Level.WARNING) // .text("The grid frequency is out of tolerable range " // + "| Die Netz-Frequenz befindet sich außerhalb der zulässigen Parameter " // + "| Bitte überprüfen Sie die Installation und führen anschließend einen Geräteneustart aus")), // - STATE_30(Doc.of(Level.FAULT) // + STATE_30(Doc.of(Level.WARNING) // .text("EEPROM cannot be read or written " // + "| EEPROM kann nicht gelesen oder geschrieben werden " // + "| Bitte führen Sie einen Geräteneustart aus")), // - STATE_31(Doc.of(Level.FAULT) // + STATE_31(Doc.of(Level.WARNING) // .text("Communication failure between microcontrollers " // + "| Die Kommunikation zwischen den einzelnen Microkontrollern ist fehlerhaft " // + "| Bitte führen Sie einen Geräteneustart aus")), // @@ -1642,7 +1642,7 @@ public static enum ChannelId implements io.openems.edge.common.channel.ChannelId .text("SMART mode does not work correctly with active PID filter")), NO_SMART_METER_DETECTED(Doc.of(Level.WARNING) // .text("No GoodWe Smart Meter detected. Only REMOTE mode can work correctly")), - IMPOSSIBLE_FENECON_HOME_COMBINATION(Doc.of(Level.FAULT) // + IMPOSSIBLE_FENECON_HOME_COMBINATION(Doc.of(Level.WARNING) // .text("The installed inverter and battery combination is not authorised. Operation could cause hardware damages, so charging and discharging is blocked. Please install a complete Home 10, Home 20 or Home 30 system.")), // IGNORE_IMPOSSIBLE_P_BATTERY_VALUE(Doc.of(OpenemsType.BOOLEAN) // .text("Ignore impossible battery power")) // diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/Config.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/Config.java index 0c62512f586..abaa040ae8f 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/Config.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/Config.java @@ -20,6 +20,13 @@ @AttributeDefinition(name = "Datasource-ID", description = "ID of Simulator Datasource.") String datasource_id() default "datasource0"; + @AttributeDefinition(name = "Need frequency Step response?", description = "Need frequency Step response?") + boolean needFrequencyStepResponse() default false; + + @AttributeDefinition(name = "Start Time for frequency step response", description = "Time to Start(format: 2024-01-29 20:12:00). " + + "if the time specified is not in future or start time is not entered, Current time is used instead .") + String startTime() default "2024-01-29 20:12:00"; + @AttributeDefinition(name = "Datasource target filter", description = "This is auto-generated by 'Datasource-ID'.") String datasource_target() default "(enabled=true)"; diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActing.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActing.java index 578f38db722..c7b5b733618 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActing.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActing.java @@ -2,9 +2,13 @@ import org.osgi.service.event.EventHandler; +import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.StateChannel; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.TimedataProvider; @@ -12,6 +16,10 @@ public interface SimulatorGridMeterActing extends ElectricityMeter, OpenemsComponent, TimedataProvider, EventHandler { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + STATE_MACHINE(Doc.of(State.values()) // + .persistencePriority(PersistencePriority.HIGH)// + .text("Current State of State-Machine")), + SIMULATED_ACTIVE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT)); @@ -26,4 +34,32 @@ public Doc doc() { return this.doc; } } + + /** + * Gets the Channel for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel + */ + public default Channel getStateMachineChannel() { + return this.channel(ChannelId.STATE_MACHINE); + } + + /** + * Gets the {@link StateChannel} for {@link ChannelId#STATE_MACHINE}. + * + * @return the Channel {@link Value} + */ + public default Value getStateMachine() { + return this.getStateMachineChannel().value(); + } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#STATE_MACHINE} + * Channel. + * + * @param value the next value + */ + public default void _setStateMachine(State value) { + this.getStateMachineChannel().setNextValue(value); + } } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java index 1a60a481d58..215d25ac505 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java @@ -1,6 +1,11 @@ package io.openems.edge.simulator.meter.grid.acting; import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -22,6 +27,7 @@ import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.common.type.TypeUtils; @@ -33,6 +39,8 @@ import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Designate(ocd = Config.class, factory = true) @Component(// @@ -49,11 +57,18 @@ public class SimulatorGridMeterActingImpl extends AbstractOpenemsComponent implements SimulatorGridMeterActing, ElectricityMeter, OpenemsComponent, TimedataProvider, EventHandler { + private final Logger log = LoggerFactory.getLogger(SimulatorGridMeterActingImpl.class); private final CalculateEnergyFromPower calculateProductionEnergy = new CalculateEnergyFromPower(this, ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY); private final CalculateEnergyFromPower calculateConsumptionEnergy = new CalculateEnergyFromPower(this, ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY); + private StepResponseHandler stepResponseHandler; + private Config config = null; + + @Reference + private ComponentManager componentManager; + @Reference private ConfigurationAdmin cm; @@ -72,16 +87,23 @@ public SimulatorGridMeterActingImpl() { ElectricityMeter.ChannelId.values(), // SimulatorGridMeterActing.ChannelId.values() // ); + } @Activate private void activate(ComponentContext context, Config config) throws IOException { + this.config = config; super.activate(context, config.id(), config.alias(), config.enabled()); // update filter for 'datasource' if (OpenemsComponent.updateReferenceFilter(this.cm, this.servicePid(), "datasource", config.datasource_id())) { return; } + + if (this.config.needFrequencyStepResponse()) { + Instant startTime = this.convertTime(this.config.startTime()); + this.stepResponseHandler = new StepResponseHandler(this, startTime); + } } @Override @@ -103,6 +125,9 @@ public void handleEvent(Event event) { switch (event.getTopic()) { case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: this.updateChannels(); + if (this.config.needFrequencyStepResponse()) { + this.stepResponseHandler.doStepResponse(); + } break; case EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE: this.calculateEnergy(); @@ -168,4 +193,44 @@ public Timedata getTimedata() { return this.timedata; } + /** + * Converts a string representation of time to an Instant object. + *

+ * If the input time is null or empty, the current time is returned. If the + * input time is successfully parsed, it is converted to an Instant object. If + * the parsed time is in the past, and the current time is returned. If there's + * an error parsing the input time, and the current time is returned. + *

+ * + * @param inputTime the string representation of time to be converted + * @return an Instant converted time + */ + public Instant convertTime(String inputTime) { + + Instant currentTime = this.getCurrentTime(); + if (inputTime == null || inputTime.isEmpty()) { + return currentTime; + } + try { + LocalDateTime localDateTime = LocalDateTime.parse(inputTime, + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + Instant futureTime = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); + return currentTime.isAfter(futureTime) ? currentTime : futureTime; + } catch (DateTimeParseException e) { + this.log.error( + "Error parsing input time: " + inputTime + " instead current time: " + currentTime + " is taken."); + return currentTime; + } + } + + /** + * Retrieves the current time component manager. + * + * @return An Instant representing the current time. + */ + public Instant getCurrentTime() { + var currentTime = this.componentManager.getClock().withZone(ZoneId.systemDefault()); + return Instant.now(currentTime); + } + } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/State.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/State.java new file mode 100644 index 00000000000..e5c26f2332c --- /dev/null +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/State.java @@ -0,0 +1,33 @@ +package io.openems.edge.simulator.meter.grid.acting; + +import io.openems.common.types.OptionsEnum; + +public enum State implements OptionsEnum { + UNDEFINED(-1), // + INITIAL_FREQ(1), // + FIRST_STEPDOWN_FREQUENCY(2), // + SECOND_STEPDOWN_FREQUENCY(3), // + FINISH(4); + + private final int value; + + private State(int value) { + this.value = value; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + + @Override + public String getName() { + return this.name(); + } + +} diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/StepResponseHandler.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/StepResponseHandler.java new file mode 100644 index 00000000000..5a75825775e --- /dev/null +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/StepResponseHandler.java @@ -0,0 +1,57 @@ +package io.openems.edge.simulator.meter.grid.acting; + +import java.time.Duration; +import java.time.Instant; + +public class StepResponseHandler { + + private State state; + private int repetitionCounter; + private SimulatorGridMeterActingImpl meter; + private Instant startTime; + + private static int TIME_THRESHOLD_SECONDS = 120; // [120 seconds in each step] + + public StepResponseHandler(SimulatorGridMeterActingImpl meter, Instant startTime) { + this.state = State.UNDEFINED; + // repeats atleast once + this.repetitionCounter = 1; + this.meter = meter; + this.startTime = startTime; + } + + /** + * State Machine for frequency step response. + */ + public void doStepResponse() { + switch (this.state) { + case UNDEFINED -> this.state = State.INITIAL_FREQ; + case INITIAL_FREQ -> this.handleStateTransition(50000, State.FIRST_STEPDOWN_FREQUENCY); + case FIRST_STEPDOWN_FREQUENCY -> this.handleStateTransition(49750, State.SECOND_STEPDOWN_FREQUENCY); + case SECOND_STEPDOWN_FREQUENCY -> this.handleStateTransition(49650, State.FINISH); + case FINISH -> { + if (this.repetitionCounter <= 1) { + this.handleFinishTransition(50000, State.FINISH); + } else { + this.repetitionCounter--; + this.handleFinishTransition(50000, State.INITIAL_FREQ); + } + } + } + this.meter._setStateMachine(this.state); + } + + private void handleStateTransition(int frequency, State nextState) { + var now = this.meter.getCurrentTime(); + this.meter._setFrequency(frequency); + if (Duration.between(this.startTime, now).getSeconds() > TIME_THRESHOLD_SECONDS) { + this.startTime = now; + this.state = nextState; + } + } + + private void handleFinishTransition(int frequency, State nextState) { + this.meter._setFrequency(frequency); + this.state = nextState; + } +} diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/MyConfig.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/MyConfig.java index 6367e673ebd..04a1c548104 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/MyConfig.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/MyConfig.java @@ -9,6 +9,8 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id; private String datasourceId; + private String startTime; + private boolean needFrequencyStepResponse; private Builder() { } @@ -23,6 +25,16 @@ public Builder setDatasourceId(String datasourceId) { return this; } + public Builder setStartTime(String startTime) { + this.startTime = startTime; + return this; + } + + public Builder needFrequencyStepResponse(boolean needFrequencyStepResponse) { + this.needFrequencyStepResponse = needFrequencyStepResponse; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -54,4 +66,14 @@ public String datasource_target() { return ConfigUtils.generateReferenceTargetFilter(this.id(), this.datasource_id()); } + @Override + public boolean needFrequencyStepResponse() { + return this.builder.needFrequencyStepResponse; + } + + @Override + public String startTime() { + return this.builder.startTime; + } + } \ No newline at end of file diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java index 32fa540b54c..59d29433d83 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java @@ -1,9 +1,14 @@ package io.openems.edge.simulator.meter.grid.acting; +import java.time.Instant; +import java.time.ZoneOffset; + import org.junit.Test; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.simulator.datasource.csv.direct.SimulatorDatasourceCsvDirectImpl; @@ -19,6 +24,24 @@ public void test() throws OpenemsException, Exception { .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // .activate(MyConfig.create() // .setId(COMPONENT_ID) // + .setStartTime("")// + .needFrequencyStepResponse(false)// + .setDatasourceId(DATASOURCE_ID) // + .build()); // + // .next(new TestCase()); // TODO requires DummyDatasource + } + + @Test + public void test1() throws OpenemsException, Exception { + final var clock = new TimeLeapClock(Instant.parse("2024-01-29T19:05:00Z"), ZoneOffset.UTC); + new ComponentTest(new SimulatorGridMeterActingImpl()) // + .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("cm", new DummyConfigurationAdmin()) // + .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // + .activate(MyConfig.create() // + .setId(COMPONENT_ID) // + .setStartTime("")// + .needFrequencyStepResponse(true)// .setDatasourceId(DATASOURCE_ID) // .build()); // // .next(new TestCase()); // TODO requires DummyDatasource diff --git a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java index e12637892de..0be558ae28e 100644 --- a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java +++ b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jReadHandler.java @@ -552,8 +552,7 @@ public CompletableFuture> getLatestValue(// try { channel = this.componentManager.getChannel(channelAddress); } catch (Exception e) { - // unable to get channel - this.log.warn("Unable to query RRD4j", e); + this.log.warn("Unable to query [" + channelAddress + "] from RRD4j: " + e.getMessage()); return Optional.empty(); } diff --git a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java index fc0d4f2b50b..06c62d9e289 100644 --- a/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java +++ b/io.openems.edge.timedata.rrd4j/src/io/openems/edge/timedata/rrd4j/Rrd4jSupplier.java @@ -143,7 +143,7 @@ public static ChannelDef getDsDefForChannel(final Unit channelUnit) { MICROVOLT, MILLIAMPERE_HOURS, MILLIAMPERE, MILLIHERTZ, MILLIOHM, MILLISECONDS, MILLIVOLT, MILLIWATT, MINUTE, NONE, WATT, VOLT, VOLT_AMPERE, VOLT_AMPERE_REACTIVE, WATT_HOURS_BY_WATT_PEAK, OHM, SECONDS, THOUSANDTH, WATT_HOURS, KILOWATT_HOURS, VOLT_AMPERE_HOURS, VOLT_AMPERE_REACTIVE_HOURS, - KILOVOLT_AMPERE_REACTIVE_HOURS, BAR -> + KILOVOLT_AMPERE_REACTIVE_HOURS, BAR, MILLIBAR -> new ChannelDef(DsType.GAUGE, Double.NaN, Double.NaN, ConsolFun.AVERAGE); case PERCENT -> new ChannelDef(DsType.GAUGE, 0, 100, ConsolFun.AVERAGE); case ON_OFF -> new ChannelDef(DsType.GAUGE, 0, 1, ConsolFun.AVERAGE); diff --git a/io.openems.wrapper/bnd.bnd b/io.openems.wrapper/bnd.bnd index d024f94a589..c1d497e0a9f 100644 --- a/io.openems.wrapper/bnd.bnd +++ b/io.openems.wrapper/bnd.bnd @@ -18,6 +18,10 @@ Bundle-Description: This wraps external java libraries that do not have OSGi hea eu.chargetime.ocpp:OCPP-J;version='1.0.2',\ eu.chargetime.ocpp:common;version='1.0.2',\ eu.chargetime.ocpp:v1_6;version='1.1.0',\ + io.helins:linux-common;version='0.1.4',\ + io.helins:linux-errno;version='1.0.2',\ + io.helins:linux-i2c;version='1.0.2',\ + io.helins:linux-io;version='0.0.4',\ io.jenetics:jenetics;version='7.2.0',\ info.faljse:SDNotify;version='1.5.0',\ io.reactivex.rxjava3.rxjava;version='3.1.8',\ diff --git a/io.openems.wrapper/helins-linux-i2c.bnd b/io.openems.wrapper/helins-linux-i2c.bnd new file mode 100644 index 00000000000..1bda4835a21 --- /dev/null +++ b/io.openems.wrapper/helins-linux-i2c.bnd @@ -0,0 +1,20 @@ +Bundle-Name: Helins linux-i2c lib +Bundle-SymbolicName: io.openems.wrapper.helins-linux-i2c +Bundle-DocURL: https://github.com/helins/linux-i2c.jav +Bundle-License: https://opensource.org/license/mpl-2-0 +Bundle-Version: 1.0.2 + +Include-Resource: \ + @linux-i2c-1.0.2.jar,\ + @linux-common-0.1.4.jar,\ + @linux-errno-1.0.2.jar,\ + @linux-io-0.0.4.jar,\ + +-dsannotations: * + +-metatypeannotations: * + +Export-Package: \ + io.helins.linux.i2c,\ + +-sources: false \ No newline at end of file diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json index 8a3902ecddb..0c5b77ab6af 100644 --- a/ui/.eslintrc.json +++ b/ui/.eslintrc.json @@ -99,6 +99,17 @@ { "allowDefaultCaseForExhaustiveSwitch": false } + ], + "no-restricted-syntax": [ + "error", + { + "selector": "CallExpression[callee.name='fdescribe']", + "message": "Using 'fdescribe' is not allowed." + }, + { + "selector": "CallExpression[callee.name='xdescribe']", + "message": "Using 'xdescribe' is not allowed." + } ] } }, diff --git a/ui/angular.json b/ui/angular.json index e1fba2f0688..11947ebaaeb 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -70,7 +70,8 @@ }, "styles": [ "src/themes/openems/scss/variables.scss", - "src/global.scss" + "src/global.scss", + "src/global-ion-custom.scss" ] }, "openems-backend-dev": { @@ -162,23 +163,23 @@ "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { - "browserTarget": "app:build:openems,openems-edge-dev" + "buildTarget": "app:build:openems,openems-edge-dev" }, "configurations": { "openems-backend-dev": { - "browserTarget": "app:build:openems,openems-backend-dev" + "buildTarget": "app:build:openems,openems-backend-dev" }, "openems-edge-dev": { - "browserTarget": "app:build:openems,openems-edge-dev" + "buildTarget": "app:build:openems,openems-edge-dev" }, "openems-backend-prod": { - "browserTarget": "app:build:openems,openems-backend-prod,prod" + "buildTarget": "app:build:openems,openems-backend-prod,prod" }, "openems-edge-prod": { - "browserTarget": "app:build:openems,openems-edge-prod,prod" + "buildTarget": "app:build:openems,openems-edge-prod,prod" }, "openems-gitpod": { - "browserTarget": "app:build:openems,openems-gitpod" + "buildTarget": "app:build:openems,openems-gitpod" } } }, diff --git a/ui/package-lock.json b/ui/package-lock.json index 2dd132f8d58..4641fa8ea1b 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -57,7 +57,7 @@ "zone.js": "~0.14.7" }, "devDependencies": { - "@angular-devkit/build-angular": "^18.2.5", + "@angular-devkit/build-angular": "^18.2.8", "@angular-devkit/core": "18.2.5", "@angular-devkit/schematics": "18.2.5", "@angular-eslint/builder": "^18.3.1", @@ -97,7 +97,7 @@ "karma-coverage-istanbul-reporter": "~3.0.3", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "^2.1.0", - "protractor": "~7.0.0", + "protractor": "^7.0.0", "ts-node": "^10.9.2", "typescript": "~5.4.5", "typescript-strict-plugin": "^2.4.4" @@ -142,17 +142,17 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.6.tgz", - "integrity": "sha512-u12cJZttgs5j7gICHWSmcaTCu0EFXEzKqI8nkYCwq2MtuJlAXiMQSXYuEP9OU3Go4vMAPtQh2kShyOWCX5b4EQ==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.8.tgz", + "integrity": "sha512-qK/iLk7A8vQp1CyiJV4DpwfLjPKoiOlTtFqoO5vD8Tyxmc+R06FQp6GJTsZ7JtrTLYSiH+QAWiY6NgF/Rj/hHg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.6", - "@angular-devkit/build-webpack": "0.1802.6", - "@angular-devkit/core": "18.2.6", - "@angular/build": "18.2.6", + "@angular-devkit/architect": "0.1802.8", + "@angular-devkit/build-webpack": "0.1802.8", + "@angular-devkit/core": "18.2.8", + "@angular/build": "18.2.8", "@babel/core": "7.25.2", "@babel/generator": "7.25.0", "@babel/helper-annotate-as-pure": "7.24.7", @@ -163,7 +163,7 @@ "@babel/preset-env": "7.25.3", "@babel/runtime": "7.25.0", "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.6", + "@ngtools/webpack": "18.2.8", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", @@ -271,13 +271,13 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1802.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.6.tgz", - "integrity": "sha512-oF7cPFdTLxeuvXkK/opSdIxZ1E4LrBbmuytQ/nCoAGOaKBWdqvwagRZ6jVhaI0Gwu48rkcV7Zhesg/ESNnROdw==", + "version": "0.1802.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.8.tgz", + "integrity": "sha512-/rtFQEKgS7LlB9oHr4NCBSdKnvP5kr8L5Hbd3Vl8hZOYK9QWjxKPEXnryA2d5+PCE98bBzZswCNXqELZCPTgIQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.6", + "@angular-devkit/core": "18.2.8", "rxjs": "7.8.1" }, "engines": { @@ -287,9 +287,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.6.tgz", - "integrity": "sha512-la4CFvs5PcRWSkQ/H7TB5cPZirFVA9GoWk5LzIk8si6VjWBJRm8b3keKJoC9LlNeABRUIR5z0ocYkyQQUhdMfg==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.8.tgz", + "integrity": "sha512-4o2T6wsmXGE/v53+F8L7kGoN2+qzt03C9rtjLVQpOljzpJVttQ8bhvfWxyYLWwcl04RWqRa+82fpIZtBkOlZJw==", "dev": true, "license": "MIT", "dependencies": { @@ -315,14 +315,14 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular/build": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.6.tgz", - "integrity": "sha512-TQzX6Mi7uXFvmz7+OVl4Za7WawYPcx+B5Ewm6IY/DdMyB9P/Z4tbKb1LO+ynWUXYwm7avXo6XQQ4m5ArDY5F/A==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.8.tgz", + "integrity": "sha512-ufuA4vHJSrL9SQW7bKV61DOoN1mm0t0ILTHaxSoCG3YF70cZJOX7+HNp3cK2uoldRMwbTOKSvCWBw54KKDRd5Q==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/architect": "0.1802.8", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", @@ -383,6 +383,38 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.22.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", @@ -614,6 +646,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@angular-devkit/build-angular/node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/rollup": { "version": "4.22.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", @@ -655,6 +700,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -663,16 +709,17 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.6.tgz", - "integrity": "sha512-JMLcXFaitJplwZMKkqhbYirINCRD6eOPZuIGaIOVynXYGWgvJkLT9t5C2wm9HqSLtp1K7NcYG2Y7PtTVR4krnQ==", + "version": "0.1802.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.8.tgz", + "integrity": "sha512-uPpopkXkO66SSdjtVr7xCyQCPs/x6KUC76xkDc4j0b8EEHifTbi/fNpbkcZ6wBmoAfjKLWXfKvtkh0TqKK5Hkw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.6", + "@angular-devkit/architect": "0.1802.8", "rxjs": "7.8.1" }, "engines": { @@ -686,13 +733,13 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.1802.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.6.tgz", - "integrity": "sha512-oF7cPFdTLxeuvXkK/opSdIxZ1E4LrBbmuytQ/nCoAGOaKBWdqvwagRZ6jVhaI0Gwu48rkcV7Zhesg/ESNnROdw==", + "version": "0.1802.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.8.tgz", + "integrity": "sha512-/rtFQEKgS7LlB9oHr4NCBSdKnvP5kr8L5Hbd3Vl8hZOYK9QWjxKPEXnryA2d5+PCE98bBzZswCNXqELZCPTgIQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.6", + "@angular-devkit/core": "18.2.8", "rxjs": "7.8.1" }, "engines": { @@ -702,9 +749,9 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.6.tgz", - "integrity": "sha512-la4CFvs5PcRWSkQ/H7TB5cPZirFVA9GoWk5LzIk8si6VjWBJRm8b3keKJoC9LlNeABRUIR5z0ocYkyQQUhdMfg==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.8.tgz", + "integrity": "sha512-4o2T6wsmXGE/v53+F8L7kGoN2+qzt03C9rtjLVQpOljzpJVttQ8bhvfWxyYLWwcl04RWqRa+82fpIZtBkOlZJw==", "dev": true, "license": "MIT", "dependencies": { @@ -1139,13 +1186,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/highlight": "^7.25.7", "picocolors": "^1.0.0" }, "engines": { @@ -1153,9 +1200,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", "dev": true, "license": "MIT", "engines": { @@ -1211,16 +1258,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.0", + "@babel/types": "^7.25.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" @@ -1240,29 +1287,29 @@ } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", - "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.7.tgz", + "integrity": "sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -1281,18 +1328,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", - "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz", + "integrity": "sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-member-expression-to-functions": "^7.25.7", + "@babel/helper-optimise-call-expression": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/traverse": "^7.25.7", "semver": "^6.3.1" }, "engines": { @@ -1302,6 +1349,19 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1313,14 +1373,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", - "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.7.tgz", + "integrity": "sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.7", + "regexpu-core": "^6.1.1", "semver": "^6.3.1" }, "engines": { @@ -1330,6 +1390,19 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1358,44 +1431,44 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz", + "integrity": "sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1405,22 +1478,22 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz", + "integrity": "sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", "dev": true, "license": "MIT", "engines": { @@ -1428,15 +1501,15 @@ } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", - "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.7.tgz", + "integrity": "sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-wrap-function": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1445,16 +1518,29 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-remap-async-to-generator/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz", + "integrity": "sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-member-expression-to-functions": "^7.25.7", + "@babel/helper-optimise-call-expression": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1464,28 +1550,28 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz", + "integrity": "sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1505,9 +1591,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", "dev": true, "license": "MIT", "engines": { @@ -1515,9 +1601,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "dev": true, "license": "MIT", "engines": { @@ -1525,9 +1611,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", "dev": true, "license": "MIT", "engines": { @@ -1535,15 +1621,15 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", - "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.7.tgz", + "integrity": "sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1564,13 +1650,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -1580,13 +1666,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", - "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.4" + "@babel/types": "^7.25.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -1596,14 +1682,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", - "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.7.tgz", + "integrity": "sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.3" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1613,13 +1699,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", - "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.7.tgz", + "integrity": "sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1629,13 +1715,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", - "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.7.tgz", + "integrity": "sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1645,15 +1731,15 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", - "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.7.tgz", + "integrity": "sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/plugin-transform-optional-chaining": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1663,14 +1749,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", - "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.7.tgz", + "integrity": "sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1761,13 +1847,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", - "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.7.tgz", + "integrity": "sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1777,13 +1863,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz", + "integrity": "sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1946,13 +2032,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", - "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.7.tgz", + "integrity": "sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -1999,13 +2085,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", - "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.7.tgz", + "integrity": "sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2015,13 +2101,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", - "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.7.tgz", + "integrity": "sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2031,14 +2117,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", - "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.7.tgz", + "integrity": "sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2048,15 +2134,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", - "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.8.tgz", + "integrity": "sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2066,17 +2151,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", - "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.7.tgz", + "integrity": "sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/traverse": "^7.25.4", + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7", + "@babel/traverse": "^7.25.7", "globals": "^11.1.0" }, "engines": { @@ -2086,15 +2171,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-classes/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", - "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.7.tgz", + "integrity": "sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/template": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/template": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2104,13 +2202,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", - "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.7.tgz", + "integrity": "sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2120,14 +2218,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", - "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.7.tgz", + "integrity": "sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2137,13 +2235,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", - "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.7.tgz", + "integrity": "sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2153,14 +2251,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", - "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.7.tgz", + "integrity": "sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2170,14 +2268,13 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", - "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.8.tgz", + "integrity": "sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2187,14 +2284,14 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", - "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.7.tgz", + "integrity": "sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2204,14 +2301,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", - "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.8.tgz", + "integrity": "sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2221,14 +2317,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", - "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.7.tgz", + "integrity": "sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2238,15 +2334,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", - "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.7.tgz", + "integrity": "sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.1" + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2256,14 +2352,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", - "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.8.tgz", + "integrity": "sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2273,13 +2368,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", - "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.7.tgz", + "integrity": "sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2289,14 +2384,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", - "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.8.tgz", + "integrity": "sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2306,13 +2400,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", - "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.7.tgz", + "integrity": "sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2322,14 +2416,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", - "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.7.tgz", + "integrity": "sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2339,15 +2433,15 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", - "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz", + "integrity": "sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-simple-access": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2357,16 +2451,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", - "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.7.tgz", + "integrity": "sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2376,14 +2470,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", - "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.7.tgz", + "integrity": "sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2393,14 +2487,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", - "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.7.tgz", + "integrity": "sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2410,13 +2504,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", - "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.7.tgz", + "integrity": "sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2426,14 +2520,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", - "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.8.tgz", + "integrity": "sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2443,14 +2536,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", - "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.8.tgz", + "integrity": "sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2460,16 +2552,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", - "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.8.tgz", + "integrity": "sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.7" + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-transform-parameters": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2479,14 +2570,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", - "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.7.tgz", + "integrity": "sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2496,14 +2587,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", - "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.8.tgz", + "integrity": "sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2513,15 +2603,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", - "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.8.tgz", + "integrity": "sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2531,13 +2620,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.7.tgz", + "integrity": "sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2547,14 +2636,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", - "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz", + "integrity": "sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.4", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2564,16 +2653,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", - "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.8.tgz", + "integrity": "sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2582,14 +2670,27 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-private-property-in-object/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", - "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.7.tgz", + "integrity": "sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2599,13 +2700,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", - "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.7.tgz", + "integrity": "sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.25.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -2616,13 +2717,13 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", - "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.7.tgz", + "integrity": "sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2663,13 +2764,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", - "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.7.tgz", + "integrity": "sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2679,14 +2780,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", - "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.7.tgz", + "integrity": "sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2696,13 +2797,13 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", - "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.7.tgz", + "integrity": "sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2712,13 +2813,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", - "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.7.tgz", + "integrity": "sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2728,13 +2829,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", - "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.7.tgz", + "integrity": "sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2744,13 +2845,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", - "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.7.tgz", + "integrity": "sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2760,14 +2861,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", - "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.7.tgz", + "integrity": "sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2777,14 +2878,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", - "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.7.tgz", + "integrity": "sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2794,14 +2895,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", - "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.7.tgz", + "integrity": "sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" }, "engines": { "node": ">=6.9.0" @@ -2933,13 +3034,6 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/runtime": { "version": "7.25.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", @@ -2953,32 +3047,32 @@ } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", - "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.4", - "@babel/parser": "^7.25.4", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.4", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2986,31 +3080,15 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.25.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", - "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.4", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", - "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -3145,35 +3223,6 @@ "node": ">=8" } }, - "node_modules/@capacitor/assets/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@capacitor/assets/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@capacitor/assets/node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -3253,35 +3302,6 @@ "node": ">=8" } }, - "node_modules/@capacitor/cli/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@capacitor/cli/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@capacitor/cli/node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -4793,35 +4813,6 @@ "node": ">=8" } }, - "node_modules/@ionic/cli/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ionic/cli/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@ionic/cli/node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", @@ -5470,9 +5461,9 @@ } }, "node_modules/@jsonjoy.com/util": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.3.0.tgz", - "integrity": "sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5684,9 +5675,9 @@ ] }, "node_modules/@ngtools/webpack": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.6.tgz", - "integrity": "sha512-7HwOPE1EOgcHnpt4brSiT8G2CcXB50G0+CbCBaKGy4LYCG3Y3mrlzF5Fup9HvMJ6Tzqd62RqzpKKYBiGUT7hxg==", + "version": "18.2.8", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.8.tgz", + "integrity": "sha512-sq0kI8gEen4QlM6X8XqOYy7j4B8iLCYNo+iKxatV36ts4AXH0MuVkP56+oMaoH5oZNoSqd0RlfnotEHfvJAr8A==", "dev": true, "license": "MIT", "engines": { @@ -6238,9 +6229,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.23.0.tgz", - "integrity": "sha512-8OR+Ok3SGEMsAZispLx8jruuXw0HVF16k+ub2eNXKHDmdxL4cf9NlNpAzhlOhNyXzKDEJuFeq0nZm+XlNb1IFw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", "cpu": [ "arm" ], @@ -6252,9 +6243,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.23.0.tgz", - "integrity": "sha512-rEFtX1nP8gqmLmPZsXRMoLVNB5JBwOzIAk/XAcEPuKrPa2nPJ+DuGGpfQUR0XjRm8KjHfTZLpWbKXkA5BoFL3w==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", "cpu": [ "arm64" ], @@ -6266,9 +6257,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.23.0.tgz", - "integrity": "sha512-ZbqlMkJRMMPeapfaU4drYHns7Q5MIxjM/QeOO62qQZGPh9XWziap+NF9fsqPHT0KzEL6HaPspC7sOwpgyA3J9g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", "cpu": [ "arm64" ], @@ -6280,9 +6271,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.23.0.tgz", - "integrity": "sha512-PfmgQp78xx5rBCgn2oYPQ1rQTtOaQCna0kRaBlc5w7RlA3TDGGo7m3XaptgitUZ54US9915i7KeVPHoy3/W8tA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", "cpu": [ "x64" ], @@ -6294,9 +6285,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.23.0.tgz", - "integrity": "sha512-WAeZfAAPus56eQgBioezXRRzArAjWJGjNo/M+BHZygUcs9EePIuGI1Wfc6U/Ki+tMW17FFGvhCfYnfcKPh18SA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", "cpu": [ "arm" ], @@ -6308,9 +6299,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.23.0.tgz", - "integrity": "sha512-v7PGcp1O5XKZxKX8phTXtmJDVpE20Ub1eF6w9iMmI3qrrPak6yR9/5eeq7ziLMrMTjppkkskXyxnmm00HdtXjA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", "cpu": [ "arm" ], @@ -6322,9 +6313,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.23.0.tgz", - "integrity": "sha512-nAbWsDZ9UkU6xQiXEyXBNHAKbzSAi95H3gTStJq9UGiS1v+YVXwRHcQOQEF/3CHuhX5BVhShKoeOf6Q/1M+Zhg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", "cpu": [ "arm64" ], @@ -6336,9 +6327,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.23.0.tgz", - "integrity": "sha512-5QT/Di5FbGNPaVw8hHO1wETunwkPuZBIu6W+5GNArlKHD9fkMHy7vS8zGHJk38oObXfWdsuLMogD4sBySLJ54g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", "cpu": [ "arm64" ], @@ -6350,9 +6341,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.23.0.tgz", - "integrity": "sha512-Sefl6vPyn5axzCsO13r1sHLcmPuiSOrKIImnq34CBurntcJ+lkQgAaTt/9JkgGmaZJ+OkaHmAJl4Bfd0DmdtOQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", "cpu": [ "ppc64" ], @@ -6364,9 +6355,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.23.0.tgz", - "integrity": "sha512-o4QI2KU/QbP7ZExMse6ULotdV3oJUYMrdx3rBZCgUF3ur3gJPfe8Fuasn6tia16c5kZBBw0aTmaUygad6VB/hQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", "cpu": [ "riscv64" ], @@ -6378,9 +6369,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.23.0.tgz", - "integrity": "sha512-+bxqx+V/D4FGrpXzPGKp/SEZIZ8cIW3K7wOtcJAoCrmXvzRtmdUhYNbgd+RztLzfDEfA2WtKj5F4tcbNPuqgeg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", "cpu": [ "s390x" ], @@ -6392,9 +6383,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.23.0.tgz", - "integrity": "sha512-I/eXsdVoCKtSgK9OwyQKPAfricWKUMNCwJKtatRYMmDo5N859tbO3UsBw5kT3dU1n6ZcM1JDzPRSGhAUkxfLxw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", "cpu": [ "x64" ], @@ -6406,9 +6397,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.23.0.tgz", - "integrity": "sha512-4ZoDZy5ShLbbe1KPSafbFh1vbl0asTVfkABC7eWqIs01+66ncM82YJxV2VtV3YVJTqq2P8HMx3DCoRSWB/N3rw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", "cpu": [ "x64" ], @@ -6420,9 +6411,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.23.0.tgz", - "integrity": "sha512-+5Ky8dhft4STaOEbZu3/NU4QIyYssKO+r1cD3FzuusA0vO5gso15on7qGzKdNXnc1gOrsgCqZjRw1w+zL4y4hQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", "cpu": [ "arm64" ], @@ -6434,9 +6425,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.23.0.tgz", - "integrity": "sha512-0SPJk4cPZQhq9qA1UhIRumSE3+JJIBBjtlGl5PNC///BoaByckNZd53rOYD0glpTkYFBQSt7AkMeLVPfx65+BQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", "cpu": [ "ia32" ], @@ -6448,9 +6439,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.23.0.tgz", - "integrity": "sha512-lqCK5GQC8fNo0+JvTSxcG7YB1UKYp8yrNLhsArlvPWN+16ovSZgoehlVHg6X0sSWPUkpjRBR5TuR12ZugowZ4g==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", "cpu": [ "x64" ], @@ -7936,9 +7927,9 @@ } }, "node_modules/adm-zip": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.15.tgz", - "integrity": "sha512-jYPWSeOA8EFoZnucrKCNihqBjoEGQSU4HKgHYQgKNEQ0pQF9a/DYuo/+fAxY76k4qe75LUlLWpAM1QWcBMTOKw==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", "dev": true, "license": "MIT", "engines": { @@ -8437,9 +8428,9 @@ } }, "node_modules/aws4": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", - "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true, "license": "MIT" }, @@ -8824,9 +8815,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "dev": true, "funding": [ { @@ -8844,8 +8835,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, @@ -9081,9 +9072,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001653", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz", - "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==", + "version": "1.0.30001668", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz", + "integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==", "dev": true, "funding": [ { @@ -10064,9 +10055,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -11603,9 +11594,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", - "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", + "version": "1.5.36", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz", + "integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==", "dev": true, "license": "ISC" }, @@ -11671,9 +11662,9 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "dev": true, "license": "MIT", "dependencies": { @@ -11682,7 +11673,7 @@ "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -12753,9 +12744,9 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12764,7 +12755,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -12796,9 +12787,9 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "license": "MIT", "engines": { @@ -14391,6 +14382,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -14913,16 +14917,16 @@ } }, "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -14980,6 +14984,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -15105,16 +15125,13 @@ } }, "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/is-plain-object": { @@ -15277,19 +15294,16 @@ "license": "MIT" }, "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "license": "MIT", "dependencies": { - "is-inside-container": "^1.0.0" + "is-docker": "^2.0.0" }, "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/isarray": { @@ -15594,16 +15608,16 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -17216,9 +17230,9 @@ } }, "node_modules/memfs": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.12.0.tgz", - "integrity": "sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.13.0.tgz", + "integrity": "sha512-dIs5KGy24fbdDhIAg0RxXpFqQp3RwL6wgSMRF9OSuphL/Uc9a4u2/SDJKPLj/zUgtOGKuHrRMrj563+IErj4Cg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -17650,16 +17664,6 @@ "node": ">= 6" } }, - "node_modules/minimist-options/node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -18850,6 +18854,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -20620,9 +20640,9 @@ "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dev": true, "license": "MIT", "dependencies": { @@ -20682,16 +20702,16 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", + "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.11.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -20699,28 +20719,26 @@ "node": ">=4" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.1.tgz", + "integrity": "sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/replace": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/replace/-/replace-1.2.2.tgz", @@ -21172,9 +21190,9 @@ "license": "Unlicense" }, "node_modules/rollup": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.23.0.tgz", - "integrity": "sha512-vXB4IT9/KLDrS2WRXmY22sVB2wTsTwkpxjB8Q3mnakTENcYw3FRmfdYDy/acNmls+lHmDazgrRjK/yQ6hQAtwA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, "license": "MIT", "dependencies": { @@ -21188,22 +21206,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.23.0", - "@rollup/rollup-android-arm64": "4.23.0", - "@rollup/rollup-darwin-arm64": "4.23.0", - "@rollup/rollup-darwin-x64": "4.23.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.23.0", - "@rollup/rollup-linux-arm-musleabihf": "4.23.0", - "@rollup/rollup-linux-arm64-gnu": "4.23.0", - "@rollup/rollup-linux-arm64-musl": "4.23.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.23.0", - "@rollup/rollup-linux-riscv64-gnu": "4.23.0", - "@rollup/rollup-linux-s390x-gnu": "4.23.0", - "@rollup/rollup-linux-x64-gnu": "4.23.0", - "@rollup/rollup-linux-x64-musl": "4.23.0", - "@rollup/rollup-win32-arm64-msvc": "4.23.0", - "@rollup/rollup-win32-ia32-msvc": "4.23.0", - "@rollup/rollup-win32-x64-msvc": "4.23.0", + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", "fsevents": "~2.3.2" } }, @@ -22173,9 +22191,9 @@ } }, "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dev": true, "license": "MIT", "dependencies": { @@ -22183,7 +22201,7 @@ "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -22283,6 +22301,7 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -23842,9 +23861,9 @@ "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, "license": "MIT", "engines": { @@ -23866,9 +23885,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "dev": true, "license": "MIT", "engines": { @@ -25059,9 +25078,9 @@ } }, "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, "license": "MIT", "dependencies": { @@ -25083,6 +25102,19 @@ } } }, + "node_modules/webpack-dev-server/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webpack-dev-server/node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", diff --git a/ui/package.json b/ui/package.json index d1e7e5250b9..65e20fe3d95 100644 --- a/ui/package.json +++ b/ui/package.json @@ -52,7 +52,7 @@ "zone.js": "~0.14.7" }, "devDependencies": { - "@angular-devkit/build-angular": "^18.2.5", + "@angular-devkit/build-angular": "^18.2.8", "@angular-devkit/core": "18.2.5", "@angular-devkit/schematics": "18.2.5", "@angular-eslint/builder": "^18.3.1", @@ -92,7 +92,7 @@ "karma-coverage-istanbul-reporter": "~3.0.3", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "^2.1.0", - "protractor": "~7.0.0", + "protractor": "^7.0.0", "ts-node": "^10.9.2", "typescript": "~5.4.5", "typescript-strict-plugin": "^2.4.4" diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index f84be2e897f..a6324b0d68b 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -91,6 +91,7 @@ export const routes: Routes = [ { path: ":componentId/gridOptimizedChargeChart", component: GridOptimizedChargeChartOverviewComponent }, { path: ":componentId/heatingelementchart", component: HeatingelementChartOverviewComponent }, { path: ":componentId/heatpumpchart", component: HeatPumpChartOverviewComponent }, + { path: ":componentId/modbusTcpApi", component: ModbusTcpApiOverviewComponent }, { path: ":componentId/scheduleChart", component: TimeOfUseTariffOverviewComponent }, { path: ":componentId/symmetricpeakshavingchart", component: SymmetricPeakshavingChartOverviewComponent }, { path: ":componentId/timeslotpeakshavingchart", component: TimeslotPeakshavingChartOverviewComponent }, @@ -101,7 +102,6 @@ export const routes: Routes = [ { path: "gridchart", component: GridChartOverviewComponent }, { path: "gridchart/:componentId", component: GridDetailsOverviewComponent }, { path: "gridchart/:componentId/currentVoltage", component: CurrentAndVoltageOverviewComponent }, - { path: ":componentId/modbusTcpApi", component: ModbusTcpApiOverviewComponent }, { path: "productionchart", component: ProductionChartOverviewComponent }, { path: "productionchart/:componentId", component: DetailsOverviewComponent }, { path: "productionchart/:componentId/currentVoltage", component: CurrentAndVoltageOverviewComponent }, @@ -134,6 +134,7 @@ export const routes: Routes = [ { path: "settings/alerting", component: EdgeSettingsAlerting, canActivate: [hasEdgeRole(Role.OWNER)], data: { navbarTitleToBeTranslated: "Edge.Config.Index.alerting" } }, { path: "settings/jsonrpctest", component: JsonrpcTestComponent, data: { navbarTitle: "Jsonrpc Test" } }, { path: "settings/powerAssistant", component: PowerAssistantComponent, canActivate: [hasEdgeRole(Role.ADMIN)], data: { navbarTitle: "Power-Assistant" } }, + { path: "settings/app", data: { navbarTitle: environment.edgeShortName + "Apps" }, component: EdgeSettingsAppIndex }, ], }, diff --git a/ui/src/app/edge/history/abstracthistorychart.ts b/ui/src/app/edge/history/abstracthistorychart.ts index 0891b3f453e..7c8c70ec7bb 100644 --- a/ui/src/app/edge/history/abstracthistorychart.ts +++ b/ui/src/app/edge/history/abstracthistorychart.ts @@ -57,9 +57,6 @@ export abstract class AbstractHistoryChart { borderColor: "rgba(128,128,0,1)", }; - private activeQueryData: string; - private debounceTimeout: any | null = null; - constructor( public readonly spinnerId: string, protected service: Service, @@ -271,48 +268,33 @@ export abstract class AbstractHistoryChart { const resolution = res ?? calculateResolution(this.service, fromDate, toDate).resolution; this.errorResponse = null; - - if (this.debounceTimeout) { - clearTimeout(this.debounceTimeout); - } - - this.debounceTimeout = setTimeout(() => { - const result: Promise = new Promise((resolve, reject) => { - this.service.getCurrentEdge().then(edge => { - this.service.getConfig().then(config => { - this.setLabel(config); - this.getChannelAddresses(edge, config).then(channelAddresses => { - const request = new QueryHistoricTimeseriesDataRequest(DateUtils.maxDate(fromDate, this.edge?.firstSetupProtocol), toDate, channelAddresses, resolution); - edge.sendRequest(this.service.websocket, request).then(response => { - resolve(response as QueryHistoricTimeseriesDataResponse); - this.activeQueryData = request.id; - }).catch(error => { - this.errorResponse = error; - resolve(new QueryHistoricTimeseriesDataResponse(error.id, { - timestamps: [null], data: { null: null }, - })); - }); + const result: Promise = new Promise((resolve, reject) => { + this.service.getCurrentEdge().then(edge => { + this.service.getConfig().then(config => { + this.setLabel(config); + this.getChannelAddresses(edge, config).then(channelAddresses => { + const request = new QueryHistoricTimeseriesDataRequest(DateUtils.maxDate(fromDate, this.edge?.firstSetupProtocol), toDate, channelAddresses, resolution); + edge.sendRequest(this.service.websocket, request).then(response => { + resolve(response as QueryHistoricTimeseriesDataResponse); + }).catch(error => { + this.errorResponse = error; + resolve(new QueryHistoricTimeseriesDataResponse(error.id, { + timestamps: [null], data: { null: null }, + })); }); }); }); - }).then((response) => { - if (this.activeQueryData !== response.id) { - return; - } - if (Utils.isDataEmpty(response)) { - this.loading = false; - this.service.stopSpinner(this.spinnerId); - this.initializeChart(); - } - return DateTimeUtils.normalizeTimestamps(resolution.unit, response); }); - - return result; - }, ChartConstants.REQUEST_TIMEOUT); - - return new Promise((resolve) => { - resolve(new QueryHistoricTimeseriesDataResponse("", { timestamps: [], data: {} })); + }).then((response) => { + if (Utils.isDataEmpty(response)) { + this.loading = false; + this.service.stopSpinner(this.spinnerId); + this.initializeChart(); + } + return DateTimeUtils.normalizeTimestamps(resolution.unit, response); }); + + return result; } /** diff --git a/ui/src/app/edge/history/common/consumption/details/details.overview.html b/ui/src/app/edge/history/common/consumption/details/details.overview.html index 31819efc6fb..bf708560a45 100644 --- a/ui/src/app/edge/history/common/consumption/details/details.overview.html +++ b/ui/src/app/edge/history/common/consumption/details/details.overview.html @@ -1,4 +1,4 @@ - + diff --git a/ui/src/app/edge/history/common/energy/chart/channels.spec.ts b/ui/src/app/edge/history/common/energy/chart/channels.spec.ts index 2abfaee9aed..fe2dbeb853b 100644 --- a/ui/src/app/edge/history/common/energy/chart/channels.spec.ts +++ b/ui/src/app/edge/history/common/energy/chart/channels.spec.ts @@ -19,7 +19,7 @@ export namespace History { "legend": { "display": true, "position": "bottom", "labels": { "color": "" }, }, "tooltip": { - "intersect": false, "mode": "index", "callbacks": {}, + "intersect": false, "mode": "index", "callbacks": {}, "enabled": true, }, "annotation": { annotations: {}, @@ -56,7 +56,7 @@ export namespace History { "legend": { "display": true, "position": "bottom", "labels": { "color": "" }, }, "tooltip": { - "intersect": false, "mode": "x", "callbacks": {}, + "intersect": false, "mode": "x", "callbacks": {}, "enabled": true, }, "annotation": { annotations: {}, diff --git a/ui/src/app/edge/history/common/energy/chart/chart.ts b/ui/src/app/edge/history/common/energy/chart/chart.ts index dff22346d79..9598a1615a0 100644 --- a/ui/src/app/edge/history/common/energy/chart/chart.ts +++ b/ui/src/app/edge/history/common/energy/chart/chart.ts @@ -200,6 +200,7 @@ export class ChartComponent extends AbstractHistoryChart { displayGrid: false, }), ], + normalizeOutputData: true, }; } diff --git a/ui/src/app/edge/history/common/grid/details/details.overview.html b/ui/src/app/edge/history/common/grid/details/details.overview.html index 1bba13ab767..506e8390f44 100644 --- a/ui/src/app/edge/history/common/grid/details/details.overview.html +++ b/ui/src/app/edge/history/common/grid/details/details.overview.html @@ -1,6 +1,8 @@ - + { - const gridMeter = Object.values(this.config.components) - .find((component) => component.isEnabled && this.config.isTypeGrid(component)) ?? null; + if (!this.component) { + return; + } + const gridMeter = this.config.isTypeGrid(this.component) ?? null; if (!gridMeter) { return; } + const gridMeters = Object.values(this.config.components) + .filter((comp) => comp.isEnabled && this.config.isTypeGrid(comp)) ?? null; + + if (gridMeters?.length == 1) { + this.title = this.translate.instant("General.grid"); + } + this.navigationButtons = [ - { id: "currentVoltage", isEnabled: edge.roleIsAtLeast(Role.INSTALLER), alias: this.translate.instant("Edge.History.CURRENT_AND_VOLTAGE"), callback: () => { this.router.navigate([`../${gridMeter.id}/currentVoltage`], { relativeTo: this.route }); } }]; + { id: "currentVoltage", isEnabled: edge.roleIsAtLeast(Role.INSTALLER), alias: this.translate.instant("Edge.History.CURRENT_AND_VOLTAGE"), callback: () => { this.router.navigate(["./currentVoltage"], { relativeTo: this.route }); } }]; }); } } diff --git a/ui/src/app/edge/history/common/grid/overview/overview.html b/ui/src/app/edge/history/common/grid/overview/overview.html index 71e464fef64..51b01128edf 100644 --- a/ui/src/app/edge/history/common/grid/overview/overview.html +++ b/ui/src/app/edge/history/common/grid/overview/overview.html @@ -3,4 +3,4 @@ - + diff --git a/ui/src/app/edge/history/common/grid/overview/overview.ts b/ui/src/app/edge/history/common/grid/overview/overview.ts index b99f248ebf5..20982339233 100644 --- a/ui/src/app/edge/history/common/grid/overview/overview.ts +++ b/ui/src/app/edge/history/common/grid/overview/overview.ts @@ -2,15 +2,18 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; +import { filter, takeUntil } from "rxjs/operators"; import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/abstractHistoryChartOverview"; import { NavigationOption } from "src/app/shared/components/footer/subnavigation/footerNavigation"; import { EdgeConfig, Service } from "src/app/shared/shared"; + @Component({ templateUrl: "./overview.html", }) export class OverviewComponent extends AbstractHistoryChartOverview { protected navigationButtons: NavigationOption[] = []; + protected isAllowed: boolean = false; constructor( public override service: Service, @@ -24,24 +27,23 @@ export class OverviewComponent extends AbstractHistoryChartOverview { protected override afterIsInitialized() { - const sum: EdgeConfig.Component = this.config.getComponent("_sum"); - sum.alias = this.translate.instant("General.TOTAL"); + this.service.historyPeriod.pipe(takeUntil(this.stopOnDestroy), filter(period => !!period)) + .subscribe((period) => { + this.isAllowed = period.isWeekOrDay(); + }); + const navigationButtons: EdgeConfig.Component[] = []; const gridMeters = Object.values(this.config.components) .filter((component) => component.isEnabled && this.config.isTypeGrid(component)); if (!gridMeters) { - navigationButtons.push(sum); + return; } - if (gridMeters?.length <= 1) { - navigationButtons.push(sum); - } else { - navigationButtons.push(...gridMeters); - } + navigationButtons.push(...gridMeters); this.navigationButtons = navigationButtons.map(el => ( - { id: el.id, alias: el.alias, callback: () => { this.router.navigate(["./" + el.id], { relativeTo: this.route }); } } + { id: el.id, alias: navigationButtons.length === 1 ? this.translate.instant("Edge.History.PHASE_ACCURATE") : el.alias, callback: () => { this.router.navigate(["./" + el.id], { relativeTo: this.route }); } } )); } } diff --git a/ui/src/app/edge/history/common/production/details/details.overview.html b/ui/src/app/edge/history/common/production/details/details.overview.html index 1884e99ef81..0e69bd49bec 100644 --- a/ui/src/app/edge/history/common/production/details/details.overview.html +++ b/ui/src/app/edge/history/common/production/details/details.overview.html @@ -1,4 +1,4 @@ - diff --git a/ui/src/app/edge/history/common/production/overview/overview.ts b/ui/src/app/edge/history/common/production/overview/overview.ts index e75c1e02ea0..6f44c4fe37a 100644 --- a/ui/src/app/edge/history/common/production/overview/overview.ts +++ b/ui/src/app/edge/history/common/production/overview/overview.ts @@ -35,10 +35,8 @@ export class OverviewComponent extends AbstractHistoryChartOverview { this.config.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") .filter(component => component.isEnabled && this.config.isProducer(component)); - const sum: EdgeConfig.Component = this.config.getComponent("_sum"); - sum.alias = this.translate.instant("General.TOTAL"); - this.navigationButtons = [sum, ...this.chargerComponents, ...this.productionMeterComponents].map(el => ( + this.navigationButtons = [...this.productionMeterComponents, ...this.chargerComponents].map(el => ( { id: el.id, alias: el.alias, callback: () => { this.router.navigate(["./" + el.id], { relativeTo: this.route }); } } )); return []; diff --git a/ui/src/app/edge/history/history.component.html b/ui/src/app/edge/history/history.component.html index bd86a09bf04..0b7e40f54f2 100644 --- a/ui/src/app/edge/history/history.component.html +++ b/ui/src/app/edge/history/history.component.html @@ -46,10 +46,6 @@ - - - - diff --git a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/statePriceChart.ts b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/statePriceChart.ts index 3e690c5828f..63aaa9ac05b 100644 --- a/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/statePriceChart.ts +++ b/ui/src/app/edge/live/Controller/Ess/TimeOfUseTariff/modal/statePriceChart.ts @@ -4,13 +4,12 @@ import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import * as Chart from "chart.js"; import { AbstractHistoryChart } from "src/app/edge/history/abstracthistorychart"; +import { calculateResolution } from "src/app/edge/history/shared"; import { AbstractHistoryChart as NewAbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { ComponentJsonApiRequest } from "src/app/shared/jsonrpc/request/componentJsonApiRequest"; import { ChartAxis, HistoryUtils, TimeOfUseTariffUtils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, Currency, Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; - -import { calculateResolution } from "src/app/edge/history/shared"; -import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; import { ColorUtils } from "src/app/shared/utils/color/color.utils"; import { GetScheduleRequest } from "../../../../../../shared/jsonrpc/request/getScheduleRequest"; import { GetScheduleResponse } from "../../../../../../shared/jsonrpc/response/getScheduleResponse"; diff --git a/ui/src/app/edge/live/Controller/Evcs/modal/modal.html b/ui/src/app/edge/live/Controller/Evcs/modal/modal.html index da7866b9130..0820cf1130a 100644 --- a/ui/src/app/edge/live/Controller/Evcs/modal/modal.html +++ b/ui/src/app/edge/live/Controller/Evcs/modal/modal.html @@ -32,6 +32,18 @@ [name]="'Edge.Index.Widgets.EVCS.energySinceBeginning' | translate" [value]="energySession"> + + + + + + + + + + + diff --git a/ui/src/app/edge/live/Controller/Evcs/modal/modal.ts b/ui/src/app/edge/live/Controller/Evcs/modal/modal.ts index 0e92bbc6eda..0b2e5053bc9 100644 --- a/ui/src/app/edge/live/Controller/Evcs/modal/modal.ts +++ b/ui/src/app/edge/live/Controller/Evcs/modal/modal.ts @@ -37,6 +37,7 @@ export class ModalComponent extends AbstractModal { protected isChargingEnabled: boolean = false; protected sessionLimit: number; protected helpKey: string; + protected awaitingHysteresis: boolean; constructor( @Inject(Websocket) protected override websocket: Websocket, @@ -116,11 +117,13 @@ export class ModalComponent extends AbstractModal { new ChannelAddress(this.controller?.id, "_PropertyChargeMode"), new ChannelAddress(this.controller?.id, "_PropertyEnabledCharging"), new ChannelAddress(this.controller?.id, "_PropertyDefaultChargeMinPower"), + new ChannelAddress(this.controller?.id, "AwaitingHysteresis"), ]; } protected override onCurrentData(currentData: CurrentData) { this.isConnectionSuccessful = currentData.allComponents[this.component.id + "/State"] !== 3 ? true : false; + this.awaitingHysteresis = currentData.allComponents[this.controller?.id + "/AwaitingHysteresis"]; // Do not change values after touching formControls if (this.formGroup?.pristine) { this.status = this.getState(this.controller ? currentData.allComponents[this.controller.id + "/_PropertyEnabledCharging"] === 1 : null, currentData.allComponents[this.component.id + "/Status"], currentData.allComponents[this.component.id + "/Plug"]); diff --git a/ui/src/app/edge/live/live.module.ts b/ui/src/app/edge/live/live.module.ts index 944e2240b03..ccd88577618 100644 --- a/ui/src/app/edge/live/live.module.ts +++ b/ui/src/app/edge/live/live.module.ts @@ -1,6 +1,7 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { EdgeOfflineModule } from "src/app/shared/components/edge/offline/offline.module"; import { SharedModule } from "./../../shared/shared.module"; import { Controller_ChannelthresholdComponent } from "./Controller/Channelthreshold/Channelthreshold"; import { Controller_ChpSocComponent } from "./Controller/ChpSoc/ChpSoc"; @@ -41,27 +42,25 @@ import { DelayedSellToGridModalComponent } from "./delayedselltogrid/modal/modal import { EnergymonitorModule } from "./energymonitor/energymonitor.module"; import { InfoComponent } from "./info/info.component"; import { LiveComponent } from "./live.component"; -import { OfflineComponent } from "./offline/offline.component"; @NgModule({ imports: [ BrowserAnimationsModule, BrowserModule, - // Common Common_Autarchy, - Common_Production, - Common_Selfconsumption, Common_Consumption, Common_Grid, - // Controller + Common_Production, + Common_Selfconsumption, + Controller_Api_ModbusTcp, Controller_Ess_FixActivePower, Controller_Ess_GridOptimizedCharge, + Controller_Ess_TimeOfUseTariff, + Controller_Evcs, Controller_Io_HeatingElement, - Controller_Api_ModbusTcp, + EdgeOfflineModule, EnergymonitorModule, SharedModule, - Controller_Evcs, - Controller_Ess_TimeOfUseTariff, ], declarations: [ AdministrationComponent, @@ -90,7 +89,6 @@ import { OfflineComponent } from "./offline/offline.component"; Io_Api_DigitalInput_ModalComponent, Io_Api_DigitalInputComponent, LiveComponent, - OfflineComponent, StorageComponent, StorageModalComponent, ], diff --git a/ui/src/app/index/login.component.ts b/ui/src/app/index/login.component.ts index 4fa20f9aeff..26a38d7e767 100644 --- a/ui/src/app/index/login.component.ts +++ b/ui/src/app/index/login.component.ts @@ -35,16 +35,16 @@ export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { ) { } /** - * Trims credentials - * - * @param password the password - * @param username the username - * @returns trimmed credentials - */ - public static trimCredentials(password: string, username?: string): { password: string, username?: string } { + * Preprocesses the credentials + * + * @param password the password + * @param username the username + * @returns trimmed credentials + */ + public static preprocessCredentials(password: string, username?: string): { password: string, username?: string } { return { password: password?.trim(), - ...(username && { username: username?.trim() }), + ...(username && { username: username?.trim().toLowerCase() }), }; } @@ -95,7 +95,7 @@ export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { public doLogin(param: { username?: string, password: string }) { this.websocket.state.set(States.AUTHENTICATION_WITH_CREDENTIALS); - param = LoginComponent.trimCredentials(param.password, param.username); + param = LoginComponent.preprocessCredentials(param.password, param.username); // Prevent that user submits via keyevent 'enter' multiple times if (this.formIsDisabled) { diff --git a/ui/src/app/index/login.spec.ts b/ui/src/app/index/login.spec.ts index dad874f51d4..9feea8aa9ce 100644 --- a/ui/src/app/index/login.spec.ts +++ b/ui/src/app/index/login.spec.ts @@ -12,26 +12,30 @@ describe("Login", () => { }).compileComponents(); }); - it("#trimCredentials should trim password and username", () => { + it("#preprocessCredentials should trim password and username and should lowerCase username", () => { { // Username and password - OpenEMS Backend - expect(LoginComponent.trimCredentials(password, username)).toEqual({ password: "password", username: "username" }); + expect(LoginComponent.preprocessCredentials(password, username)).toEqual({ password: "password", username: "username" }); } { // Only Password - OpenEMS Edge - expect(LoginComponent.trimCredentials(password)).toEqual({ password: "password" }); + expect(LoginComponent.preprocessCredentials(password)).toEqual({ password: "password" }); } { // Password is null - expect(LoginComponent.trimCredentials(null)).toEqual({ password: undefined }); + expect(LoginComponent.preprocessCredentials(null)).toEqual({ password: undefined }); } { // Username is null - expect(LoginComponent.trimCredentials(password, null)).toEqual({ password: "password" }); + expect(LoginComponent.preprocessCredentials(password, null)).toEqual({ password: "password" }); } { // Username and password are null - expect(LoginComponent.trimCredentials(null, null)).toEqual({ password: undefined }); + expect(LoginComponent.preprocessCredentials(null, null)).toEqual({ password: undefined }); + } + { + // Username in Upper case + expect(LoginComponent.preprocessCredentials(password, username.toUpperCase())).toEqual({ password: "password", username: "username" }); } }); }); diff --git a/ui/src/app/shared/components/chart/abstracthistorychart.ts b/ui/src/app/shared/components/chart/abstracthistorychart.ts index b30e69af4b8..2e7d88b2fa3 100644 --- a/ui/src/app/shared/components/chart/abstracthistorychart.ts +++ b/ui/src/app/shared/components/chart/abstracthistorychart.ts @@ -28,6 +28,7 @@ import { Converter } from "../shared/converter"; import { ChartConstants, XAxisType } from "./chart.constants"; import "chartjs-adapter-date-fns"; +import { JsonRpcUtils } from "../../jsonrpc/jsonrpcutils"; Chart.Chart.register(annotationPlugin); @@ -166,15 +167,20 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { public static fillData(element: HistoryUtils.DisplayValue, label: string, chartObject: HistoryUtils.ChartData, chartType: "line" | "bar", data: number[] | null): { datasets: Chart.ChartDataset[], legendOptions: { label: string, strokeThroughHidingStyle: boolean, hideLabelInLegend: boolean; }[]; } { const legendOptions: { label: string, strokeThroughHidingStyle: boolean, hideLabelInLegend: boolean; }[] = []; const datasets: Chart.ChartDataset[] = []; + let normalizedData: (number | null)[] = data; + + if (chartObject.normalizeOutputData == true) { + normalizedData = JsonRpcUtils.normalizeQueryData(data); + } // Enable one dataset to be displayed in multiple stacks if (Array.isArray(element.stack)) { for (const stack of element.stack) { - datasets.push(AbstractHistoryChart.getDataSet(element, label, data, stack, chartObject, element.custom?.type ?? chartType)); + datasets.push(AbstractHistoryChart.getDataSet(element, label, normalizedData, stack, chartObject, element.custom?.type ?? chartType)); legendOptions.push(AbstractHistoryChart.getLegendOptions(label, element)); } } else { - datasets.push(AbstractHistoryChart.getDataSet(element, label, data, element.stack, chartObject, element.custom?.type ?? chartType)); + datasets.push(AbstractHistoryChart.getDataSet(element, label, normalizedData, element.stack, chartObject, element.custom?.type ?? chartType)); legendOptions.push(AbstractHistoryChart.getLegendOptions(label, element)); } @@ -460,6 +466,8 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { return null; }; + options.plugins.tooltip.enabled = chartObject.tooltip.enabled ?? true; + // Remove duplicates from legend, if legendItem with two or more occurrences in legend, use one legendItem to trigger them both options.plugins.legend.onClick = function (event: Chart.ChartEvent, legendItem: Chart.LegendItem, legend) { const chart: Chart.Chart = this.chart; @@ -556,11 +564,17 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { }, }; break; + case YAxisType.VOLTAGE: + case YAxisType.CURRENT: + options.scales[element.yAxisId] = { + ...baseConfig, + beginAtZero: false, + }; + break; + case YAxisType.POWER: case YAxisType.ENERGY: case YAxisType.REACTIVE: - case YAxisType.VOLTAGE: - case YAxisType.CURRENT: case YAxisType.NONE: options.scales[element.yAxisId] = baseConfig; break; @@ -869,7 +883,6 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { this.queryHistoricTimeseriesEnergy(this.service.historyPeriod.value.from, this.service.historyPeriod.value.to), ]) .then(([dataResponse, energyResponse]) => { - dataResponse = DateTimeUtils.normalizeTimestamps(unit, dataResponse); this.chartType = "line"; this.chartObject = this.getChartData(); diff --git a/ui/src/app/shared/components/chart/chart.constants.ts b/ui/src/app/shared/components/chart/chart.constants.ts index 93bf4dc58e7..fa8ccf10a71 100644 --- a/ui/src/app/shared/components/chart/chart.constants.ts +++ b/ui/src/app/shared/components/chart/chart.constants.ts @@ -5,6 +5,7 @@ import { formatNumber } from "@angular/common"; import { TranslateService } from "@ngx-translate/core"; import ChartDataLabels from "chartjs-plugin-datalabels"; import { HistoryUtils, Utils } from "../../service/utils"; +import { Language } from "../../type/language"; import { ArrayUtils } from "../../utils/array/array.utils"; import { AbstractHistoryChart } from "./abstracthistorychart"; @@ -41,7 +42,8 @@ export class ChartConstants { public static readonly BAR_CHART_DATALABELS = (unit: string, disable: boolean): any => ({ ...ChartDataLabels, formatter: (value, ctx) => { - return formatNumber(value, "de", "1.0-0") + "\xa0" + unit ?? null; + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; + return formatNumber(value, locale, "1.0-0") + "\xa0" + unit ?? null; }, ...{ anchor: "end", offset: -18, align: "start", clip: false, clamp: true, diff --git a/ui/src/app/shared/components/chart/chart.html b/ui/src/app/shared/components/chart/chart.html index 075411661cb..0bed497b52d 100644 --- a/ui/src/app/shared/components/chart/chart.html +++ b/ui/src/app/shared/components/chart/chart.html @@ -15,7 +15,8 @@ - +
+
diff --git a/ui/src/app/shared/components/edge/meter/currentVoltage/chart/asymmetricMeter.ts b/ui/src/app/shared/components/edge/meter/currentVoltage/chart/asymmetricMeter.ts index 54d5f05ee5c..f187ea50ccc 100644 --- a/ui/src/app/shared/components/edge/meter/currentVoltage/chart/asymmetricMeter.ts +++ b/ui/src/app/shared/components/edge/meter/currentVoltage/chart/asymmetricMeter.ts @@ -34,7 +34,7 @@ export class CurrentVoltageAsymmetricChartComponent extends AbstractHistoryChart }, hideShadow: true, color: currentPhasesColors[index], - yAxisId: ChartAxis.RIGHT, + yAxisId: ChartAxis.LEFT, })), ...Phase.THREE_PHASE.map((phase, index) => ({ name: this.translate.instant("Edge.History.VOLTAGE") + " " + phase, @@ -43,6 +43,7 @@ export class CurrentVoltageAsymmetricChartComponent extends AbstractHistoryChart }, hideShadow: true, color: voltagePhasesColors[index], + yAxisId: ChartAxis.RIGHT, })), ], tooltip: { @@ -51,13 +52,14 @@ export class CurrentVoltageAsymmetricChartComponent extends AbstractHistoryChart }, yAxes: [{ unit: YAxisType.VOLTAGE, - position: "left", - yAxisId: ChartAxis.LEFT, + position: "right", + yAxisId: ChartAxis.RIGHT, + displayGrid: false, }, { unit: YAxisType.CURRENT, - position: "right", - yAxisId: ChartAxis.RIGHT, + position: "left", + yAxisId: ChartAxis.LEFT, }, ], }; diff --git a/ui/src/app/shared/components/edge/meter/currentVoltage/chart/symmetricMeter.ts b/ui/src/app/shared/components/edge/meter/currentVoltage/chart/symmetricMeter.ts index e7998dda9f6..d3ad8c86502 100644 --- a/ui/src/app/shared/components/edge/meter/currentVoltage/chart/symmetricMeter.ts +++ b/ui/src/app/shared/components/edge/meter/currentVoltage/chart/symmetricMeter.ts @@ -35,7 +35,7 @@ export class CurrentVoltageSymmetricChartComponent extends AbstractHistoryChart hiddenOnInit: false, stack: 1, - yAxisId: ChartAxis.RIGHT, + yAxisId: ChartAxis.LEFT, }, { name: this.translate.instant("Edge.History.VOLTAGE"), @@ -45,7 +45,7 @@ export class CurrentVoltageSymmetricChartComponent extends AbstractHistoryChart color: "rgb(255,0,0)", hiddenOnInit: false, stack: 1, - yAxisId: ChartAxis.LEFT, + yAxisId: ChartAxis.RIGHT, }, ], tooltip: { @@ -54,13 +54,14 @@ export class CurrentVoltageSymmetricChartComponent extends AbstractHistoryChart }, yAxes: [{ unit: YAxisType.VOLTAGE, - position: "left", - yAxisId: ChartAxis.LEFT, + position: "right", + yAxisId: ChartAxis.RIGHT, + displayGrid: false, }, { unit: YAxisType.CURRENT, - position: "right", - yAxisId: ChartAxis.RIGHT, + position: "left", + yAxisId: ChartAxis.LEFT, }, ], }; diff --git a/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.html b/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.html index 71727e415e6..0f1ff84692e 100644 --- a/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.html +++ b/ui/src/app/shared/components/edge/meter/currentVoltage/currentVoltage.overview.html @@ -1,7 +1,8 @@ - + diff --git a/ui/src/app/shared/components/edge/offline/offline.component.html b/ui/src/app/shared/components/edge/offline/offline.component.html new file mode 100644 index 00000000000..4517b57f3c8 --- /dev/null +++ b/ui/src/app/shared/components/edge/offline/offline.component.html @@ -0,0 +1,6 @@ + + + + {{environment.edgeShortName}} ist offline + + diff --git a/ui/src/app/edge/live/offline/offline.component.ts b/ui/src/app/shared/components/edge/offline/offline.component.ts similarity index 78% rename from ui/src/app/edge/live/offline/offline.component.ts rename to ui/src/app/shared/components/edge/offline/offline.component.ts index 5af0358d185..2f9af23442a 100644 --- a/ui/src/app/edge/live/offline/offline.component.ts +++ b/ui/src/app/shared/components/edge/offline/offline.component.ts @@ -1,16 +1,25 @@ import { Component, OnInit } from "@angular/core"; import { Edge, Service, Utils } from "src/app/shared/shared"; +import { Role } from "src/app/shared/type/role"; import { DateUtils } from "src/app/shared/utils/date/dateutils"; +import { environment } from "src/environments"; // TODO add translations when refactoring offline.component.html @Component({ selector: "offline", templateUrl: "./offline.component.html", + styles: [` + ion-item > ion-label > h3 { + font-weight: bolder; + } + `], }) export class OfflineComponent implements OnInit { protected edge: Edge | null = null; protected timeSinceOffline: string | null = null; + protected isAtLeastInstaller: boolean = false; + protected readonly environment = environment; constructor( public service: Service, @@ -45,7 +54,8 @@ export class OfflineComponent implements OnInit { ngOnInit() { this.service.getCurrentEdge().then(edge => { this.edge = edge; - this.timeSinceOffline = OfflineComponent.formatSecondsToFullMinutes(edge.lastmessage.toString()); + this.isAtLeastInstaller = this.edge.roleIsAtLeast(Role.INSTALLER); + this.timeSinceOffline = OfflineComponent.formatSecondsToFullMinutes(edge.lastmessage?.toString()); }); } } diff --git a/ui/src/app/shared/components/edge/offline/offline.module.ts b/ui/src/app/shared/components/edge/offline/offline.module.ts new file mode 100644 index 00000000000..5c500e85c32 --- /dev/null +++ b/ui/src/app/shared/components/edge/offline/offline.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from "@angular/core"; +import { BrowserModule } from "@angular/platform-browser"; +import { IonicModule } from "@ionic/angular"; +import { OfflineComponent } from "./offline.component"; + +@NgModule({ + imports: [ + BrowserModule, + IonicModule, + ], + declarations: [ + OfflineComponent, + ], + exports: [ + OfflineComponent, + ], +}) +export class EdgeOfflineModule { } diff --git a/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts b/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts index 48738330c12..2d697a6a6e0 100644 --- a/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts +++ b/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts @@ -30,7 +30,10 @@ export abstract class AbstractFlatWidgetLine implements OnChanges, OnDestroy { */ public displayValue: string | null = null; + protected displayName: string = null; protected show: boolean = true; + + private _name: string | ((value: any) => string); private _channelAddress: ChannelAddress | null = null; /** @@ -48,6 +51,15 @@ export abstract class AbstractFlatWidgetLine implements OnChanges, OnDestroy { @Inject(DataService) private dataService: DataService, ) { } + @Input() set name(value: string | { channel: ChannelAddress, converter: (value: any) => string }) { + if (typeof value === "object") { + this.subscribe(value.channel); + this._name = value.converter; + } else { + this._name = value; + } + } + /** Channel defines the channel, you need for this line */ @Input() set channelAddress(channelAddress: string) { @@ -79,7 +91,14 @@ export abstract class AbstractFlatWidgetLine implements OnChanges, OnDestroy { } protected setValue(value: any) { + if (typeof this._name == "function") { + this.displayName = this._name(value); + + } else { + this.displayName = this._name; + } this.displayValue = this.converter(value); + if (this.filter) { this.show = this.filter(value); } diff --git a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.html b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.html index 7ac84753d40..a33df0902d6 100644 --- a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.html +++ b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item.html @@ -1,4 +1,5 @@ -
diff --git a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.html b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.html index 46424574ca4..4be476c385b 100644 --- a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.html +++ b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.html @@ -2,10 +2,10 @@ - diff --git a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.ts b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.ts index 288f0f5aced..e9d3d45fdc3 100644 --- a/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.ts +++ b/ui/src/app/shared/components/flat/flat-widget-line/flat-widget-line.ts @@ -7,9 +7,6 @@ import { AbstractFlatWidgetLine } from "../abstract-flat-widget-line"; templateUrl: "./flat-widget-line.html", }) export class FlatWidgetLineComponent extends AbstractFlatWidgetLine { - /** Name for parameter, displayed on the left side */ - @Input({ required: true }) - public name!: string; /** Width of left Column, right Column is (100 - width of left Column) */ @Input() diff --git a/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.html b/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.html new file mode 100644 index 00000000000..07dbace6ba8 --- /dev/null +++ b/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.html @@ -0,0 +1,66 @@ + + + + + + {{props.label}} + + + + + + + {{props.description}} + + + + + + + + + + +
+ {{props.attributes?.infoLine }} +
+
+
+ + + + {{step.label}} + + + + + + + + + + {{step.description}} + + + + + + + + + +
+ + + + + + +
+
+
+
+
diff --git a/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.ts b/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.ts new file mode 100644 index 00000000000..b9705f87493 --- /dev/null +++ b/ui/src/app/shared/components/formly/form-field-multi-step/form-field-multi-step.ts @@ -0,0 +1,70 @@ +import { Component, OnInit } from "@angular/core"; +import { FieldType } from "@ngx-formly/core"; + +@Component({ + selector: "form-field-multi-step", + templateUrl: "./form-field-multi-step.html", +}) +export class FormlyFieldMultiStepComponent extends FieldType implements OnInit { + + public currentStep: number = 0; + + protected get steps() { + return this.props.steps || []; + } + + public ngOnInit() { + // Ensure the model has an array to track steps + const stepArray = this.formControl.value; + + if (!Array.isArray(stepArray)) { + this.formControl.setValue(Array(this.steps.length).fill(false)); + } + + // Listen to status changes to reset steps if disabled + this.formControl.statusChanges.subscribe(status => { + if (status === "DISABLED") { + this.resetSteps(); + } + }); + + // Determine the current step based on the array of steps + const lastFalseIndex = stepArray.lastIndexOf(false); + const lastTrueIndex = stepArray.lastIndexOf(true); + + if (lastFalseIndex === -1) { + // All steps are true, show the final step + this.currentStep = this.steps.length - 1; + } else if (lastTrueIndex === -1) { + // No true steps, show the first step + this.currentStep = 0; + } else { + // Show the last true step + this.currentStep = lastTrueIndex; + } + } + + + protected nextStep() { + if (this.currentStep < this.steps.length - 1) { + this.currentStep++; + } + } + + protected prevStep() { + if (this.currentStep > 0) { + this.currentStep--; + } + } + + protected onCheckboxChange(event: any, index: number) { + const updatedValue = this.formControl.value; + updatedValue[index] = event.detail.checked; + this.formControl.setValue(updatedValue); + } + + private resetSteps() { + this.formControl.setValue(Array(this.steps.length).fill(false)); + this.currentStep = 0; + } +} diff --git a/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.html b/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.html index bd1c0ab4da9..64274e6732d 100644 --- a/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.html +++ b/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.html @@ -4,13 +4,20 @@ {{props.label}} - - + + + + {{props.description}} + + + + diff --git a/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.ts b/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.ts index c00fdafb5a2..b182f88a0c7 100644 --- a/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.ts +++ b/ui/src/app/shared/components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image.ts @@ -12,7 +12,15 @@ export class FormlyFieldCheckboxWithImageComponent extends FieldWrapper implemen public ngOnInit() { // If the default value is not set in beginning. - this.value = this.field.defaultValue; + this.value = this.formControl.value ?? this.field.defaultValue; + + // Listen to form control status changes to reset steps if disabled + this.formControl.statusChanges.subscribe(status => { + if (status === "DISABLED" && this.value !== false) { + this.value = false; + this.formControl.setValue(this.value); + } + }); } /** @@ -23,4 +31,13 @@ export class FormlyFieldCheckboxWithImageComponent extends FieldWrapper implemen this.formControl.setValue(this.value); } + /** + * Returns the show/hide value based on the properties. + * + * @returns boolean value representing "show" or "hide". + */ + protected showContent() { + return (!this.field.props?.disabled && !this.value) && this.field.props?.url !== undefined; + } + } diff --git a/ui/src/app/shared/components/header/header.component.ts b/ui/src/app/shared/components/header/header.component.ts index fc97026c4e6..27c51f4732d 100644 --- a/ui/src/app/shared/components/header/header.component.ts +++ b/ui/src/app/shared/components/header/header.component.ts @@ -1,5 +1,5 @@ // @ts-strict-ignore -import { AfterViewChecked, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { AfterViewChecked, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; import { MenuController, ModalController } from "@ionic/angular"; import { Subject } from "rxjs"; @@ -24,6 +24,8 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { public currentPage: "EdgeSettings" | "Other" | "IndexLive" | "IndexHistory" = "Other"; public isSystemLogEnabled: boolean = false; private ngUnsubscribe: Subject = new Subject(); + private _customBackUrl: string | null = null; + constructor( private cdRef: ChangeDetectorRef, @@ -35,6 +37,14 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { private route: ActivatedRoute, ) { } + @Input() public set customBackUrl(url: string | null) { + if (!url) { + return; + } + this._customBackUrl = url; + this.updateBackUrl(url); + } + ngOnInit() { // set inital URL this.updateUrl(this.router.routerState.snapshot.url); @@ -74,6 +84,11 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { updateBackUrl(url: string) { + if (this._customBackUrl) { + this.backUrl = this._customBackUrl; + return; + } + // disable backUrl & Segment Navigation on initial 'login' page if (url === "/login" || url === "/overview" || url === "/index") { this.backUrl = false; diff --git a/ui/src/app/shared/components/shared/converter.ts b/ui/src/app/shared/components/shared/converter.ts index b76ad1ace3f..6791bf9166e 100644 --- a/ui/src/app/shared/components/shared/converter.ts +++ b/ui/src/app/shared/components/shared/converter.ts @@ -93,6 +93,20 @@ export namespace Converter { Formatter.FORMAT_WATT(value)); }; + /** + * Formats a Power value as Watt [W]. + * + * Value 1000 -> "1.000 W". + * Value null -> "-". + * + * @param value the power value + * @returns formatted value; '-' for null + */ + export const POWER_IN_KILO_WATT: Converter = (raw) => { + return IF_NUMBER(raw, value => + Formatter.FORMAT_KILO_WATT(Utils.divideSafely(value, 1000))); + }; + /** * Formats a Energy value as Kilo watt hours [kWh]. * diff --git a/ui/src/app/shared/components/shared/formatter.ts b/ui/src/app/shared/components/shared/formatter.ts index 798818e17b6..8cfba6bba0b 100644 --- a/ui/src/app/shared/components/shared/formatter.ts +++ b/ui/src/app/shared/components/shared/formatter.ts @@ -1,39 +1,54 @@ import { formatNumber } from "@angular/common"; import { Currency } from "../../shared"; +import { Language } from "../../type/language"; export namespace Formatter { + + // Changes the number format based on the language selected. + const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; + export const FORMAT_WATT = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " W"; + return formatNumber(value, locale, "1.0-0") + " W"; + }; + + export const FORMAT_KILO_WATT = (value: number) => { + // TODO apply correct locale + return formatNumber(value, locale, "1.0-2") + " kW"; }; export const FORMAT_KILO_WATT_HOURS = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " kWh"; + return formatNumber(value, locale, "1.0-0") + " kWh"; }; export const FORMAT_VOLT = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " V"; + return formatNumber(value, locale, "1.0-0") + " V"; }; export const FORMAT_AMPERE = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.1-1") + " A"; + return formatNumber(value, locale, "1.1-1") + " A"; }; export const FORMAT_CELSIUS = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " °C"; + return formatNumber(value, locale, "1.0-0") + " °C"; }; export const FORMAT_PERCENT = (value: number) => { // TODO apply correct locale - return formatNumber(value, "de", "1.0-0") + " %"; + return formatNumber(value, locale, "1.0-0") + " %"; + }; + + export const FORMAT_BAR = (value: number) => { + // TODO apply correct locale + return formatNumber(value, locale, "1.1-1") + " mbar"; }; export const FORMAT_CURRENCY_PER_KWH = (value: number | string, currency: string = Currency.Unit.CENT) => { // TODO apply correct locale - return formatNumber(parseInt(value.toString()), "de", "1.0-2") + " " + Currency.getCurrencyLabelByCurrency(currency); + return formatNumber(parseInt(value.toString()), locale, "1.0-2") + " " + Currency.getCurrencyLabelByCurrency(currency); }; } diff --git a/ui/src/app/shared/components/shared/testing/common.ts b/ui/src/app/shared/components/shared/testing/common.ts index 62f21fe345a..59e22834a1c 100644 --- a/ui/src/app/shared/components/shared/testing/common.ts +++ b/ui/src/app/shared/components/shared/testing/common.ts @@ -26,7 +26,7 @@ export namespace OeTester { type: "option", options: { "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": {}, "line": {} }, "plugins": { - "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "index", "callbacks": {} }, "annotation": { "annotations": {} }, "datalabels": { + "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "index", "callbacks": {}, "enabled": true }, "annotation": { "annotations": {} }, "datalabels": { display: false, }, }, "scales": { @@ -46,7 +46,7 @@ export namespace OeTester { type: "option", options: { "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": { "barPercentage": 1 }, "line": {} }, "plugins": { - "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "x", "callbacks": {} }, "annotation": { "annotations": {} }, "datalabels": { + "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "x", "callbacks": {}, "enabled": true }, "annotation": { "annotations": {} }, "datalabels": { display: false, }, }, "scales": { @@ -75,6 +75,7 @@ export namespace OeTester { "display": true, "position": "bottom", "labels": { "color": "" }, }, "tooltip": { "intersect": false, "mode": "index", "callbacks": {}, + "enabled": true, }, "annotation": { "annotations": {}, @@ -112,7 +113,7 @@ export namespace OeTester { type: "option", options: { "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": { "barPercentage": 1 }, "line": {} }, "plugins": { - "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "x", "callbacks": {} }, "annotation": { "annotations": {} }, "datalabels": { + "colors": { "enabled": false }, "legend": { "display": true, "position": "bottom", "labels": { "color": "" } }, "tooltip": { "intersect": false, "mode": "x", "callbacks": {}, "enabled": true }, "annotation": { "annotations": {} }, "datalabels": { display: false, }, }, "scales": { diff --git a/ui/src/app/shared/components/shared/testing/tester.ts b/ui/src/app/shared/components/shared/testing/tester.ts index 2e30117a4fa..7b2e9c7c662 100644 --- a/ui/src/app/shared/components/shared/testing/tester.ts +++ b/ui/src/app/shared/components/shared/testing/tester.ts @@ -233,6 +233,7 @@ export class OeChartTester { from: new Date(channelData.result.timestamps[0] ?? 0), to: new Date(channelData.result.timestamps.reverse()[0] ?? 0), getText: () => testContext.service.historyPeriod.value.getText(testContext.translate, testContext.service), + isWeekOrDay: () => testContext.service.historyPeriod.value.isWeekOrDay(), }); // Fill Data diff --git a/ui/src/app/shared/jsonrpc/jsonrpcutils.spec.ts b/ui/src/app/shared/jsonrpc/jsonrpcutils.spec.ts new file mode 100644 index 00000000000..0133cd573ed --- /dev/null +++ b/ui/src/app/shared/jsonrpc/jsonrpcutils.spec.ts @@ -0,0 +1,10 @@ +import { JsonRpcUtils } from "./jsonrpcutils"; + +describe("JsonRpcUtils", () => { + + const productionActivePowerData = [-0.01, -0.1, -0.49, -0.50, -1, null]; + const expectedOutput = [0, 0, 0, -0.5, -1, null]; + it("#normalizeQueryData", () => { + expect(JsonRpcUtils.normalizeQueryData(productionActivePowerData)).toEqual(expectedOutput); + }); +}); diff --git a/ui/src/app/shared/jsonrpc/jsonrpcutils.ts b/ui/src/app/shared/jsonrpc/jsonrpcutils.ts index 642ffee60c6..979d7be8b53 100644 --- a/ui/src/app/shared/jsonrpc/jsonrpcutils.ts +++ b/ui/src/app/shared/jsonrpc/jsonrpcutils.ts @@ -2,6 +2,26 @@ import { ChannelAddress } from "../type/channeladdress"; export class JsonRpcUtils { + private static THRESHOLD: number = -0.50; + + public static normalizeQueryData(data: (number | null)[]): (number | null)[] { + return data.map(el => JsonRpcUtils.roundSlightlyNegativeValues(el)); + } + + /** + * Rounds values between 0 and -1kW to 0 + * + * @param value the value to convert + */ + public static roundSlightlyNegativeValues(value: number | null): number | null { + if (value == null) { + return null; + } + + return (value > JsonRpcUtils.THRESHOLD && value < 0) ? 0 : value; + } + + /** * Converts an array of ChannelAddresses to a string array with unique values. */ diff --git a/ui/src/app/shared/service/defaulttypes.ts b/ui/src/app/shared/service/defaulttypes.ts index b01b9875627..08b2419b983 100644 --- a/ui/src/app/shared/service/defaulttypes.ts +++ b/ui/src/app/shared/service/defaulttypes.ts @@ -1,6 +1,6 @@ // @ts-strict-ignore import { TranslateService } from "@ngx-translate/core"; -import { endOfMonth, endOfYear, format, getDay, getMonth, getYear, isSameDay, isSameMonth, isSameYear, startOfMonth, startOfYear, subDays } from "date-fns"; +import { differenceInDays, endOfMonth, endOfYear, format, getDay, getMonth, getYear, isSameDay, isSameMonth, isSameYear, startOfMonth, startOfYear, subDays } from "date-fns"; import { QueryHistoricTimeseriesEnergyResponse } from "../jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; import { ChannelAddress, Service } from "../shared"; @@ -157,6 +157,7 @@ export namespace DefaultTypes { public to: Date = new Date(), ) { } + /** * Returns a translated weekday name. * @@ -236,6 +237,15 @@ export namespace DefaultTypes { }); } } + + /** + * Checks if current period is week or day + * + * @returns true if period is week or day, false if not + */ + public isWeekOrDay(): boolean { + return Math.abs(differenceInDays(this.to, this.from)) <= 6; + } } } diff --git a/ui/src/app/shared/service/service.ts b/ui/src/app/shared/service/service.ts index e25fa22fac3..cc997a4d76b 100644 --- a/ui/src/app/shared/service/service.ts +++ b/ui/src/app/shared/service/service.ts @@ -6,7 +6,7 @@ import { ToastController } from "@ionic/angular"; import { LangChangeEvent, TranslateService } from "@ngx-translate/core"; import { NgxSpinnerService } from "ngx-spinner"; import { BehaviorSubject, Subject } from "rxjs"; -import { filter, first, take } from "rxjs/operators"; +import { filter, first, map, take } from "rxjs/operators"; import { ChosenFilter } from "src/app/index/filter/filter.component"; import { environment } from "src/environments"; import { ChartConstants } from "../components/chart/chart.constants"; @@ -186,6 +186,24 @@ export class Service extends AbstractService { }); } + /** + * Gets the current user + * + * @returns a Promise of the user + */ + public getCurrentUser(): Promise { + return new Promise((resolve) => { + this.metadata.pipe( + filter(metadata => metadata != null && metadata.user != null), + map(metadata => metadata.user), + first(), + ).toPromise().then(resolve); + if (this.currentUser) { + resolve(this.currentUser); + } + }); + } + public getConfig(): Promise { return new Promise((resolve, reject) => { this.getCurrentEdge().then(edge => { diff --git a/ui/src/app/shared/service/utils.spec.ts b/ui/src/app/shared/service/utils.spec.ts index 3034ab95019..8315e541078 100644 --- a/ui/src/app/shared/service/utils.spec.ts +++ b/ui/src/app/shared/service/utils.spec.ts @@ -1,6 +1,6 @@ // @ts-strict-ignore import { DummyConfig } from "../components/edge/edgeconfig.spec"; -import { EdgeConfig } from "../shared"; +import { Currency, EdgeConfig } from "../shared"; import { HistoryUtils, Utils } from "./utils"; describe("Utils", () => { @@ -54,4 +54,12 @@ describe("Utils", () => { const expectedResult4 = [null, null, null, 565, 560, 561, 573]; expect(Utils.calculateOtherConsumption(channelData, [], [])).toEqual(expectedResult4); }); + + it("+CONVERT_PRICE_TO_CENT_PER_KWH", () => { + const currencyLabel: string = Currency.getCurrencyLabelByEdgeId("0"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(0)).toEqual("0 Cent/kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(null)).toEqual("- Cent/kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(undefined)).toEqual("- Cent/kWh"); + expect(Utils.CONVERT_PRICE_TO_CENT_PER_KWH(2, currencyLabel)(1)).toEqual("0,1 Cent/kWh"); + }); }); diff --git a/ui/src/app/shared/service/utils.ts b/ui/src/app/shared/service/utils.ts index 9e237060345..0d5f686b21a 100644 --- a/ui/src/app/shared/service/utils.ts +++ b/ui/src/app/shared/service/utils.ts @@ -396,8 +396,8 @@ export class Utils { * @returns converted value */ public static CONVERT_PRICE_TO_CENT_PER_KWH = (decimal: number, label: string) => { - return (value: number | null): string => - (!value ? "-" : formatNumber(value / 10, "de", "1.0-" + decimal)) + " " + label; + return (value: number | null | undefined): string => + (value == null ? "-" : formatNumber(value / 10, "de", "1.0-" + decimal)) + " " + label; }; /** @@ -522,7 +522,7 @@ export class Utils { * * @param value the value to convert */ - public static roundSlightlyNegativeValues(value: number) { + public static roundSlightlyNegativeValues(value: number | null): number | null { return (value > -0.49 && value < 0) ? 0 : value; } @@ -763,8 +763,12 @@ export namespace HistoryUtils { /** Format of Number displayed */ formatNumber: string, afterTitle?: (stack: string) => string, + /** Defaults to true */ + enabled?: boolean, }, yAxes: yAxes[], + /** Rounds slightly negative values, defaults to false */ + normalizeOutputData?: boolean, }; export type yAxes = { diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index 2ff830daf99..d85cf25b0e5 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -15,6 +15,7 @@ import { ComponentsModule } from "./components/components.module"; import { MeterModule } from "./components/edge/meter/meter.module"; import { FormlyCheckBoxHyperlinkWrapperComponent } from "./components/formly/form-field-checkbox-hyperlink/form-field-checkbox-hyperlink.wrapper"; import { FormlyWrapperDefaultValueWithCasesComponent } from "./components/formly/form-field-default-cases.wrapper"; +import { FormlyFieldMultiStepComponent } from "./components/formly/form-field-multi-step/form-field-multi-step"; import { FormlyWrapperFormFieldComponent } from "./components/formly/form-field.wrapper"; import { FormlyFieldCheckboxWithImageComponent } from "./components/formly/formly-field-checkbox-image/formly-field-checkbox-with-image"; import { FormlyFieldModalComponent } from "./components/formly/formly-field-modal/formlyfieldmodal"; @@ -84,6 +85,7 @@ export function SubnetmaskValidatorMessage(err, field: FormlyFieldConfig) { types: [ { name: "input", component: InputTypeComponent }, { name: "repeat", component: RepeatTypeComponent }, + { name: "multi-step", component: FormlyFieldMultiStepComponent }, ], validators: [ { name: "ip", validation: IpValidator }, @@ -119,6 +121,7 @@ export function SubnetmaskValidatorMessage(err, field: FormlyFieldConfig) { PanelWrapperComponent, FormlyFieldWithLoadingAnimationComponent, FormlyFieldCheckboxWithImageComponent, + FormlyFieldMultiStepComponent, ], exports: [ // modules diff --git a/ui/src/app/user/user.component.html b/ui/src/app/user/user.component.html index bc5370ebca3..08f52cc3ffc 100644 --- a/ui/src/app/user/user.component.html +++ b/ui/src/app/user/user.component.html @@ -139,11 +139,6 @@

- - - - Debug-Mode - @@ -166,6 +161,12 @@ The language can not be changed permanently in the local online-monitoring for technical reasons. + + + + + Debug-Mode + diff --git a/ui/src/app/user/user.component.ts b/ui/src/app/user/user.component.ts index b42945d1521..694549cf3b6 100644 --- a/ui/src/app/user/user.component.ts +++ b/ui/src/app/user/user.component.ts @@ -13,6 +13,7 @@ import { GetUserInformationResponse } from "../shared/jsonrpc/response/getUserIn import { Service, Websocket } from "../shared/shared"; import { COUNTRY_OPTIONS } from "../shared/type/country"; import { Language } from "../shared/type/language"; +import { Role } from "../shared/type/role"; type CompanyUserInformation = UserInformation & { companyName: string }; @@ -57,6 +58,8 @@ export class UserComponent implements OnInit { }]; protected readonly companyInformationFields: FormlyFieldConfig[] = []; + protected isAtLeastAdmin: boolean = false; + constructor( public translate: TranslateService, public service: Service, @@ -67,6 +70,11 @@ export class UserComponent implements OnInit { ngOnInit() { // Set currentLanguage to this.currentLanguage = Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT; + + this.service.getCurrentUser().then(user => { + this.isAtLeastAdmin = Role.isAtLeast(user.globalRole, Role.ADMIN); + }); + this.getUserInformation().then((userInformation) => { this.form = { formGroup: new FormGroup({}), diff --git a/ui/src/assets/i18n/de.json b/ui/src/assets/i18n/de.json index 457f5cb15ab..e105762966d 100644 --- a/ui/src/assets/i18n/de.json +++ b/ui/src/assets/i18n/de.json @@ -200,7 +200,9 @@ "name": "Erzwungene Beladung", "shortName": "Manuell" }, - "Uncontrollable": "Diese Ladesäule kann nicht gesteuert werden." + "Uncontrollable": "Diese Ladesäule kann nicht gesteuert werden.", + "HYSTERESIS": "Mindestumschaltzeit der Ladestation aktiv", + "HYSTERESIS_INFO": "Um Ladeabbrüche aufgrund zu häufigen Startens/Pausierens des Ladevorgangs zu verhindern, wird der aktuell eingestellte Lademodus für die nächsten Minuten fortgesetzt." }, "Heatingelement": { "activeForced": "Aktiv (Mindestlaufzeit)", @@ -271,6 +273,7 @@ "SYSTEMUPDATE": "Systemupdate" }, "History": { + "PHASE_ACCURATE": "Phasengenau", "CURRENT_AND_VOLTAGE": "Strom & Spannung", "beginDate": "Startdatum wählen", "day": "Tag", diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index b3f33f87876..58ca563831c 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -201,7 +201,9 @@ "name": "Force charging", "shortName": "Manually" }, - "Uncontrollable": "This charging station can not be controlled." + "Uncontrollable": "This charging station can not be controlled.", + "HYSTERESIS": "Minimum switching time of the charging station active", + "HYSTERESIS_INFO": "For preventing charging interruptions due to frequent starting/pausing of the charing process, the charging mode currently selected will continue for the next few minutes." }, "Heatingelement": { "activeForced": "Active (Minimum runtime)", @@ -314,7 +316,8 @@ "activeDuration": "active duration", "CURRENT_AND_VOLTAGE": "Current & Voltage", "CURRENT": "Current", - "VOLTAGE": "Voltage" + "VOLTAGE": "Voltage", + "PHASE_ACCURATE": "Phasengenau" }, "Config": { "Index": { diff --git a/ui/src/global-ion-custom.scss b/ui/src/global-ion-custom.scss new file mode 100644 index 00000000000..95979a5ee40 --- /dev/null +++ b/ui/src/global-ion-custom.scss @@ -0,0 +1,11 @@ +.ion-font-size-medium { + font-size: medium !important; +} + +.ion-font-size-smaller { + font-size: small !important; +} + +.ion-font-weight-bolder { + font-weight: bolder; +} diff --git a/ui/src/global.scss b/ui/src/global.scss index 59a24c31264..50d881926b2 100644 --- a/ui/src/global.scss +++ b/ui/src/global.scss @@ -25,6 +25,7 @@ /* ngx-spinner */ @import "node_modules/ngx-spinner/animations/ball-clip-rotate-multiple.css"; @import "variables"; +@import "./global-ion-custom.scss"; /* Live- and HistoryComponent*/ ion-refresher-content { @@ -54,6 +55,13 @@ ion-refresher-content { } +.disabled { + color: gray; + pointer-events: none; + opacity: 0.5; + /* Makes it semi-transparent */ +} + formly-wrapper-ion-form-field, formly-input-serial-number, formly-field-ion-radio { @@ -403,4 +411,8 @@ ion-modal.full-width { --storage-segment-2: block; --storage-segment-3: block; --storage-segment-4: block; -} \ No newline at end of file +} + +.card-with-primary-border { + border: 2px solid $primary-color; +} From fd713534073f24ceb941225f24564f815cde32aa Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sun, 13 Oct 2024 14:27:04 +0200 Subject: [PATCH 03/19] UI: add import order rule to eslint (#2838) * npm install -D eslint-import-resolver-typescript eslint-plugin-import * add import rules * fix: add ignore comment for eslint-plugin-import bug. cf. import-js/eslint-plugin-import#1479 * eslint src/ --fix Co-authored-by: Hiromasa Ihara --- ui/.eslintrc.json | 16 +++- ui/package-lock.json | 82 ++++++++++++++++++- ui/package.json | 3 +- ui/src/app/app.service.ts | 2 +- .../Io/DigitalOutput/digitalOutput.module.ts | 6 +- .../ModbusTcpApi/modbusTcpApi.module.ts | 2 +- .../history/Controller/controller.module.ts | 4 +- .../history/common/consumption/Consumption.ts | 2 +- .../history/common/production/production.ts | 2 +- ui/src/app/edge/history/shared.ts | 3 + .../history/storage/totalchart.component.ts | 2 +- .../live/common/storage/storage.component.ts | 3 +- ui/src/app/edge/live/live.module.ts | 30 +++---- .../settings/channels/channels.component.ts | 4 +- ui/src/app/index/login.component.ts | 2 +- .../components/abstracthistorywidget.ts | 2 +- .../components/chart/abstracthistorychart.ts | 8 +- .../components/chart/chart.constants.ts | 2 +- .../shared/components/components.module.ts | 2 +- .../flat/abstract-flat-widget-line.ts | 2 +- .../components/flat/abstract-flat-widget.ts | 4 +- .../components/modal/abstract-modal-line.ts | 2 +- .../shared/components/modal/abstractModal.ts | 2 +- ui/src/app/shared/service/pagination.ts | 2 +- .../shared/utils/datetime/datetime-utils.ts | 3 + 25 files changed, 145 insertions(+), 47 deletions(-) diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json index 0c5b77ab6af..9cfb0059ab9 100644 --- a/ui/.eslintrc.json +++ b/ui/.eslintrc.json @@ -20,6 +20,7 @@ "createDefaultProgram": true }, "plugins": [ + "import", "unused-imports", "@stylistic" ], @@ -27,11 +28,19 @@ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@angular-eslint/recommended", - "plugin:@angular-eslint/template/process-inline-templates" + "plugin:@angular-eslint/template/process-inline-templates", + "plugin:import/recommended" ], "rules": { "curly": "error", "unused-imports/no-unused-imports": "error", + "import/order": [ + "error", + { + "groups": ["builtin", "external", "internal", "parent", "sibling", "index"], + "alphabetize": { "order": "asc", "caseInsensitive": true } + } + ], "@typescript-eslint/explicit-member-accessibility": [ "error", { @@ -111,6 +120,11 @@ "message": "Using 'xdescribe' is not allowed." } ] + }, + "settings": { + "import/resolver": { + "typescript": {} + } } }, { diff --git a/ui/package-lock.json b/ui/package-lock.json index 4641fa8ea1b..241ae35ba69 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -85,7 +85,8 @@ "@typescript-eslint/parser": "^7.0.0", "@typescript-eslint/types": "^8.7.0", "eslint": "^8.57.0", - "eslint-plugin-import": "2.30.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-import": "^2.30.0", "eslint-plugin-jsdoc": "50.2.4", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-unused-imports": "^4.1.4", @@ -5907,6 +5908,16 @@ "tslib": "^2.0.0" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@npmcli/agent": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", @@ -12142,6 +12153,42 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz", + "integrity": "sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.5", + "enhanced-resolve": "^5.15.0", + "eslint-module-utils": "^2.8.1", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, "node_modules/eslint-module-utils": { "version": "2.11.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.1.tgz", @@ -13639,6 +13686,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/get-uri": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", @@ -14855,6 +14915,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.2.1.tgz", + "integrity": "sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -21024,6 +21094,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve-url-loader": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", diff --git a/ui/package.json b/ui/package.json index 65e20fe3d95..30f406fc93d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -80,7 +80,8 @@ "@typescript-eslint/parser": "^7.0.0", "@typescript-eslint/types": "^8.7.0", "eslint": "^8.57.0", - "eslint-plugin-import": "2.30.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-import": "^2.30.0", "eslint-plugin-jsdoc": "50.2.4", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-unused-imports": "^4.1.4", diff --git a/ui/src/app/app.service.ts b/ui/src/app/app.service.ts index e544f51838a..4d5bddec5ab 100644 --- a/ui/src/app/app.service.ts +++ b/ui/src/app/app.service.ts @@ -3,8 +3,8 @@ import { Injectable } from "@angular/core"; import { App } from "@capacitor/app"; import { Capacitor } from "@capacitor/core"; import { Directory, Encoding, Filesystem } from "@capacitor/filesystem"; -import { FileOpener } from "@ionic-native/file-opener"; import { AlertController } from "@ionic/angular"; +import { FileOpener } from "@ionic-native/file-opener"; import { TranslateService } from "@ngx-translate/core"; import { saveAs } from "file-saver-es"; import { DeviceDetectorService, DeviceInfo } from "ngx-device-detector"; diff --git a/ui/src/app/edge/history/Controller/Io/DigitalOutput/digitalOutput.module.ts b/ui/src/app/edge/history/Controller/Io/DigitalOutput/digitalOutput.module.ts index df92f38b353..cc060d3d5a8 100644 --- a/ui/src/app/edge/history/Controller/Io/DigitalOutput/digitalOutput.module.ts +++ b/ui/src/app/edge/history/Controller/Io/DigitalOutput/digitalOutput.module.ts @@ -1,12 +1,12 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; import { SharedModule } from "src/app/shared/shared.module"; import { TotalChartComponent } from "./chart/chart"; -import { FlatComponent } from "./flat/flat"; -import { OverviewComponent } from "./overview/overview"; import { ChartComponent } from "./details/chart/chart"; import { DetailsOverviewComponent } from "./details/details.overview"; -import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; +import { FlatComponent } from "./flat/flat"; +import { OverviewComponent } from "./overview/overview"; @NgModule({ imports: [ diff --git a/ui/src/app/edge/history/Controller/ModbusTcpApi/modbusTcpApi.module.ts b/ui/src/app/edge/history/Controller/ModbusTcpApi/modbusTcpApi.module.ts index aef372d5a35..b0ca5fb3021 100644 --- a/ui/src/app/edge/history/Controller/ModbusTcpApi/modbusTcpApi.module.ts +++ b/ui/src/app/edge/history/Controller/ModbusTcpApi/modbusTcpApi.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { SharedModule } from "src/app/shared/shared.module"; +import { ChartComponent } from "./chart/chart"; import { FlatComponent } from "./flat/flat"; import { OverviewComponent } from "./overview/overview"; -import { ChartComponent } from "./chart/chart"; @NgModule({ imports: [ diff --git a/ui/src/app/edge/history/Controller/controller.module.ts b/ui/src/app/edge/history/Controller/controller.module.ts index e009e41d838..773e4d92f97 100644 --- a/ui/src/app/edge/history/Controller/controller.module.ts +++ b/ui/src/app/edge/history/Controller/controller.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; -import { ControllerEss } from "./Ess/ess.module"; -import { ControllerIo } from "./Io/Io.module"; import { ChannelThreshold } from "./ChannelThreshold/channelThreshold.module"; +import { ControllerEss } from "./Ess/ess.module"; import { GridOptimizeCharge } from "./Ess/GridoptimizedCharge/gridOptimizeCharge.module"; import { TimeOfUseTariff } from "./Ess/TimeOfUseTariff/timeOfUseTariff.module"; +import { ControllerIo } from "./Io/Io.module"; import { ModbusTcpApi } from "./ModbusTcpApi/modbusTcpApi.module"; @NgModule({ diff --git a/ui/src/app/edge/history/common/consumption/Consumption.ts b/ui/src/app/edge/history/common/consumption/Consumption.ts index c36c786695d..9b957fb4e03 100644 --- a/ui/src/app/edge/history/common/consumption/Consumption.ts +++ b/ui/src/app/edge/history/common/consumption/Consumption.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { CurrentVoltageModule } from "src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule"; import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; import { SharedModule } from "src/app/shared/shared.module"; -import { CurrentVoltageModule } from "src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule"; import { ChartComponent } from "./chart/chart"; import { ConsumptionMeterChartDetailsComponent } from "./details/chart/consumptionMeter"; import { EvcsChartDetailsComponent } from "./details/chart/evcs"; diff --git a/ui/src/app/edge/history/common/production/production.ts b/ui/src/app/edge/history/common/production/production.ts index d4b7b9ce39c..b78789fc8de 100644 --- a/ui/src/app/edge/history/common/production/production.ts +++ b/ui/src/app/edge/history/common/production/production.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { CurrentVoltageModule } from "src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule"; import { FooterNavigationModule } from "src/app/shared/components/footer/subnavigation/footerNavigation.module"; import { SharedModule } from "src/app/shared/shared.module"; -import { CurrentVoltageModule } from "src/app/shared/components/edge/meter/currentVoltage/currentVoltageModule"; import { TotalChartComponent } from "./chart/totalChart"; import { ChargerChartDetailsComponent } from "./details/chart/charger"; import { ProductionMeterChartDetailsComponent } from "./details/chart/productionMeter"; diff --git a/ui/src/app/edge/history/shared.ts b/ui/src/app/edge/history/shared.ts index ac05ac3b66f..8f997b3ceb7 100644 --- a/ui/src/app/edge/history/shared.ts +++ b/ui/src/app/edge/history/shared.ts @@ -1,7 +1,10 @@ // @ts-strict-ignore import * as Chart from "chart.js"; +/* eslint-disable import/no-duplicates */ +// cf. https://github.com/import-js/eslint-plugin-import/issues/1479 import { differenceInDays, differenceInMinutes, startOfDay } from "date-fns"; import { de } from "date-fns/locale"; +/* eslint-enable import/no-duplicates */ import { QueryHistoricTimeseriesDataResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesDataResponse"; import { ChannelAddress, Service } from "src/app/shared/shared"; import { DateUtils } from "src/app/shared/utils/date/dateutils"; diff --git a/ui/src/app/edge/history/storage/totalchart.component.ts b/ui/src/app/edge/history/storage/totalchart.component.ts index 13375ae97db..c774c6139ef 100644 --- a/ui/src/app/edge/history/storage/totalchart.component.ts +++ b/ui/src/app/edge/history/storage/totalchart.component.ts @@ -1,4 +1,5 @@ // @ts-strict-ignore +import { formatNumber } from "@angular/common"; import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; @@ -7,7 +8,6 @@ import { DefaultTypes } from "src/app/shared/service/defaulttypes"; import { ChartAxis, Utils, YAxisType } from "src/app/shared/service/utils"; import { ChannelAddress, Edge, EdgeConfig, Service } from "src/app/shared/shared"; -import { formatNumber } from "@angular/common"; import { AbstractHistoryChart } from "../abstracthistorychart"; @Component({ diff --git a/ui/src/app/edge/live/common/storage/storage.component.ts b/ui/src/app/edge/live/common/storage/storage.component.ts index ca1846f220f..8114c49b3b3 100644 --- a/ui/src/app/edge/live/common/storage/storage.component.ts +++ b/ui/src/app/edge/live/common/storage/storage.component.ts @@ -2,10 +2,9 @@ import { formatNumber } from "@angular/common"; import { Component } from "@angular/core"; import { AbstractFlatWidget } from "src/app/shared/components/flat/abstract-flat-widget"; -import { CurrentData } from "src/app/shared/shared"; +import { CurrentData , ChannelAddress, EdgeConfig, Utils } from "src/app/shared/shared"; import { DateUtils } from "src/app/shared/utils/date/dateutils"; -import { ChannelAddress, EdgeConfig, Utils } from "../../../../shared/shared"; import { StorageModalComponent } from "./modal/modal.component"; @Component({ diff --git a/ui/src/app/edge/live/live.module.ts b/ui/src/app/edge/live/live.module.ts index ccd88577618..ffec734d90c 100644 --- a/ui/src/app/edge/live/live.module.ts +++ b/ui/src/app/edge/live/live.module.ts @@ -3,14 +3,21 @@ import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { EdgeOfflineModule } from "src/app/shared/components/edge/offline/offline.module"; import { SharedModule } from "./../../shared/shared.module"; +import { Common_Autarchy } from "./common/autarchy/Common_Autarchy"; +import { Common_Consumption } from "./common/consumption/Common_Consumption"; +import { Common_Grid } from "./common/grid/Common_Grid"; +import { Common_Production } from "./common/production/Common_Production"; +import { Common_Selfconsumption } from "./common/selfconsumption/Common_Selfconsumption"; +import { StorageModalComponent } from "./common/storage/modal/modal.component"; +import { StorageComponent } from "./common/storage/storage.component"; import { Controller_ChannelthresholdComponent } from "./Controller/Channelthreshold/Channelthreshold"; import { Controller_ChpSocComponent } from "./Controller/ChpSoc/ChpSoc"; import { Controller_ChpSocModalComponent } from "./Controller/ChpSoc/modal/modal.component"; import { Controller_Ess_FixActivePower } from "./Controller/Ess/FixActivePower/Ess_FixActivePower"; import { Controller_Ess_GridOptimizedCharge } from "./Controller/Ess/GridOptimizedCharge/Ess_GridOptimizedCharge"; import { Controller_Ess_TimeOfUseTariff } from "./Controller/Ess/TimeOfUseTariff/Ess_TimeOfUseTariff"; -import { Controller_Evcs } from "./Controller/Evcs/Evcs"; import { AdministrationComponent } from "./Controller/Evcs/administration/administration.component"; +import { Controller_Evcs } from "./Controller/Evcs/Evcs"; import { Controller_Io_ChannelSingleThresholdComponent } from "./Controller/Io/ChannelSingleThreshold/Io_ChannelSingleThreshold"; import { Controller_Io_ChannelSingleThresholdModalComponent } from "./Controller/Io/ChannelSingleThreshold/modal/modal.component"; import { Controller_Io_FixDigitalOutputComponent } from "./Controller/Io/FixDigitalOutput/Io_FixDigitalOutput"; @@ -21,27 +28,20 @@ import { Controller_Io_HeatpumpModalComponent } from "./Controller/Io/Heatpump/m import { Controller_Api_ModbusTcp } from "./Controller/ModbusTcpApi/modbusTcpApi.module"; import { Controller_Asymmetric_PeakShavingComponent } from "./Controller/PeakShaving/Asymmetric/Asymmetric"; import { Controller_Asymmetric_PeakShavingModalComponent } from "./Controller/PeakShaving/Asymmetric/modal/modal.component"; -import { Controller_Symmetric_PeakShavingComponent } from "./Controller/PeakShaving/Symmetric/Symmetric"; import { Controller_Symmetric_PeakShavingModalComponent } from "./Controller/PeakShaving/Symmetric/modal/modal.component"; -import { Controller_Symmetric_TimeSlot_PeakShavingComponent } from "./Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot"; +import { Controller_Symmetric_PeakShavingComponent } from "./Controller/PeakShaving/Symmetric/Symmetric"; import { Controller_Symmetric_TimeSlot_PeakShavingModalComponent } from "./Controller/PeakShaving/Symmetric_TimeSlot/modal/modal.component"; -import { Io_Api_DigitalInputComponent } from "./Io/Api_DigitalInput/Io_Api_DigitalInput"; -import { Io_Api_DigitalInput_ModalComponent } from "./Io/Api_DigitalInput/modal/modal.component"; -import { Evcs_Api_ClusterComponent } from "./Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster"; -import { EvcsChartComponent } from "./Multiple/Evcs_Api_Cluster/modal/evcs-chart/evcs.chart"; -import { Evcs_Api_ClusterModalComponent } from "./Multiple/Evcs_Api_Cluster/modal/evcsCluster-modal.page"; -import { Common_Autarchy } from "./common/autarchy/Common_Autarchy"; -import { Common_Consumption } from "./common/consumption/Common_Consumption"; -import { Common_Grid } from "./common/grid/Common_Grid"; -import { Common_Production } from "./common/production/Common_Production"; -import { Common_Selfconsumption } from "./common/selfconsumption/Common_Selfconsumption"; -import { StorageModalComponent } from "./common/storage/modal/modal.component"; -import { StorageComponent } from "./common/storage/storage.component"; +import { Controller_Symmetric_TimeSlot_PeakShavingComponent } from "./Controller/PeakShaving/Symmetric_TimeSlot/Symmetric_TimeSlot"; import { DelayedSellToGridComponent } from "./delayedselltogrid/delayedselltogrid.component"; import { DelayedSellToGridModalComponent } from "./delayedselltogrid/modal/modal.component"; import { EnergymonitorModule } from "./energymonitor/energymonitor.module"; import { InfoComponent } from "./info/info.component"; +import { Io_Api_DigitalInputComponent } from "./Io/Api_DigitalInput/Io_Api_DigitalInput"; +import { Io_Api_DigitalInput_ModalComponent } from "./Io/Api_DigitalInput/modal/modal.component"; import { LiveComponent } from "./live.component"; +import { Evcs_Api_ClusterComponent } from "./Multiple/Evcs_Api_Cluster/Evcs_Api_Cluster"; +import { EvcsChartComponent } from "./Multiple/Evcs_Api_Cluster/modal/evcs-chart/evcs.chart"; +import { Evcs_Api_ClusterModalComponent } from "./Multiple/Evcs_Api_Cluster/modal/evcsCluster-modal.page"; @NgModule({ imports: [ diff --git a/ui/src/app/edge/settings/channels/channels.component.ts b/ui/src/app/edge/settings/channels/channels.component.ts index 36ea1f7911c..e8f2411c1f0 100644 --- a/ui/src/app/edge/settings/channels/channels.component.ts +++ b/ui/src/app/edge/settings/channels/channels.component.ts @@ -3,12 +3,12 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { PersistencePriority } from "src/app/shared/components/edge/edgeconfig"; -import { SetChannelValueRequest } from "src/app/shared/jsonrpc/request/setChannelValueRequest"; -import { environment } from "src/environments"; import { ComponentJsonApiRequest } from "src/app/shared/jsonrpc/request/componentJsonApiRequest"; import { GetChannelsOfComponentRequest } from "src/app/shared/jsonrpc/request/getChannelsOfComponentRequest"; +import { SetChannelValueRequest } from "src/app/shared/jsonrpc/request/setChannelValueRequest"; import { Channel, GetChannelsOfComponentResponse } from "src/app/shared/jsonrpc/response/getChannelsOfComponentResponse"; +import { environment } from "src/environments"; import { ChannelAddress, Edge, EdgeConfig, EdgePermission, Service, Websocket } from "../../../shared/shared"; @Component({ diff --git a/ui/src/app/index/login.component.ts b/ui/src/app/index/login.component.ts index 26a38d7e767..7b2f876f623 100644 --- a/ui/src/app/index/login.component.ts +++ b/ui/src/app/index/login.component.ts @@ -2,10 +2,10 @@ import { AfterContentChecked, ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; import { FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; +import { Capacitor } from "@capacitor/core"; import { Subject } from "rxjs"; import { environment } from "src/environments"; -import { Capacitor } from "@capacitor/core"; import { AppService } from "../app.service"; import { AuthenticateWithPasswordRequest } from "../shared/jsonrpc/request/authenticateWithPasswordRequest"; import { States } from "../shared/ngrx-store/states"; diff --git a/ui/src/app/shared/components/abstracthistorywidget.ts b/ui/src/app/shared/components/abstracthistorywidget.ts index dfc48dffa2a..76204d7e133 100644 --- a/ui/src/app/shared/components/abstracthistorywidget.ts +++ b/ui/src/app/shared/components/abstracthistorywidget.ts @@ -4,9 +4,9 @@ import { ActivatedRoute } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; +import { v4 as uuidv4 } from "uuid"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Websocket } from "src/app/shared/shared"; -import { v4 as uuidv4 } from "uuid"; // NOTE: Auto-refresh of widgets is currently disabled to reduce server load @Directive() diff --git a/ui/src/app/shared/components/chart/abstracthistorychart.ts b/ui/src/app/shared/components/chart/abstracthistorychart.ts index 2e7d88b2fa3..3fdc02b231b 100644 --- a/ui/src/app/shared/components/chart/abstracthistorychart.ts +++ b/ui/src/app/shared/components/chart/abstracthistorychart.ts @@ -4,13 +4,14 @@ import { ChangeDetectorRef, Directive, Input, OnDestroy, OnInit } from "@angular import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import * as Chart from "chart.js"; +import "chartjs-adapter-date-fns"; import annotationPlugin from "chartjs-plugin-annotation"; +import { v4 as uuidv4 } from "uuid"; import { ChronoUnit, DEFAULT_NUMBER_CHART_OPTIONS, DEFAULT_TIME_CHART_OPTIONS, Resolution, calculateResolution, isLabelVisible, setLabelVisible } from "src/app/edge/history/shared"; import { QueryHistoricTimeseriesEnergyPerPeriodResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyPerPeriodResponse"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; -import { v4 as uuidv4 } from "uuid"; - import { JsonrpcResponseError } from "../../jsonrpc/base"; +import { JsonRpcUtils } from "../../jsonrpc/jsonrpcutils"; import { QueryHistoricTimeseriesDataRequest } from "../../jsonrpc/request/queryHistoricTimeseriesDataRequest"; import { QueryHistoricTimeseriesEnergyPerPeriodRequest } from "../../jsonrpc/request/queryHistoricTimeseriesEnergyPerPeriodRequest"; import { QueryHistoricTimeseriesEnergyRequest } from "../../jsonrpc/request/queryHistoricTimeseriesEnergyRequest"; @@ -27,9 +28,6 @@ import { TimeUtils } from "../../utils/time/timeutils"; import { Converter } from "../shared/converter"; import { ChartConstants, XAxisType } from "./chart.constants"; -import "chartjs-adapter-date-fns"; -import { JsonRpcUtils } from "../../jsonrpc/jsonrpcutils"; - Chart.Chart.register(annotationPlugin); // NOTE: Auto-refresh of widgets is currently disabled to reduce server load diff --git a/ui/src/app/shared/components/chart/chart.constants.ts b/ui/src/app/shared/components/chart/chart.constants.ts index fa8ccf10a71..2adc960c541 100644 --- a/ui/src/app/shared/components/chart/chart.constants.ts +++ b/ui/src/app/shared/components/chart/chart.constants.ts @@ -1,8 +1,8 @@ // @ts-strict-ignore -import { ChartComponentLike, ChartDataset } from "chart.js"; import { formatNumber } from "@angular/common"; import { TranslateService } from "@ngx-translate/core"; +import { ChartComponentLike, ChartDataset } from "chart.js"; import ChartDataLabels from "chartjs-plugin-datalabels"; import { HistoryUtils, Utils } from "../../service/utils"; import { Language } from "../../type/language"; diff --git a/ui/src/app/shared/components/components.module.ts b/ui/src/app/shared/components/components.module.ts index 8e39c2dc74e..cb3b444a6fb 100644 --- a/ui/src/app/shared/components/components.module.ts +++ b/ui/src/app/shared/components/components.module.ts @@ -9,9 +9,9 @@ import { PipeModule } from "../pipe/pipe"; import { ChartModule } from "./chart/chart.module"; import { FlatWidgetComponent } from "./flat/flat"; import { FlatWidgetHorizontalLineComponent } from "./flat/flat-widget-horizontal-line/flat-widget-horizontal-line"; -import { FlatWidgetLineDividerComponent } from "./flat/flat-widget-line-divider/flat-widget-line-divider"; import { FlatWidgetLineComponent } from "./flat/flat-widget-line/flat-widget-line"; import { FlatWidgetLineItemComponent } from "./flat/flat-widget-line/flat-widget-line-item/flat-widget-line-item"; +import { FlatWidgetLineDividerComponent } from "./flat/flat-widget-line-divider/flat-widget-line-divider"; import { FlatWidgetPercentagebarComponent } from "./flat/flat-widget-percentagebar/flat-widget-percentagebar"; import { FooterComponent } from "./footer/footer"; import { FooterNavigationModule } from "./footer/subnavigation/footerNavigation.module"; diff --git a/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts b/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts index 2d697a6a6e0..04821ded125 100644 --- a/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts +++ b/ui/src/app/shared/components/flat/abstract-flat-widget-line.ts @@ -4,8 +4,8 @@ import { ActivatedRoute } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; -import { ChannelAddress, Edge, Service, Websocket } from "src/app/shared/shared"; import { v4 as uuidv4 } from "uuid"; +import { ChannelAddress, Edge, Service, Websocket } from "src/app/shared/shared"; import { DataService } from "../shared/dataservice"; import { Filter } from "../shared/filter"; diff --git a/ui/src/app/shared/components/flat/abstract-flat-widget.ts b/ui/src/app/shared/components/flat/abstract-flat-widget.ts index bddb7f5283e..a8a1e654f82 100644 --- a/ui/src/app/shared/components/flat/abstract-flat-widget.ts +++ b/ui/src/app/shared/components/flat/abstract-flat-widget.ts @@ -1,14 +1,14 @@ // @ts-strict-ignore import { Directive, Inject, Input, OnDestroy, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; -import { ChannelAddress, CurrentData, Edge, EdgeConfig, Utils } from "src/app/shared/shared"; import { v4 as uuidv4 } from "uuid"; +import { ChannelAddress, CurrentData, Edge, EdgeConfig, Utils } from "src/app/shared/shared"; -import { FormBuilder, FormGroup } from "@angular/forms"; import { Service } from "../../service/service"; import { Websocket } from "../../service/websocket"; import { Converter } from "../shared/converter"; diff --git a/ui/src/app/shared/components/modal/abstract-modal-line.ts b/ui/src/app/shared/components/modal/abstract-modal-line.ts index f16f75a48f8..0db855672ea 100644 --- a/ui/src/app/shared/components/modal/abstract-modal-line.ts +++ b/ui/src/app/shared/components/modal/abstract-modal-line.ts @@ -6,8 +6,8 @@ import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; import { takeUntil } from "rxjs/operators"; -import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; import { v4 as uuidv4 } from "uuid"; +import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; import { Role } from "../../type/role"; import { Converter } from "../shared/converter"; diff --git a/ui/src/app/shared/components/modal/abstractModal.ts b/ui/src/app/shared/components/modal/abstractModal.ts index 225cf314e76..b6520994122 100644 --- a/ui/src/app/shared/components/modal/abstractModal.ts +++ b/ui/src/app/shared/components/modal/abstractModal.ts @@ -6,8 +6,8 @@ import { ModalController } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject, Subscription } from "rxjs"; import { takeUntil } from "rxjs/operators"; -import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; import { v4 as uuidv4 } from "uuid"; +import { ChannelAddress, CurrentData, Edge, EdgeConfig, Service, Utils, Websocket } from "src/app/shared/shared"; import { Role } from "../../type/role"; import { Converter } from "../shared/converter"; diff --git a/ui/src/app/shared/service/pagination.ts b/ui/src/app/shared/service/pagination.ts index f75a43c2f71..bef8d9d1e77 100644 --- a/ui/src/app/shared/service/pagination.ts +++ b/ui/src/app/shared/service/pagination.ts @@ -2,9 +2,9 @@ import { Directive } from "@angular/core"; import { Router } from "@angular/router"; import { SubscribeEdgesRequest } from "../jsonrpc/request/subscribeEdgesRequest"; +import { States } from "../ngrx-store/states"; import { ChannelAddress, Edge } from "../shared"; import { Service } from "./service"; -import { States } from "../ngrx-store/states"; @Directive() export class Pagination { diff --git a/ui/src/app/shared/utils/datetime/datetime-utils.ts b/ui/src/app/shared/utils/datetime/datetime-utils.ts index 70bb0b7cc58..83cec0673be 100644 --- a/ui/src/app/shared/utils/datetime/datetime-utils.ts +++ b/ui/src/app/shared/utils/datetime/datetime-utils.ts @@ -1,6 +1,9 @@ // @ts-strict-ignore +/* eslint-disable import/no-duplicates */ +// cf. https://github.com/import-js/eslint-plugin-import/issues/1479 import { format, startOfMonth, startOfYear } from "date-fns"; import { de } from "date-fns/locale"; +/* eslint-enable import/no-duplicates */ import { ChronoUnit } from "src/app/edge/history/shared"; import { QueryHistoricTimeseriesDataResponse } from "../../jsonrpc/response/queryHistoricTimeseriesDataResponse"; From 864d7ce2a89af8dbe4031e390436be1ef96e872a Mon Sep 17 00:00:00 2001 From: "Kai J." Date: Sun, 13 Oct 2024 14:55:00 +0200 Subject: [PATCH 04/19] UI: fix display error + percentagebar calculation (#2837) * fix reserve bar * fix energy chart cutoff * fix percentbar warning * fix on reserve soc --- ui/src/app/edge/live/common/storage/storage.component.html | 7 +++---- .../app/edge/live/energymonitor/chart/chart.component.html | 2 +- .../flat-widget-percentagebar.html | 6 +++--- .../flat-widget-percentagebar/flat-widget-percentagebar.ts | 6 +++++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/ui/src/app/edge/live/common/storage/storage.component.html b/ui/src/app/edge/live/common/storage/storage.component.html index d7ffcb21081..6f8c5db2bb1 100644 --- a/ui/src/app/edge/live/common/storage/storage.component.html +++ b/ui/src/app/edge/live/common/storage/storage.component.html @@ -15,10 +15,9 @@ - - + + diff --git a/ui/src/app/edge/live/energymonitor/chart/chart.component.html b/ui/src/app/edge/live/energymonitor/chart/chart.component.html index c2eddea61c8..d27b5bac185 100644 --- a/ui/src/app/edge/live/energymonitor/chart/chart.component.html +++ b/ui/src/app/edge/live/energymonitor/chart/chart.component.html @@ -1,7 +1,7 @@
- + diff --git a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.html b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.html index 5570abc71b8..4e42eb338d3 100644 --- a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.html +++ b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.html @@ -1,10 +1,10 @@ - + - {{ displayValue | unitvalue: '%' }} + {{ displayPercent | unitvalue: '%' }} diff --git a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts index 668caef9e60..3faf1948a9f 100644 --- a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts +++ b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts @@ -5,4 +5,8 @@ import { AbstractFlatWidgetLine } from "../abstract-flat-widget-line"; selector: "oe-flat-widget-percentagebar", templateUrl: "./flat-widget-percentagebar.html", }) -export class FlatWidgetPercentagebarComponent extends AbstractFlatWidgetLine { } +export class FlatWidgetPercentagebarComponent extends AbstractFlatWidgetLine { + protected get displayPercent(): number { + return Math.round(Number.parseFloat(this.displayValue)); + } +} From 1b18fa05789469a829c0e06f96e239e86ffd2334 Mon Sep 17 00:00:00 2001 From: j-eissler <57557568+j-eissler@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:14:13 +0200 Subject: [PATCH 05/19] Docs: add hint to README to fix an error with building a Docker image (#2833) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan Eißler --- tools/docker/backend/README.md | 6 ++++++ tools/docker/edge/README.md | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tools/docker/backend/README.md b/tools/docker/backend/README.md index 259f8b5c14d..a785852113b 100644 --- a/tools/docker/backend/README.md +++ b/tools/docker/backend/README.md @@ -78,3 +78,9 @@ ``` *for UI Image see [ui/README.md](../ui/README.md)* + +# Common Problems and Solutions +``` +ERROR: failed to solve: error from sender: context canceled +``` +When building the Docker image this error may occur because another program is accessing the project files. Try closing these programs (e.g. Eclipse IDE) and run the build command again. \ No newline at end of file diff --git a/tools/docker/edge/README.md b/tools/docker/edge/README.md index 441b234c9a4..2cce0d4d245 100644 --- a/tools/docker/edge/README.md +++ b/tools/docker/edge/README.md @@ -35,4 +35,11 @@ docker build . -t openems_edge -f tools/docker/edge/Dockerfile ``` - *for UI Image see [ui/README.md](../ui/README.md)* \ No newline at end of file + *for UI Image see [ui/README.md](../ui/README.md)* + +# Common Problems and Solutions +``` +ERROR: failed to solve: error from sender: context canceled +``` +When building the Docker image this error may occur because another program is accessing the project files. Try closing these programs (e.g. Eclipse IDE) and run the build command again. + From 0b2071c9b8db7441019f18f8bdf7ae13e4d36fa2 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sat, 19 Oct 2024 00:22:55 +0200 Subject: [PATCH 06/19] FEMS Backports 2024-10 (2) (#2846) - EVCS HardyBarth: communicate via HTTP-Bridge - Implemented BridgeHttp in Impl of HardyBarthEvcs - All Api Calls now flow through that Bridge - Api Calls (esp. setHeartBeat that previously was synchronous) are now asynchronous - Add JUnit tests - EVCS: implement ElectricityMeter - Migrate all EVCS to ElectricityMeter Nature - Add `DeprecatedEvcs` Nature to mark EVCS that have to be migrated and still support old Channels ChargePower and ActiveConsumptionEnergy - Implement generic `evaluatePhaseCount()` method - UI: Mix Evcs & ElectricityMeter (live != history) - Live: use ElectricityMeter if its already available (e.g. for power of individual phases) - History: use Evcs to ensure availability of historic data - Implement configurable PhaseRotation in configuration and Apps (copied and adjusted from #2047) - Fix AbstractManagedEvcs deactivate() - UI: Performance improvements - Navigation for users with at most Role `OWNER` with one fems assigned, get directly routed to `device/edgeId/live` - Navigation for users with at least Role `INSTALLER` -> `/overview` - Removed flickering of headerComponent. Issue results from creating header component for each view - Sum: do not ignore ExtremeEverValues in EdgeConfig - Update gradle to 8.10.2 - https://github.com/gradle/gradle/releases/tag/v8.10.2 - UI: Refactor heating element history - Refactoring HeatingElement and using the new ```Cumulated[Level 1 -3]ActiveTime``` - UI: Adjust Chart-axis generation - Multiple yAxis: Increase chart canvas by putting y axis labels into ticks, the most upper tick gets replaced by axis title - Extend export to Excel file - Added detailed data for the excel export of historic data - UI: fix header in history charts and add enable rescaling in history charts - Rescaling of charts was not possible due to not ignoring hidden values - Header was shown in the chart views - Introduce `` and keep `
` as single navigation point header - Java JUnit tests: improve framework + cleanup - Improve OpenEMS JUnit test framework - Apply best practices to JUnit tests --------- Co-authored-by: Lukas Rieger <73471197+lukasrgr@users.noreply.github.com> Co-authored-by: Sebastian Asen <47855186+sebastianasen@users.noreply.github.com> Co-authored-by: Stefan Feilmeier <3515268+sfeilmeier@users.noreply.github.com> Co-authored-by: Johann Kaufmann <165755282+johannk24@users.noreply.github.com> --- .gradle-wrapper/gradle-wrapper.properties | 2 +- .../EdgeRpcRequestHandler.java | 45 +- .../$basePackageDir$/MyControllerTest.java | 9 +- .../$basePackageDir$/MyModbusDeviceTest.java | 14 +- .../$basePackageDir$/MyDeviceTest.java | 9 +- ...yHistoricTimeseriesExportXlsxResponse.java | 274 +++++- .../response/translation_de.properties | 7 +- .../response/translation_en.properties | 7 +- .../timedata/CommonTimedataService.java | 36 - .../common/timedata/XlsxExportDetailData.java | 37 + .../common/timedata/XlsxExportUtil.java | 112 +++ .../common/timedata/XlsxWorksheetWrapper.java | 46 + .../openems/common/types/CurrencyConfig.java | 49 ++ .../io/openems/common/types/EdgeConfig.java | 42 +- .../openems/common/websocket/MyDraft6455.java | 1 + .../jsonrpc/response/CreateXlxsTest.java | 248 ++++++ .../protection/BatteryProtectionTest.java | 250 +++--- .../edge/battery/bmw/BmwBatteryImplTest.java | 18 +- .../BydBatteryBoxCommercialC130ImplTest.java | 20 +- .../BatteryFeneconCommercialImplTest.java | 94 +-- .../DynamicChannelsAndSerialNumbersTest.java | 34 +- .../home/FeneconHomeBatteryProtection64.java | 6 +- .../home/BatteryFeneconHomeImplTest.java | 375 ++++----- .../fenecon/home/TowersAndModulesTest.java | 25 +- ...BatterySoltaroClusterVersionBImplTest.java | 28 +- ...BatterySoltaroClusterVersionCImplTest.java | 9 +- ...terySoltaroSingleRackVersionAImplTest.java | 9 +- ...terySoltaroSingleRackVersionBImplTest.java | 9 +- ...terySoltaroSingleRackVersionCImplTest.java | 9 +- ...nverterKacoBlueplanetGridsaveImplTest.java | 62 +- .../BatteryInverterRefuStore88kImplTest.java | 9 +- .../BatteryInverterSinexcelImplTest.java | 34 +- .../http/dummy/DummyBridgeHttpBundle.java | 7 +- .../http/dummy/DummyBridgeHttpExecutor.java | 10 + .../http/dummy/DummyBridgeHttpFactory.java | 25 + .../bridge/http/api/BridgeHttpCycleTest.java | 3 +- .../edge/bridge/http/api/BridgeHttpTest.java | 5 +- .../bridge/http/api/BridgeHttpTimeTest.java | 3 +- .../modbus/BridgeModbusSerialImplTest.java | 4 +- .../modbus/BridgeModbusTcpImplTest.java | 23 +- .../internal/DefectiveComponentsTest.java | 7 +- .../internal/TasksSupplierImplTest.java | 3 +- .../onewire/impl/BridgeOnewireImplTest.java | 4 +- .../edge/common/currency/Currency.java | 15 + .../edge/common/currency/CurrencyConfig.java | 38 - .../common/test/AbstractComponentTest.java | 256 +++++- .../common/test/DummyComponentManager.java | 4 +- .../openems/edge/common/test/TestUtils.java | 11 + .../backend/ControllerApiBackendImplTest.java | 4 +- .../common/handler/QueryRequestHandler.java | 45 +- ...ontrollerApiModbusTcpReadOnlyImplTest.java | 11 +- ...ntrollerApiModbusTcpReadWriteImplTest.java | 23 +- .../api/modbus/readwrite/MyConfig.java | 5 - .../api/mqtt/ControllerApiMqttImplTest.java | 8 +- .../ControllerApiRestReadOnlyImplTest.java | 7 +- .../ControllerApiRestReadWriteImplTest.java | 18 +- .../ControllerApiWebsocketImplTest.java | 10 +- ...llerAsymmetricBalancingCosPhiImplTest.java | 18 +- ...lerAsymmetricFixReactivePowerImplTest.java | 13 +- ...ntrollerAsymmetricPeakShavingImplTest.java | 172 ++-- ...rAsymmetricPhaseRectificationImplTest.java | 18 +- .../ControllerChannelThresholdImplTest.java | 11 +- .../controller/channelthreshold/MyConfig.java | 9 +- .../chp/soc/ControllerChpSocImplTest.java | 59 +- .../ControllerDebugDetailedLogImplTest.java | 7 +- .../debuglog/ControllerDebugLogImplTest.java | 56 +- .../edge/controller/debuglog/MyConfig.java | 4 +- .../ControllerEssAcIslandImplTest.java | 19 +- .../CharacteristicImplTest.java | 63 +- .../ess/balancing/BalancingImplTest.java | 110 ++- .../ess/cycle/ControllerEssCycleImplTest.java | 94 +-- .../ControllerEssDelayChargeImplTest.java | 42 +- ...ontrollerEssDelayedSellToGridImplTest.java | 84 +- ...erEssEmergencyCapacityReserveImplTest.java | 340 ++++---- .../statemachine/ActivationTimeHandler.java | 2 +- ...ntrollerFastFrequencyReserveImplTest.java} | 247 +++--- ...trollerFastFrequencyReserveImplTest2.java} | 141 ++-- .../ess/fastfrequencyreserve/MyConfig.java | 2 - .../ControllerEssFixActivePowerImplTest.java | 28 +- ...ControllerEssFixStateOfChargeImplTest.java | 792 +++++++++--------- ...trollerEssGridOptimizedChargeImplTest.java | 529 ++++++------ ...lerEssHybridSurplusFeedToGridImplTest.java | 32 +- .../ControllerEssLimiter14aImpl.java | 1 + .../ControllerEssLimiter14aTest.java | 42 +- ...trollerEssLimitTotalDischargeImplTest.java | 49 +- .../ControllerEssLinearPowerBandImplTest.java | 49 +- ...ollerEssMinimumDischargePowerImplTest.java | 11 +- ...ivePowerVoltageCharacteristicImplTest.java | 106 ++- .../ControllerEssSellToGridLimitImplTest.java | 40 +- .../standby/ControllerEssStandbyImplTest.java | 141 ++-- .../TimeOfUseTariffControllerImplTest.java | 14 +- .../bnd.bnd | 3 +- .../ControllerEvcsFixActivePowerImplTest.java | 11 +- io.openems.edge.controller.evcs/bnd.bnd | 3 +- .../evcs/ChargingLowerThanTargetHandler.java | 2 +- .../controller/evcs/ControllerEvcsImpl.java | 12 +- .../evcs/ControllerEvcsImplTest.java | 359 ++++---- .../ControllerGenericJsonLogicImplTest.java | 31 +- .../ControllerGenericJsonLogicImplTest2.java | 84 +- .../ControllerHighLoadTimeslotImplTest.java | 16 +- .../io/alarm/ControllerIoAlarmImplTest.java | 51 +- .../io/analog/MyControllerTest.java | 100 +-- ...ollerIoChannelSingleThresholdImplTest.java | 35 +- .../ControllerIoFixDigitalOutputImplTest.java | 18 +- .../ControllerHeatingElementImplTest4.java | 57 +- .../ControllerIoHeatingElementImplTest.java | 236 +++--- .../ControllerIoHeatingElementImplTest2.java | 158 ++-- .../ControllerIoHeatingElementImplTest3.java | 60 +- .../ControllerIoHeatPumpSgReadyImplTest.java | 334 ++++---- ...rollerPvInverterFixPowerLimitImplTest.java | 11 +- ...llerPvInverterSellToGridLimitImplTest.java | 199 +++-- ...ontrollerEssBalancingScheduleImplTest.java | 88 +- ...ControllerEssFixReactivePowerImplTest.java | 11 +- ...ControllerEssLimitActivePowerImplTest.java | 11 +- .../ControllerEssPeakShavingImplTest.java | 110 ++- .../ControllerEssRandomPowerImplTest.java | 10 +- ...trollerEssTimeslotPeakshavingImplTest.java | 80 +- .../io/openems/edge/app/evcs/EvcsProps.java | 24 +- .../openems/edge/app/evcs/HardyBarthEvcs.java | 5 + .../io/openems/edge/app/evcs/KebaEvcs.java | 6 +- .../core/appmanager/translation_de.properties | 2 + .../core/appmanager/translation_en.properties | 2 + .../componentmanager/EdgeConfigWorker.java | 2 +- .../src/io/openems/edge/core/meta/Config.java | 2 +- .../io/openems/edge/core/meta/MetaImpl.java | 3 +- .../src/io/openems/edge/core/sum/SumImpl.java | 3 +- .../TestFeneconHome30DefaultRelays.java | 6 +- .../TestFeneconHomeDefaultRelays.java | 6 +- .../openems/edge/core/meta/MetaImplTest.java | 2 +- .../io/openems/edge/core/meta/MyConfig.java | 2 +- .../PredictorManagerImplTest.java | 6 +- .../edge/core/sum/ExtremeEverValuesTest.java | 17 +- .../io/openems/edge/core/sum/SumImplTest.java | 12 +- .../edge2edge/ess/Edge2EdgeEssImplTest.java | 11 +- .../meter/Edge2EdgeEssMeterImplTest.java | 16 +- .../edge/energy/EnergySchedulerImplTest.java | 4 +- .../edge/energy/optimizer/UtilsTest.java | 17 +- .../EssFeneconBydContainerImplTest.java | 19 +- ...ydContainerWatchdogControllerImplTest.java | 81 -- .../byd/container/watchdog/MyEssConfig.java | 101 --- .../container/watchdog/MyWatchdogConfig.java | 51 -- .../edge/ess/cluster/EssClusterImplTest.java | 184 ++-- .../ess/core/power/PowerComponentTest.java | 171 ++-- .../ess/core/power/PowerComponentTest2.java | 22 +- .../EssFeneconCommercial40ImplTest.java | 9 +- .../EssFeneconCommercial40Pv1ImplTest.java | 18 +- .../EssFeneconCommercial40Pv2ImplTest.java | 18 +- .../AllowedChargeDischargeHandlerTest.java | 29 +- .../offgrid/EssGenericOffGridImplTest.java | 26 +- .../EssGenericManagedSymmetricImplTest.java | 110 ++- .../generic/symmetric/EssProtectionTest.java | 656 +++++++-------- .../EssSmaSunnyIslandImplTest.java | 9 +- .../bnd.bnd | 1 + .../EvcsAlpitronicHyperchargerImpl.java | 77 +- .../EvcsAlpitronicHyperchargerImplTest.java | 9 +- io.openems.edge.evcs.api/bnd.bnd | 2 +- .../api/AbstractManagedEvcsComponent.java | 18 +- .../openems/edge/evcs/api/DeprecatedEvcs.java | 58 ++ .../src/io/openems/edge/evcs/api/Evcs.java | 160 +--- .../openems/edge/evcs/api/MeasuringEvcs.java | 39 +- .../openems/edge/evcs/api/PhaseRotation.java | 77 ++ .../openems/edge/evcs/api/WriteHandler.java | 11 +- .../edge/evcs/test/DummyManagedEvcs.java | 8 +- .../evcs/api/AbstractManagedEvcsTest.java | 541 ++++++------ .../cluster/EvcsClusterPeakShavingImpl.java | 51 +- .../EvcsClusterPeakShavingImplTest.java | 766 ++++++++--------- .../openems/edge/evcs/cluster/MyConfig.java | 14 +- io.openems.edge.evcs.dezony/bnd.bnd | 3 +- .../edge/evcs/dezony/DezonyReadWorker.java | 89 +- .../edge/evcs/dezony/EvcsDezonyImpl.java | 26 +- .../edge/evcs/dezony/EvcsDezonyImplTest.java | 4 +- io.openems.edge.evcs.goe.chargerhome/bnd.bnd | 3 +- .../goe/chargerhome/EvcsGoeChargerHome.java | 10 +- .../chargerhome/EvcsGoeChargerHomeImpl.java | 58 +- .../EvcsGoeChargerHomeImplTest.java | 4 +- io.openems.edge.evcs.hardybarth/bnd.bnd | 4 +- .../openems/edge/evcs/hardybarth/Config.java | 5 + .../edge/evcs/hardybarth/EvcsHardyBarth.java | 199 +++-- .../evcs/hardybarth/EvcsHardyBarthImpl.java | 186 ++-- .../edge/evcs/hardybarth/HardyBarthApi.java | 154 ---- .../evcs/hardybarth/HardyBarthReadUtils.java | 272 ++++++ .../evcs/hardybarth/HardyBarthReadWorker.java | 318 ------- .../hardybarth/HardyBarthWriteHandler.java | 31 + .../hardybarth/EvcsHardyBarthImplTest.java | 267 +++++- .../edge/evcs/hardybarth/MyConfig.java | 12 + io.openems.edge.evcs.keba.kecontact/bnd.bnd | 3 +- .../edge/evcs/keba/kecontact/Config.java | 5 + .../keba/kecontact/EvcsKebaKeContact.java | 44 +- .../keba/kecontact/EvcsKebaKeContactImpl.java | 20 +- .../edge/evcs/keba/kecontact/ReadHandler.java | 508 +++++------ .../kecontact/EvcsKebaKeContactImplTest.java | 126 ++- .../edge/evcs/keba/kecontact/MyConfig.java | 14 +- .../edge/evcs/ocpp/abl/EvcsOcppAbl.java | 4 +- .../edge/evcs/ocpp/abl/EvcsOcppAblImpl.java | 5 +- .../evcs/ocpp/abl/EvcsOcppAblImplTest.java | 4 +- .../AbstractManagedOcppEvcsComponent.java | 54 +- .../evcs/ocpp/common/OcppInformations.java | 8 +- .../singleccs/EvcsOcppIesKeywattSingle.java | 3 +- .../EvcsOcppIesKeywattSingleImpl.java | 7 +- .../EvcsOcppIesKeywattSingleImplTest.java | 4 +- io.openems.edge.evcs.ocpp.server/bnd.bnd | 1 + .../ocpp/server/CoreEventHandlerImpl.java | 39 +- io.openems.edge.evcs.spelsberg/bnd.bnd | 1 + .../spelsberg/smart/EvcsSpelsbergSmart.java | 78 -- .../smart/EvcsSpelsbergSmartImpl.java | 41 +- .../smart/EvcsSpelsbergSmartImplTest.java | 9 +- io.openems.edge.evcs.webasto.next/bnd.bnd | 1 + .../evcs/webasto/next/EvcsWebastoNext.java | 75 -- .../webasto/next/EvcsWebastoNextImpl.java | 49 +- .../webasto/next/EvcsWebastoNextImplTest.java | 9 +- io.openems.edge.evcs.webasto.unite/bnd.bnd | 1 + .../evcs/webasto/unite/EvcsWebastoUnite.java | 110 +-- .../webasto/unite/EvcsWebastoUniteImpl.java | 24 +- .../webasto/unite/WebastoReadHandler.java | 56 +- .../unite/EvcsWebastoUniteImplTest.java | 9 +- .../dess/charger/FeneconDessCharger1Test.java | 16 +- .../dess/charger/FeneconDessCharger2Test.java | 16 +- .../dess/ess/FeneconDessEssImplTest.java | 9 +- .../FeneconDessGridMeterImplTest.java | 9 +- .../pvmeter/FeneconDessPvMeterImplTest.java | 9 +- .../mini/ess/FeneconMiniEssImplTest.java | 125 ++- .../FeneconMiniGridMeterImplTest.java | 9 +- .../pvmeter/FeneconMiniPvMeterImplTest.java | 9 +- .../pro/ess/FeneconProEssImplTest.java | 9 +- .../pvmeter/FeneconProPvMeterImplTest.java | 9 +- .../GoodWeBatteryInverterImplTest.java | 409 ++++----- .../GoodWeChargerMpptTwoStringImplTest.java | 191 ++--- .../singlestring/GoodWeChargerPv1Test.java | 12 +- .../singlestring/GoodWeChargerPv2Test.java | 12 +- .../GoodWeChargerTwoStringImplTest.java | 7 +- .../GoodWeEmergencyPowerMeterTest.java | 10 +- .../edge/goodwe/ess/GoodWeEssImplTest.java | 33 +- .../gridmeter/GoodWeGridMeterImplTest.java | 85 +- .../edge/io/test/DummyCustomInputOutput.java | 57 ++ .../edge/io/test/DummyInputOutput.java | 59 +- .../analog/mr/IoFilipowskiMrAo1ImplTest.java | 9 +- .../openems/edge/io/gpio/ModberryCM4Test.java | 89 +- .../eight/IoKmtronicRelay8PortImplTest.java | 9 +- .../four/IoKmtronicRelay4PortImplTest.java | 9 +- .../IoOffGridSwitchImplTest.java | 26 +- .../IoRevolutionPiDigitalIoImplTest.java | 2 - .../shelly/shellypro3em/IoShellyPro3Em.java | 2 +- .../shelly/shelly25/IoShelly25ImplTest.java | 9 +- .../shelly/shelly3em/IoShelly3EmImplTest.java | 13 +- .../shellyplug/IoShellyPlugImplTest.java | 13 +- .../IoShellyPlus1PmImplTest.java | 39 +- .../shellyplusplugs/IoShellyPlugImplTest.java | 39 +- .../io/shelly/shellyplusplugs/MyConfig.java | 1 - .../shellypro3/IoShellyPro3ImplTest.java | 4 +- .../shellypro3em/IoShelly3EmImplTest.java | 4 +- .../io/openems/edge/wago/IoWagoImplTest.java | 13 +- .../IoWeidmuellerUr20ImplTest.java | 9 +- .../KacoBlueplanetHybrid10CoreImplTest.java | 4 +- .../KacoBlueplanetHybrid10EssImplTest.java | 10 +- ...KacoBlueplanetHybrid10ChargerImplTest.java | 7 +- ...oBlueplanetHybrid10PvInverterImplTest.java | 7 +- ...coBlueplanetHybrid10GridMeterImplTest.java | 7 +- .../charger/KostalPikoChargerImplTest.java | 4 +- .../core/impl/KostalPikoCoreImplTest.java | 4 +- .../piko/ess/KostalPikoEssImplTest.java | 4 +- .../KostalPikoGridMeterImplTest.java | 4 +- .../artemes/am2/MeterArtemesAM2ImplTest.java | 14 +- .../em300/MeterBControlEM300ImplTest.java | 14 +- .../aplus/MeterCamillebauerAplusImplTest.java | 16 +- .../em300/MeterCarloGavazziEm300ImplTest.java | 14 +- .../discovergy/MeterDiscovergyImplTest.java | 9 +- .../sdm120/MeterEastronSdm120ImplTest.java | 18 +- .../sdm630/MeterEastronSdm630ImplTest.java | 14 +- .../umg511/MeterJanitzaUmg511ImplTest.java | 14 +- .../umg604/MeterJanitzaUmg604ImplTest.java | 14 +- .../MeterJanitzaUmg96rmeImplTest.java | 14 +- .../kdk/puct2/MeterKdk2puctImplTest.java | 14 +- .../PhoenixContactMeterImplTest.java | 14 +- .../MeterPlexlogDataloggerImplTest.java | 14 +- .../umd96/MeterPqplusUmd96ImplTest.java | 14 +- .../umd97/MeterPqplusUmd97ImplTest.java | 14 +- .../MeterSchneiderActi9SmartlinkImplTest.java | 14 +- .../pac1600/MeterSiemensPac1600ImplTest.java | 14 +- .../pac2200/MeterSiemensPac2200ImplTest.java | 14 +- .../sma/shm20/MeterSmaShm20ImplTest.java | 14 +- .../MeterSocomecSinglephaseImplTest.java | 18 +- .../MeterSocomecThreephaseImplTest.java | 14 +- .../virtual/add/MeterVirtualAddImplTest.java | 106 +-- .../VirtualSubtractMeterImplTest.java | 62 +- .../MeterWeidmueller525ImplTest.java | 14 +- .../MeterZiehlEfr4001IpImplTest.java | 14 +- .../PredictorPersistenceModelImplTest.java | 39 +- .../PredictorSimilardayModelImplTest.java | 7 +- .../cluster/PvInverterClusterImplTest.java | 4 +- .../fronius/PvInverterFroniusImplTest.java | 9 +- .../PvInverterKacoBlueplanetImplTest.java | 9 +- .../kostal/PvInverterKostalImplTest.java | 9 +- .../PvInverterSmaSunnyTripowerImplTest.java | 14 +- .../solarlog/PvInverterSolarlogImplTest.java | 9 +- .../SchedulerAllAlphabeticallyImplTest.java | 26 +- .../daily/SchedulerDailyImplTest.java | 59 +- .../SchedulerFixedOrderImplTest.java | 3 +- .../datasource/api/DummyDatasource.java | 38 + .../edge/simulator/evcs/SimulatorEvcs.java | 25 +- .../simulator/evcs/SimulatorEvcsImpl.java | 14 +- .../acting/SimulatorGridMeterActingImpl.java | 4 +- .../battery/SimulatorBatteryImplTest.java | 4 +- ...ulatorDatasourceCsvPredefinedImplTest.java | 4 +- ...mulatorDatasourceSingleDirectImplTest.java | 4 +- .../simulator/evcs/SimulatorEvcsImplTest.java | 4 +- ...SimulatorIoDigitalInputOutputImplTest.java | 4 +- .../SimulatorGridMeterActingImplTest.java | 27 +- .../SimulatorGridMeterReactingImplTest.java | 4 +- .../modbus/SimulatorModbusImplTest.java | 4 +- .../SimulatorPvInverterImplTest.java | 7 +- .../SimulatorThermometerImplTest.java | 4 +- .../timedata/SimulatorTimedataImplTest.java | 4 +- .../gridmeter/SolarEdgeGridMeterImplTest.java | 14 +- .../SolarEdgePvInverterImplTest.java | 14 +- .../influxdb/TimedataInfluxDbImplTest.java | 13 +- .../timedata/rrd4j/TimedataRrd4jImplTest.java | 8 +- .../TimeOfUseTariffAwattarImplTest.java | 7 +- .../TimeOfUseTariffCorrentlyImplTest.java | 4 +- .../timeofusetariff/entsoe/TouEntsoeTest.java | 4 +- .../groupe/TimeOfUseTariffGroupeImplTest.java | 13 +- .../TimeOfUseTariffHassfurtImplTest.java | 16 +- .../TimeOfUseTariffRabotChargeImplTest.java | 17 +- .../tibber/TimeOfUseTariffTibberImplTest.java | 7 +- package-lock.json | 6 + ui/.project | 11 + ui/README.md | 5 +- ui/src/app/app-routing.module.ts | 2 +- ui/src/app/app.component.html | 82 +- ui/src/app/changelog/view/view.html | 1 - .../edge/history/Controller/Io/Io.module.ts | 3 + .../Io/heatingelement/chart/chart.ts | 82 ++ .../Io/heatingelement/flat/flat.html | 8 + .../Controller/Io/heatingelement/flat/flat.ts | 10 + .../heatingelement/heatingelement.module.ts | 24 + .../Io/heatingelement/overview/overview.html | 4 + .../Io/heatingelement/overview/overview.ts | 8 + .../history/common/consumption/chart/chart.ts | 8 +- .../history/common/consumption/flat/flat.ts | 11 +- .../common/consumption/overview/overview.ts | 3 +- .../common/energy/chart/channels.spec.ts | 12 +- .../history/common/energy/chart/chart.spec.ts | 4 +- .../history/common/grid/chart/chart.spec.ts | 17 +- .../details/chart/productionMeter.spec.ts | 7 - .../history/heatingelement/chart.component.ts | 126 --- ...heatingelementchartoverview.component.html | 23 - .../heatingelementchartoverview.component.ts | 31 - .../heatingelement/widget.component.html | 37 - .../heatingelement/widget.component.ts | 77 -- .../app/edge/history/history.component.html | 5 +- ui/src/app/edge/history/history.component.ts | 11 +- ui/src/app/edge/history/history.module.ts | 11 +- .../edge/live/common/consumption/flat/flat.ts | 7 +- .../live/common/consumption/modal/modal.ts | 7 +- ui/src/app/edge/live/live.component.html | 1 - .../settings/alerting/alerting.component.html | 2 +- .../edge/settings/app/index.component.html | 2 +- .../edge/settings/app/install.component.html | 1 - .../edge/settings/app/single.component.html | 1 - .../edge/settings/app/update.component.html | 1 - .../settings/channels/channels.component.html | 1 - .../component/install/index.component.html | 1 - .../component/install/install.component.html | 1 - .../component/update/index.component.html | 1 - .../component/update/update.component.html | 1 - .../settings/jsonrpctest/jsonrpctest.html | 1 - .../settings/network/network.component.html | 1 - .../powerassistant/powerassistant.html | 2 +- .../profile/aliasupdate.component.html | 1 - .../settings/profile/profile.component.html | 1 - .../app/edge/settings/settings.component.html | 1 - ui/src/app/edge/settings/settings.module.ts | 1 - .../settings/system/system.component.html | 1 - .../systemexecute.component.html | 1 - .../systemlog/systemlog.component.html | 1 - ui/src/app/index/login.component.ts | 11 +- .../index/overview/overview.component.html | 1 - .../app/index/overview/overview.component.ts | 10 +- .../components/chart/abstracthistorychart.ts | 37 +- .../components/chart/chart.constants.spec.ts | 12 +- .../components/chart/chart.constants.ts | 60 +- ui/src/app/shared/components/edge/edge.ts | 2 +- .../app/shared/components/edge/edgeconfig.ts | 48 +- .../flat-widget-percentagebar.ts | 7 +- .../shared/components/header/app-header.ts | 222 +++++ .../components/header/header.component.html | 9 +- .../components/header/header.component.ts | 11 +- .../components/shared/testing/common.ts | 21 +- .../components/shared/testing/tester.ts | 11 +- ui/src/app/shared/ngrx-store/states.ts | 19 +- ui/src/app/shared/service/arrayutils.ts | 27 - .../app/shared/service/defaulttypes.spec.ts | 17 + ui/src/app/shared/service/defaulttypes.ts | 25 + ui/src/app/shared/service/utils.ts | 14 +- ui/src/app/shared/shared.module.ts | 72 +- ui/src/app/shared/utils/array/array.utils.ts | 19 +- .../app/shared/utils/object/object.utils.ts | 8 + ui/src/app/user/user.component.html | 1 - ui/src/assets/i18n/de.json | 5 +- ui/src/assets/i18n/en.json | 7 +- 399 files changed, 8998 insertions(+), 9086 deletions(-) create mode 100644 io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java create mode 100644 io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java create mode 100644 io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java create mode 100644 io.openems.common/src/io/openems/common/types/CurrencyConfig.java create mode 100644 io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java delete mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java rename io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/{MyControllerTest.java => ControllerFastFrequencyReserveImplTest.java} (51%) rename io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/{MyControllerTest2.java => ControllerFastFrequencyReserveImplTest2.java} (53%) delete mode 100644 io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/EssFeneconBydContainerWatchdogControllerImplTest.java delete mode 100644 io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyEssConfig.java delete mode 100644 io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyWatchdogConfig.java create mode 100644 io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/DeprecatedEvcs.java create mode 100644 io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/PhaseRotation.java delete mode 100644 io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthApi.java create mode 100644 io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadUtils.java delete mode 100644 io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java create mode 100644 io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java create mode 100644 io.openems.edge.io.api/src/io/openems/edge/io/test/DummyCustomInputOutput.java create mode 100644 io.openems.edge.simulator/src/io/openems/edge/simulator/datasource/api/DummyDatasource.java create mode 100644 package-lock.json create mode 100644 ui/.project create mode 100644 ui/src/app/edge/history/Controller/Io/heatingelement/chart/chart.ts create mode 100644 ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.html create mode 100644 ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.ts create mode 100644 ui/src/app/edge/history/Controller/Io/heatingelement/heatingelement.module.ts create mode 100644 ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.html create mode 100644 ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.ts delete mode 100644 ui/src/app/edge/history/heatingelement/chart.component.ts delete mode 100644 ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.html delete mode 100644 ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.ts delete mode 100644 ui/src/app/edge/history/heatingelement/widget.component.html delete mode 100644 ui/src/app/edge/history/heatingelement/widget.component.ts create mode 100644 ui/src/app/shared/components/header/app-header.ts delete mode 100644 ui/src/app/shared/service/arrayutils.ts create mode 100644 ui/src/app/shared/service/defaulttypes.spec.ts create mode 100644 ui/src/app/shared/utils/object/object.utils.ts diff --git a/.gradle-wrapper/gradle-wrapper.properties b/.gradle-wrapper/gradle-wrapper.properties index 0aaefbcaf0f..df97d72b8b9 100644 --- a/.gradle-wrapper/gradle-wrapper.properties +++ b/.gradle-wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java b/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java index c98e855c17d..5c4e6ef0109 100644 --- a/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java +++ b/io.openems.backend.core/src/io/openems/backend/core/jsonrpcrequesthandler/EdgeRpcRequestHandler.java @@ -5,8 +5,12 @@ import static io.openems.common.utils.JsonUtils.getAsString; import static java.util.Collections.emptyMap; +import java.io.IOException; +import java.time.temporal.ChronoUnit; +import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.TreeSet; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -15,6 +19,7 @@ import io.openems.backend.common.metadata.User; import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.jsonrpc.request.AppCenterRequest; import io.openems.common.jsonrpc.request.ComponentJsonApiRequest; @@ -29,7 +34,13 @@ import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesDataResponse; import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyPerPeriodResponse; import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyResponse; +import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse; +import io.openems.common.session.Language; import io.openems.common.session.Role; +import io.openems.common.timedata.Resolution; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.timedata.XlsxExportUtil; +import io.openems.common.types.ChannelAddress; public class EdgeRpcRequestHandler { @@ -265,8 +276,38 @@ private CompletableFuture handleQueryHistoricEnergyPerPe */ private CompletableFuture handleQueryHistoricTimeseriesExportXlxsRequest(String edgeId, User user, QueryHistoricTimeseriesExportXlxsRequest request) throws OpenemsNamedException { - return CompletableFuture.completedFuture(this.parent.timedataManager - .handleQueryHistoricTimeseriesExportXlxsRequest(edgeId, request, user.getLanguage())); + return CompletableFuture.completedFuture( + this.handleQueryHistoricTimeseriesExportXlxsRequest(edgeId, request, user.getLanguage())); + } + + private QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTimeseriesExportXlxsRequest(String edgeId, + QueryHistoricTimeseriesExportXlxsRequest request, Language language) throws OpenemsNamedException { + final var powerChannels = new TreeSet(QueryHistoricTimeseriesExportXlsxResponse.POWER_CHANNELS); + final var energyChannels = new TreeSet( + QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS); + + final var edge = this.parent.metadata.edge().getEdgeConfig(edgeId); + + final var detailData = XlsxExportUtil.getDetailData(edge); + final var channelsByType = detailData.getChannelsBySaveType(); + powerChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.POWER, Collections.emptyList())); + energyChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.ENERGY, Collections.emptyList())); + + var powerData = this.parent.timedataManager.queryHistoricData(edgeId, request.getFromDate(), + request.getToDate(), powerChannels, new Resolution(15, ChronoUnit.MINUTES)); + + var energyData = this.parent.timedataManager.queryHistoricEnergy(edgeId, request.getFromDate(), + request.getToDate(), energyChannels); + if (powerData == null || energyData == null) { + return null; + } + try { + return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), edgeId, request.getFromDate(), + request.getToDate(), powerData, energyData, language, detailData); + + } catch (IOException e) { + throw new OpenemsException("QueryHistoricTimeseriesExportXlxsRequest failed: " + e.getMessage()); + } } /** diff --git a/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java b/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java index 2205624e13f..41b5f380e43 100644 --- a/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java +++ b/io.openems.common/resources/templates/controller/$testSrcDir$/$basePackageDir$/MyControllerTest.java @@ -7,15 +7,14 @@ public class MyControllerTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { new ControllerTest(new MyControllerImpl()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .build()) - .next(new TestCase()); + .setId("ctrl0") // + .build()) // + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java index c9961ad3a77..eb60ad3df76 100644 --- a/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java +++ b/io.openems.common/resources/templates/device-modbus/$testSrcDir$/$basePackageDir$/MyModbusDeviceTest.java @@ -9,19 +9,17 @@ public class MyModbusDeviceTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MyModbusDeviceImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // - .build()) - .next(new TestCase()); + .setId("component0") // + .setModbusId("modbus0") // + .build()) // + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java b/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java index d5cb74df7c9..f02eed1d7f9 100644 --- a/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java +++ b/io.openems.common/resources/templates/device/$testSrcDir$/$basePackageDir$/MyDeviceTest.java @@ -7,15 +7,14 @@ public class MyDeviceTest { - private static final String COMPONENT_ID = "component0"; - @Test public void test() throws Exception { new ComponentTest(new MyDeviceImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .build()) - .next(new TestCase()); + .setId("component0") // + .build()) // + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java b/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java index 6158477f132..acf64aba840 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/response/QueryHistoricTimeseriesExportXlsxResponse.java @@ -6,15 +6,19 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map.Entry; import java.util.ResourceBundle; import java.util.Set; import java.util.SortedMap; import java.util.UUID; +import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.dhatim.fastexcel.BorderSide; +import org.dhatim.fastexcel.BorderStyle; import org.dhatim.fastexcel.Workbook; import org.dhatim.fastexcel.Worksheet; @@ -22,6 +26,10 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.session.Language; +import io.openems.common.timedata.XlsxExportDetailData; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry; +import io.openems.common.timedata.XlsxWorksheetWrapper; import io.openems.common.types.ChannelAddress; import io.openems.common.utils.JsonUtils; @@ -60,6 +68,11 @@ protected static class Channel { public static final ChannelAddress ESS_SOC = new ChannelAddress("_sum", "EssSoc"); } + private static final String BLUE = "44B3E1"; + private static final String LIGHT_GREY = "BFBFBF"; + private static final String DARK_GREY = "D9D9D9"; + private static final String FONT_NAME = "Calibri"; + /** * All Power Channels, i.e. Channels that are exported per channel and * timestamp. @@ -91,21 +104,24 @@ protected static class Channel { * While constructing, the actual Excel file is generated as payload of the * JSON-RPC Response. * - * @param id the JSON-RPC ID - * @param edgeId the Edge-ID - * @param fromDate the start date of the export - * @param toDate the end date of the export - * @param historicData the power data per channel and timestamp - * @param historicEnergy the energy data, one value per channel - * @param language the {@link Language} + * @param id the JSON-RPC ID + * @param edgeId the Edge-ID + * @param fromDate the start date of the export + * @param toDate the end date of the export + * @param historicData the power data per channel and timestamp + * @param historicEnergy the energy data, one value per channel + * @param language the {@link Language} + * @param detailComponents the components for the detail view * @throws IOException on error * @throws OpenemsNamedException on error */ public QueryHistoricTimeseriesExportXlsxResponse(UUID id, String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, SortedMap> historicData, - SortedMap historicEnergy, Language language) - throws IOException, OpenemsNamedException { - super(id, XlsxUtils.generatePayload(edgeId, fromDate, toDate, historicData, historicEnergy, language)); + SortedMap historicEnergy, Language language, + XlsxExportDetailData detailComponents) throws IOException, OpenemsNamedException { + + super(id, XlsxUtils.generatePayload(edgeId, fromDate, toDate, historicData, historicEnergy, language, + detailComponents)); } protected static class XlsxUtils { @@ -118,20 +134,21 @@ protected static class XlsxUtils { * Generates the Payload for a * {@link QueryHistoricTimeseriesExportXlsxResponse}. * - * @param edgeId the Edge-Id - * @param fromDate the start date of the export - * @param toDate the end date of the export - * @param powerData the power data per channel and timestamp - * @param energyData the energy data, one value per channel - * @param language the {@link Language} + * @param edgeId the Edge-Id + * @param fromDate the start date of the export + * @param toDate the end date of the export + * @param powerData the power data per channel and timestamp + * @param energyData the energy data, one value per channel + * @param language the {@link Language} + * @param detailComponents the components for the detail view * @return the Excel file as byte-array. * @throws IOException on error * @throws OpenemsNamedException on error */ private static byte[] generatePayload(String edgeId, ZonedDateTime fromDate, ZonedDateTime toDate, SortedMap> powerData, - SortedMap energyData, Language language) - throws IOException, OpenemsNamedException { + SortedMap energyData, Language language, + XlsxExportDetailData detailComponents) throws IOException, OpenemsNamedException { byte[] payload = {}; try (// var os = new ByteArrayOutputStream(); @@ -146,8 +163,54 @@ private static byte[] generatePayload(String edgeId, ZonedDateTime fromDate, Zon XlsxUtils.addBasicInfo(ws, edgeId, fromDate, toDate, translationBundle); XlsxUtils.addEnergyData(ws, energyData, translationBundle); - XlsxUtils.addPowerData(ws, powerData, translationBundle); + var rowCount = XlsxUtils.addPowerData(ws, powerData, translationBundle); + + final var worksheetWrapper = new XlsxWorksheetWrapper(ws); + + // Box for Total Overview + worksheetWrapper.setForRange(9, 0, 9, 7, t -> t.style().borderStyle(BorderSide.TOP, BorderStyle.THIN)); + worksheetWrapper.setForRange(9, 0, rowCount, 0, + t -> t.style().borderStyle(BorderSide.LEFT, BorderStyle.THIN)); + worksheetWrapper.setForRange(rowCount, 0, rowCount, 7, + t -> t.style().borderStyle(BorderSide.BOTTOM, BorderStyle.THIN)); + worksheetWrapper.setForRange(9, 7, rowCount, 7, + t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN)); + + if (detailComponents.data().values().stream().anyMatch(de -> !de.isEmpty())) { // + var rightestColumns = XlsxUtils.addDetailData(ws, powerData, detailComponents, translationBundle, + energyData); + + final var colProd = rightestColumns.get(0) - 1; + final var colCons = rightestColumns.get(1) - 1; + final var colTou = rightestColumns.get(2) - 1; + + // Set Box for detail Timerange data + worksheetWrapper.setForRange(9, 10, 9, colTou, + t -> t.style().borderStyle(BorderSide.TOP, BorderStyle.THIN)); + worksheetWrapper.setForRange(9, 10, rowCount, 10, + t -> t.style().borderStyle(BorderSide.LEFT, BorderStyle.THIN)); + worksheetWrapper.setForRange(rowCount, 10, rowCount, colTou, + t -> t.style().borderStyle(BorderSide.BOTTOM, BorderStyle.THIN)); + worksheetWrapper.setForRange(9, colTou, rowCount, colTou, + t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN)); + + // Set Separators between prod, cons and tou + worksheetWrapper.setForRange(9, colProd, rowCount, colProd, + t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN)); + worksheetWrapper.setForRange(9, colCons, rowCount, colCons, + t -> t.style().borderStyle(BorderSide.RIGHT, BorderStyle.THIN)); + + // Set "blue" Separator between detailed Overview and total Overview + worksheetWrapper.setForRange(0, 8, rowCount, 8, t -> t.style()// + .borderStyle(BorderSide.LEFT, BorderStyle.MEDIUM)// + .borderStyle(BorderSide.RIGHT, BorderStyle.MEDIUM)// + .fillColor(BLUE)); + worksheetWrapper.getCellWrapper(0, 8).style().borderStyle(BorderSide.TOP, BorderStyle.MEDIUM); + worksheetWrapper.getCellWrapper(rowCount, 8).style().borderStyle(BorderSide.BOTTOM, + BorderStyle.MEDIUM); + } + worksheetWrapper.setAll(); wb.finish(); os.flush(); payload = os.toByteArray(); @@ -199,29 +262,35 @@ protected static void addBasicInfo(Worksheet ws, String edgeId, ZonedDateTime fr */ protected static void addEnergyData(Worksheet ws, SortedMap data, ResourceBundle translationBundle) throws OpenemsNamedException { + ws.range(4, 1, 4, 6).merge(); + ws.value(4, 1, translationBundle.getString("totalOverview")); + ws.range(4, 1, 4, 6).style().fontName(FONT_NAME).fontSize(12).horizontalAlignment("center") + .verticalAlignment("center").bold().fillColor(BLUE).set(); + ws.range(5, 1, 5, 6).style().bold().fillColor(LIGHT_GREY).set(); + ws.range(6, 1, 6, 6).style().fillColor(DARK_GREY).set(); // Grid buy energy - XlsxUtils.addStringValueBold(ws, 4, 1, translationBundle.getString("gridBuy") + " [kWh]"); - XlsxUtils.addKwhValueIfnotNull(ws, 5, 1, data.get(Channel.GRID_BUY_ACTIVE_ENERGY), translationBundle); + XlsxUtils.addStringValueBold(ws, 5, 1, translationBundle.getString("gridBuy") + " [kWh]"); + XlsxUtils.addKwhValueIfnotNull(ws, 6, 1, data.get(Channel.GRID_BUY_ACTIVE_ENERGY), translationBundle); // Grid sell energy - XlsxUtils.addStringValueBold(ws, 4, 2, translationBundle.getString("gridFeedIn") + " [kWh]"); - XlsxUtils.addKwhValueIfnotNull(ws, 5, 2, data.get(Channel.GRID_SELL_ACTIVE_ENERGY), translationBundle); + XlsxUtils.addStringValueBold(ws, 5, 2, translationBundle.getString("gridFeedIn") + " [kWh]"); + XlsxUtils.addKwhValueIfnotNull(ws, 6, 2, data.get(Channel.GRID_SELL_ACTIVE_ENERGY), translationBundle); // Production energy - XlsxUtils.addStringValueBold(ws, 4, 3, translationBundle.getString("production") + " [kWh]"); - XlsxUtils.addKwhValueIfnotNull(ws, 5, 3, data.get(Channel.PRODUCTION_ACTIVE_ENERGY), translationBundle); + XlsxUtils.addStringValueBold(ws, 5, 3, translationBundle.getString("production") + " [kWh]"); + XlsxUtils.addKwhValueIfnotNull(ws, 6, 3, data.get(Channel.PRODUCTION_ACTIVE_ENERGY), translationBundle); // Charge energy - XlsxUtils.addStringValueBold(ws, 4, 4, translationBundle.getString("storageCharging") + " [kWh]"); - XlsxUtils.addKwhValueIfnotNull(ws, 5, 4, data.get(Channel.ESS_DC_CHARGE_ENERGY), translationBundle); + XlsxUtils.addStringValueBold(ws, 5, 4, translationBundle.getString("storageCharging") + " [kWh]"); + XlsxUtils.addKwhValueIfnotNull(ws, 6, 4, data.get(Channel.ESS_DC_CHARGE_ENERGY), translationBundle); // Charge energy - XlsxUtils.addStringValueBold(ws, 4, 5, translationBundle.getString("storageDischarging") + " [kWh]"); - XlsxUtils.addKwhValueIfnotNull(ws, 5, 5, data.get(Channel.ESS_DC_DISCHARGE_ENERGY), translationBundle); + XlsxUtils.addStringValueBold(ws, 5, 5, translationBundle.getString("storageDischarging") + " [kWh]"); + XlsxUtils.addKwhValueIfnotNull(ws, 6, 5, data.get(Channel.ESS_DC_DISCHARGE_ENERGY), translationBundle); // Consumption energy - XlsxUtils.addStringValueBold(ws, 4, 6, translationBundle.getString("consumption") + " [kWh]"); - XlsxUtils.addKwhValueIfnotNull(ws, 5, 6, data.get(Channel.CONSUMPTION_ACTIVE_ENERGY), translationBundle); + XlsxUtils.addStringValueBold(ws, 5, 6, translationBundle.getString("consumption") + " [kWh]"); + XlsxUtils.addKwhValueIfnotNull(ws, 6, 6, data.get(Channel.CONSUMPTION_ACTIVE_ENERGY), translationBundle); } /** @@ -230,22 +299,24 @@ protected static void addEnergyData(Worksheet ws, SortedMap> data, ResourceBundle translationBundle) throws OpenemsNamedException { // Adding the headers - XlsxUtils.addStringValueBold(ws, 7, 0, translationBundle.getString("date/time")); - XlsxUtils.addStringValueBold(ws, 7, 1, translationBundle.getString("gridBuy") + " [W]"); - XlsxUtils.addStringValueBold(ws, 7, 2, translationBundle.getString("gridFeedIn") + " [W]"); - XlsxUtils.addStringValueBold(ws, 7, 3, translationBundle.getString("production") + " [W]"); - XlsxUtils.addStringValueBold(ws, 7, 4, translationBundle.getString("storageCharging") + " [W]"); - XlsxUtils.addStringValueBold(ws, 7, 5, translationBundle.getString("storageDischarging") + " [W]"); - XlsxUtils.addStringValueBold(ws, 7, 6, translationBundle.getString("consumption") + " [W]"); - XlsxUtils.addStringValueBold(ws, 7, 7, translationBundle.getString("stateOfCharge") + " [%]"); - - var rowCount = 8; + XlsxUtils.addStringValueBold(ws, 9, 0, translationBundle.getString("date/time")); + XlsxUtils.addStringValueBold(ws, 9, 1, translationBundle.getString("gridBuy") + " [W]"); + XlsxUtils.addStringValueBold(ws, 9, 2, translationBundle.getString("gridFeedIn") + " [W]"); + XlsxUtils.addStringValueBold(ws, 9, 3, translationBundle.getString("production") + " [W]"); + XlsxUtils.addStringValueBold(ws, 9, 4, translationBundle.getString("storageCharging") + " [W]"); + XlsxUtils.addStringValueBold(ws, 9, 5, translationBundle.getString("storageDischarging") + " [W]"); + XlsxUtils.addStringValueBold(ws, 9, 6, translationBundle.getString("consumption") + " [W]"); + XlsxUtils.addStringValueBold(ws, 9, 7, translationBundle.getString("stateOfCharge") + " [%]"); + XlsxUtils.addStringValueBold(ws, 8, 1, translationBundle.getString("generalData")); + + var rowCount = 10; for (Entry> row : data.entrySet()) { var values = row.getValue(); @@ -267,12 +338,17 @@ protected static void addPowerData(Worksheet ws, // Grid sell power XlsxUtils.addFloatValue(ws, rowCount, 2, gridActivePower / -1); } + } else { + XlsxUtils.addStringValue(ws, rowCount, 1, "-"); + XlsxUtils.addStringValue(ws, rowCount, 2, "-"); } // Production power if (XlsxUtils.isNotNull(values.get(Channel.PRODUCTION_ACTIVE_POWER))) { XlsxUtils.addFloatValue(ws, rowCount, 3, JsonUtils.getAsFloat(values.get(Channel.PRODUCTION_ACTIVE_POWER))); + } else { + XlsxUtils.addStringValue(ws, rowCount, 3, "-"); } if (XlsxUtils.isNotNull(values.get(Channel.ESS_DISCHARGE_POWER))) { @@ -284,19 +360,120 @@ protected static void addPowerData(Worksheet ws, XlsxUtils.addFloatValue(ws, rowCount, 4, essDischargePower / -1); XlsxUtils.addFloatValue(ws, rowCount, 5, 0); } + } else { + XlsxUtils.addStringValue(ws, rowCount, 4, "-"); + XlsxUtils.addStringValue(ws, rowCount, 5, "-"); } // Consumption power if (XlsxUtils.isNotNull(values.get(Channel.CONSUMPTION_ACTIVE_POWER))) { XlsxUtils.addFloatValue(ws, rowCount, 6, JsonUtils.getAsFloat(values.get(Channel.CONSUMPTION_ACTIVE_POWER))); + } else { + XlsxUtils.addStringValue(ws, rowCount, 6, "-"); } // State of charge if (XlsxUtils.isNotNull(values.get(Channel.ESS_SOC))) { XlsxUtils.addFloatValue(ws, rowCount, 7, JsonUtils.getAsFloat(values.get(Channel.ESS_SOC))); + } else { + XlsxUtils.addStringValue(ws, rowCount, 7, "-"); } rowCount++; } + rowCount--; + return rowCount; + } + + protected static List addDetailData(Worksheet ws, + SortedMap> data, + XlsxExportDetailData detailComponents, ResourceBundle translationBundle, + SortedMap energyData) throws OpenemsNamedException { + ws.width(8, 4); + ws.width(9, 4); + ws.width(10, 25); + ws.width(11, 25); + ws.width(12, 25); + ws.width(13, 25); + ws.width(14, 25); + ws.width(15, 25); + + ws.range(4, 10, 4, 15).merge(); + ws.range(4, 10, 4, 15).style().fontName(FONT_NAME).fontSize(12).bold().fillColor(BLUE).set(); + ws.value(4, 10, translationBundle.getString("detailData")); + ws.range(5, 10, 5, 15).merge(); + ws.range(5, 10, 5, 15).style().fillColor(LIGHT_GREY).set(); + ws.value(5, 10, translationBundle.getString("detailHint")); + var rightestColumn1 = addProductionData(ws, data, detailComponents, translationBundle); + var rightestColumn2 = addConsumptionData(ws, data, detailComponents, rightestColumn1, translationBundle); + var rightestColumn3 = addTimeOfUseTariffData(ws, data, detailComponents, rightestColumn2, + translationBundle); + ws.width(rightestColumn3, 35); + return List.of(rightestColumn1, rightestColumn2, rightestColumn3); + } + + protected static int addProductionData(Worksheet ws, + SortedMap> data, + XlsxExportDetailData detailComponents, ResourceBundle translationBundle) throws OpenemsNamedException { + return XlsxUtils.addGenericData(ws, data, detailComponents, 10, translationBundle, + XlsxExportCategory.PRODUCTION, "production", (t, d) -> t.alias() + " [W]", 1); + } + + protected static int addConsumptionData(Worksheet ws, + SortedMap> data, + XlsxExportDetailData detailComponents, int righestColumn, ResourceBundle translationBundle) + throws OpenemsNamedException { + return XlsxUtils.addGenericData(ws, data, detailComponents, righestColumn, translationBundle, + XlsxExportCategory.CONSUMPTION, "consumption", (t, d) -> t.alias() + " [W]", 1); + } + + protected static int addTimeOfUseTariffData(Worksheet ws, + SortedMap> data, + XlsxExportDetailData detailComponents, int righestColumn, ResourceBundle translationBundle) + throws OpenemsNamedException { + final var unit = "[" + detailComponents.currency().getUnderPart() + "/kWh]"; + return XlsxUtils.addGenericData(ws, data, detailComponents, righestColumn, translationBundle, + XlsxExportCategory.TIME_OF_USE_TARIFF, "timeOfUse", (t, d) -> t.alias() + " " + unit, + 1000f / detailComponents.currency().getRatio()); + } + + protected static int addGenericData(// + Worksheet ws, // + SortedMap> data, // + XlsxExportDetailData detailComponents, // + int righestColumn, // + ResourceBundle translationBundle, // + XlsxExportDetailData.XlsxExportCategory category, // + String translationKey, // + BiFunction aliasBuilder, // + float ratio) // + throws OpenemsNamedException { + + final var righestColumnOld = righestColumn; + + for (var item : detailComponents.data().get(category)) { + ws.value(8, righestColumnOld, translationBundle.getString(translationKey)); + ws.style(8, righestColumnOld).bold().set(); + + ws.value(9, righestColumn, aliasBuilder.apply(item, detailComponents)); + ws.style(9, righestColumn).bold().set(); + + var rowCount = 10; + for (final var row : data.entrySet()) { + final var values = row.getValue(); + final var channelAddress = item.channel(); + + if (XlsxUtils.isNotNull(values.get(channelAddress))) { + XlsxUtils.addFloatValueNotRounded(ws, rowCount, righestColumn, + JsonUtils.getAsFloat(values.get(channelAddress)) / ratio); + } else { + XlsxUtils.addStringValue(ws, rowCount, righestColumn, "-"); + } + rowCount++; + } + righestColumn++; + } + + return righestColumn; } /** @@ -385,6 +562,19 @@ protected static void addFloatValue(Worksheet ws, int row, int col, float value) ws.value(row, col, Math.round(value)); } + /** + * Helper method to add the value to the excel sheet. The float value is + * mathematically rounded. + * + * @param ws the {@link Worksheet} + * @param row row number + * @param col column number + * @param value actual value in the sheet + */ + protected static void addFloatValueNotRounded(Worksheet ws, int row, int col, float value) { + ws.value(row, col, value); + } + /** * Simple helper method to check for null values. * diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties index 1ac1c7f1860..4fdcc640291 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties +++ b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_de.properties @@ -8,4 +8,9 @@ storageDischarging = Speicher Entladung consumption = Verbrauch stateOfCharge = Ladezustand date/time = Datum / Uhrzeit -notAvailable = nicht vorhanden \ No newline at end of file +notAvailable = nicht vorhanden +detailData = Detaillierte Auswertung der Erzeuger, Verbraucher und Apps +detailHint = * Bitte beachten Sie, dass diese Werte bereits in den Leistungsdaten in Ihrer Gesamtübersicht enthalten sind. +timeOfUse = Dynamischer Stromtarif +totalOverview = Gesamtübersicht +generalData = Allgemeine Daten \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties index 4da846d76c2..40f8fc4d09a 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties +++ b/io.openems.common/src/io/openems/common/jsonrpc/response/translation_en.properties @@ -8,4 +8,9 @@ storageDischarging = Storage Discharging consumption = Consumption stateOfCharge = State of Charge date/time = Date / Time -notAvailable = not available \ No newline at end of file +notAvailable = not available +detailData = Detailed evaluation of the producer, consumer and apps +detailHint = * Please note that these values are already included in the performance data in your Total Overview. +timeOfUse = Time of Use Tariff +totalOverview = Total Overview +generalData = General Data \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java b/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java index fdebd1f183c..097f7c5aad9 100644 --- a/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java +++ b/io.openems.common/src/io/openems/common/timedata/CommonTimedataService.java @@ -1,6 +1,5 @@ package io.openems.common.timedata; -import java.io.IOException; import java.time.Period; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -10,46 +9,11 @@ import com.google.gson.JsonElement; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesDataRequest; -import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesExportXlxsRequest; -import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse; -import io.openems.common.session.Language; import io.openems.common.types.ChannelAddress; public interface CommonTimedataService { - /** - * Handles a {@link QueryHistoricTimeseriesExportXlxsRequest}. Exports historic - * data to an Excel file. - * - * @param edgeId the Edge-ID - * @param request the {@link QueryHistoricTimeseriesExportXlxsRequest} request - * @param language the {@link Language} - * @return the {@link QueryHistoricTimeseriesExportXlsxResponse} - * @throws OpenemsNamedException on error - */ - public default QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTimeseriesExportXlxsRequest( - String edgeId, QueryHistoricTimeseriesExportXlxsRequest request, Language language) - throws OpenemsNamedException { - var powerData = this.queryHistoricData(edgeId, request.getFromDate(), request.getToDate(), - QueryHistoricTimeseriesExportXlsxResponse.POWER_CHANNELS, new Resolution(15, ChronoUnit.MINUTES)); - - var energyData = this.queryHistoricEnergy(edgeId, request.getFromDate(), request.getToDate(), - QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS); - - if (powerData == null || energyData == null) { - return null; - } - - try { - return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), edgeId, request.getFromDate(), - request.getToDate(), powerData, energyData, language); - } catch (IOException e) { - throw new OpenemsException("QueryHistoricTimeseriesExportXlxsRequest failed: " + e.getMessage()); - } - } - /** * Calculates the time {@link Resolution} for the period. * diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java b/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java new file mode 100644 index 00000000000..199bc345846 --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/XlsxExportDetailData.java @@ -0,0 +1,37 @@ +package io.openems.common.timedata; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.CurrencyConfig; + +public record XlsxExportDetailData(// + EnumMap> data, // + CurrencyConfig currency +) { + + public Map> getChannelsBySaveType() { + return this.data().values().stream().flatMap(List::stream).collect(Collectors.groupingBy( + XlsxExportDataEntry::type, Collectors.mapping(XlsxExportDataEntry::channel, Collectors.toList()))); + } + + public enum XlsxExportCategory { + CONSUMPTION, PRODUCTION, TIME_OF_USE_TARIFF + } + + public record XlsxExportDataEntry(// + String alias, ChannelAddress channel, // + HistoricTimedataSaveType type // + ) { + + public enum HistoricTimedataSaveType { + POWER, ENERGY + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java b/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java new file mode 100644 index 00000000000..a1f290bf48b --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/XlsxExportUtil.java @@ -0,0 +1,112 @@ +package io.openems.common.timedata; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Set; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.CurrencyConfig; +import io.openems.common.types.EdgeConfig; +import io.openems.common.utils.JsonUtils; + +public class XlsxExportUtil { + + /** + * Gathers the detail data for excel export. + * + * @param edge the edge + * @return the currency represented as a CurrencyConfig + * @throws OpenemsNamedException if component isnt found + */ + private static CurrencyConfig getCurrency(EdgeConfig edge) throws OpenemsNamedException { + return edge.getComponent("_meta") // + .flatMap(t -> t.getProperty("currency")) // + .flatMap(t -> JsonUtils.getAsOptionalEnum(CurrencyConfig.class, t)) // + .orElse(CurrencyConfig.EUR); + } + + /** + * Gathers the detail data for excel export. + * + * @param edge the edge + * @return the detailData + * @throws OpenemsNamedException if component isnt found + */ + public static XlsxExportDetailData getDetailData(EdgeConfig edge) throws OpenemsNamedException { + final var enumMap = new EnumMap>(XlsxExportCategory.class); + final var consumption = new ArrayList(); + final var production = new ArrayList(); + final var tou = new ArrayList(); + + enumMap.put(XlsxExportCategory.PRODUCTION, production); + enumMap.put(XlsxExportCategory.CONSUMPTION, consumption); + enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou); + + for (var component : edge.getComponents().values()) { + final var natures = edge.getFactories().get(component.getFactoryId()).getNatureIds(); + for (var nature : natures) { + // Electricity meter + switch (nature) { + case Natures.METER -> { + final var props = component.getProperties(); + if (props.keySet().contains("type")) { + if (props.get("type").getAsString().equals("PRODUCTION")) { + production.add(new XlsxExportDataEntry(component.getAlias(), + new ChannelAddress(component.getId(), "ActivePower"), + XlsxExportDataEntry.HistoricTimedataSaveType.POWER)); + } else if (props.get("type").getAsString().equals("CONSUMPTION_NOT_METERED") + || props.get("type").getAsString().equals("CONSUMPTION_METERED")) { + consumption.add(new XlsxExportDataEntry(component.getAlias(), + new ChannelAddress(component.getId(), "ActivePower"), + XlsxExportDataEntry.HistoricTimedataSaveType.POWER)); + } + continue; + } + final var type = getActivePowerType(component.getFactoryId()); + if (type == null) { + continue; + } + enumMap.get(type) + .add(new XlsxExportDataEntry(component.getAlias(), + new ChannelAddress(component.getId(), "ActivePower"), + XlsxExportDataEntry.HistoricTimedataSaveType.POWER)); + } + case Natures.TIME_OF_USE_TARIFF -> { + tou.add(new XlsxExportDataEntry(component.getAlias(), new ChannelAddress("_sum", "GridBuyPrice"), + XlsxExportDataEntry.HistoricTimedataSaveType.POWER)); + } + } + } + } + return new XlsxExportDetailData(enumMap, XlsxExportUtil.getCurrency(edge)); + } + + private static XlsxExportCategory getActivePowerType(String factoryId) { + if (Natures.PRODUCTION_NATURES.contains(factoryId)) { + return XlsxExportCategory.PRODUCTION; + } else if (Natures.CONSUMPTION_NATURES.contains(factoryId)) { + return XlsxExportCategory.CONSUMPTION; + } + return null; + } + + private static final class Natures { + public static final String METER = "io.openems.edge.meter.api.ElectricityMeter"; + public static final String TIME_OF_USE_TARIFF = "io.openems.edge.timeofusetariff.api.TimeOfUseTariff"; + public static final Set PRODUCTION_NATURES = Set.of("Simulator.PvInverter", "Fenecon.Dess.PvMeter", + "Fenecon.Mini.PvMeter", "Kaco.BlueplanetHybrid10.PvInverter", "PvInverter.Cluster", + "PV-Inverter.Fronius", "PV-Inverter.KACO.blueplanet", "PV-Inverter.SMA.SunnyTripower", + "PV-Inverter.Kostal", "PV-Inverter.Solarlog", "Simulator.ProductionMeter.Acting", + "SolarEdge.PV-Inverter"); + + public static final Set CONSUMPTION_NATURES = Set.of("GoodWe.EmergencyPowerMeter", + "Simulator.NRCMeter.Acting", "Evcs.AlpitronicHypercharger", "Evcs.Dezony", "Evcs.Goe.ChargerHome", + "Evcs.HardyBarth", "Evcs.Keba.KeContact", "Evcs.Ocpp.Abl", "Evcs.Ocpp.IesKeywattSingle", + "Evcs.Spelsberg.SMART", "Evcs.Webasto.Next","Evcs.Webasto.Unite"); + } + +} diff --git a/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java b/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java new file mode 100644 index 00000000000..faec99a684a --- /dev/null +++ b/io.openems.common/src/io/openems/common/timedata/XlsxWorksheetWrapper.java @@ -0,0 +1,46 @@ +package io.openems.common.timedata; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.dhatim.fastexcel.StyleSetter; +import org.dhatim.fastexcel.Worksheet; + +public class XlsxWorksheetWrapper { + public record XlsxCellWrapper(int c, int r, StyleSetter style) { + } + + private final Map> cellMap = new HashMap<>(); + private final Worksheet ws; + + public XlsxWorksheetWrapper(Worksheet ws) { + this.ws = ws; + } + + /** + * Gets a CellWrapper if exists; otherwise creates a new one and returns that. + * + * @param r row of the cell + * @param c column of the cell + * @return the XlsxCellWrapper + */ + public XlsxCellWrapper getCellWrapper(int r, int c) { + final var columns = this.cellMap.computeIfAbsent(r, row -> new HashMap<>()); + return columns.computeIfAbsent(c, col -> new XlsxCellWrapper(r, c, this.ws.style(r, c))); + } + + public void setAll() { + this.cellMap.values().stream().flatMap(map -> map.values().stream()).forEach(val -> val.style().set()); + } + + public void setForRange(int r1, int c1, int r2, int c2, Consumer styleSetterFunc) { + for (int row = r1; row <= r2; row++) { + for (int col = c1; col <= c2; col++) { + var cell = this.getCellWrapper(row, col); + styleSetterFunc.accept(cell);; + } + } + } + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/types/CurrencyConfig.java b/io.openems.common/src/io/openems/common/types/CurrencyConfig.java new file mode 100644 index 00000000000..8c401aefda3 --- /dev/null +++ b/io.openems.common/src/io/openems/common/types/CurrencyConfig.java @@ -0,0 +1,49 @@ +package io.openems.common.types; + +import java.util.Currency; + +/** + * The {@link ChannelId#CURRENCY} mandates the selection of the 'currency' + * configuration property of this specific type. Subsequently, this selected + * property is transformed into the corresponding {@link Currency} type before + * being written through {@link Meta#_setCurrency(Currency)}. + */ +public enum CurrencyConfig { + /** + * Euro. + */ + EUR("€", "Cent", 100f), + /** + * Swedish Krona. + */ + SEK("kr", "Öre", 100f), + /** + * Swiss Francs. + */ + CHF("Fr", "Rappen", 100f); + + private final String symbol; + + private final String underPart; + + private final float ratio; + + private CurrencyConfig(String symbol, String underPart, float ratio) { + this.symbol = symbol; + this.underPart = underPart; + this.ratio = ratio; + } + + public String getSymbol() { + return this.symbol; + } + + public String getUnderPart() { + return this.underPart; + } + + public float getRatio() { + return this.ratio; + } + +} diff --git a/io.openems.common/src/io/openems/common/types/EdgeConfig.java b/io.openems.common/src/io/openems/common/types/EdgeConfig.java index 1a7ad131b23..ce24afa90ed 100644 --- a/io.openems.common/src/io/openems/common/types/EdgeConfig.java +++ b/io.openems.common/src/io/openems/common/types/EdgeConfig.java @@ -588,8 +588,7 @@ public static Component fromJson(String componentId, JsonElement json) throws Op var jPropertiesOpt = JsonUtils.getAsOptionalJsonObject(json, "properties"); if (jPropertiesOpt.isPresent()) { for (Entry entry : jPropertiesOpt.get().entrySet()) { - if (!ignorePropertyKey(entry.getKey()) - && !ignoreComponentPropertyKey(componentId, entry.getKey())) { + if (!ignorePropertyKey(entry.getKey())) { properties.put(entry.getKey(), entry.getValue()); } } @@ -1171,9 +1170,9 @@ public TreeMap getFactories() { } /** - * Builds the {@link ActualEdgeConfig}. + * Builds the ActualEdgeConfig. * - * @return {@link ActualEdgeConfig} + * @return ActualEdgeConfig */ public ActualEdgeConfig build() { return new ActualEdgeConfig(ImmutableSortedMap.copyOf(this.getComponents()), @@ -1191,9 +1190,9 @@ public EdgeConfig buildEdgeConfig() { } /** - * Creates an empty {@link ActualEdgeConfig}. + * Creates an empty ActualEdgeConfig. * - * @return {@link ActualEdgeConfig} + * @return ActualEdgeConfig */ public static ActualEdgeConfig empty() { return ActualEdgeConfig.create().build(); @@ -1248,9 +1247,9 @@ public static EdgeConfig fromJson(JsonObject json) { private volatile JsonObject _json = null; /** - * Build from {@link ActualEdgeConfig}. + * Build from ActualEdgeConfig. * - * @param actual the {@link ActualEdgeConfig} + * @param actual the ActualEdgeConfig */ private EdgeConfig(ActualEdgeConfig actual) { this._actual = actual; @@ -1261,10 +1260,10 @@ private EdgeConfig(JsonObject json) { } /** - * Gets the {@link ActualEdgeConfig}. Either by parsing it from {@link #json} or - * by returning from cache. + * Gets the ActualEdgeConfig. Either by parsing it from {@link #json} or by + * returning from cache. * - * @return {@link ActualEdgeConfig}; empty on JSON parse error + * @return ActualEdgeConfig; empty on JSON parse error */ private synchronized ActualEdgeConfig getActual() { if (this._actual != null) { @@ -1465,25 +1464,4 @@ public static boolean ignorePropertyKey(String key) { default -> false; }; } - - /** - * Internal Method to decide whether a configuration property should be ignored. - * - * @param componentId the Component-ID - * @param key the property key - * @return true if it should get ignored - */ - public static boolean ignoreComponentPropertyKey(String componentId, String key) { - return switch (componentId) { - // Filter for _sum component - case "_sum" -> switch (key) { - case "productionMaxActivePower", "consumptionMaxActivePower", "gridMinActivePower", "gridMaxActivePower" -> - true; - - default -> false; - }; - - default -> false; - }; - } } diff --git a/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java b/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java index 3f549c9cb49..4c4968114c6 100644 --- a/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java +++ b/io.openems.common/src/io/openems/common/websocket/MyDraft6455.java @@ -23,6 +23,7 @@ import org.java_websocket.enums.Opcode; import org.java_websocket.enums.ReadyState; import org.java_websocket.enums.Role; +import org.java_websocket.exceptions.IncompleteException; import org.java_websocket.exceptions.InvalidDataException; import org.java_websocket.exceptions.InvalidFrameException; import org.java_websocket.exceptions.InvalidHandshakeException; diff --git a/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java b/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java new file mode 100644 index 00000000000..f855b102bee --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/response/CreateXlxsTest.java @@ -0,0 +1,248 @@ +package io.openems.common.jsonrpc.response; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Base64; +import java.util.EnumMap; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.UUID; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse.Channel; +import io.openems.common.session.Language; +import io.openems.common.timedata.XlsxExportDetailData; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportCategory; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.CurrencyConfig; + +public class CreateXlxsTest { + + private static SortedMap getMockedEnergyData() { + return ImmutableSortedMap.naturalOrder() // + .put(Channel.GRID_BUY_ACTIVE_ENERGY, new JsonPrimitive(500)) // + .put(Channel.GRID_SELL_ACTIVE_ENERGY, new JsonPrimitive(0)) // + .put(Channel.PRODUCTION_ACTIVE_ENERGY, new JsonPrimitive(300)) // + .put(Channel.CONSUMPTION_ACTIVE_ENERGY, new JsonPrimitive(700)) // + .put(Channel.ESS_DC_CHARGE_ENERGY, new JsonPrimitive(100)) // + .put(Channel.ESS_DC_DISCHARGE_ENERGY, new JsonPrimitive(80)) // + .build(); + } + + private static SortedMap> getMockedPowerData() { + + SortedMap values = new TreeMap<>(); + values.put(Channel.GRID_ACTIVE_POWER, new JsonPrimitive(50)); + values.put(Channel.PRODUCTION_ACTIVE_POWER, new JsonPrimitive(50)); + values.put(Channel.CONSUMPTION_ACTIVE_POWER, new JsonPrimitive(50)); + values.put(Channel.ESS_DISCHARGE_POWER, new JsonPrimitive(50)); + values.put(Channel.ESS_SOC, new JsonPrimitive(50)); + values.put(new ChannelAddress("meter0", "ActivePower"), new JsonPrimitive(100)); + values.put(new ChannelAddress("meter1", "ActivePower"), new JsonPrimitive(412)); + values.put(new ChannelAddress("evcs0", "ChargePower"), new JsonPrimitive(75)); + values.put(new ChannelAddress("meter2", "ActivePower"), new JsonPrimitive(10)); + values.put(new ChannelAddress("_sum", "GridBuyPower"), new JsonPrimitive(292.5)); + + return ImmutableSortedMap.>naturalOrder() + .put(ZonedDateTime.of(2020, 07, 01, 0, 15, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 0, 30, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 0, 45, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 1, 0, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 1, 15, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .put(ZonedDateTime.of(2020, 07, 01, 1, 30, 0, 0, ZoneId.systemDefault()).plusMinutes(15), values) // + .build(); + } + + private static XlsxExportDetailData getMockedDetailData() { + final var enumMap = new EnumMap>(XlsxExportCategory.class); + final var consumption = new ArrayList(); + final var production = new ArrayList(); + final var tou = new ArrayList(); + + enumMap.put(XlsxExportCategory.PRODUCTION, production); + enumMap.put(XlsxExportCategory.CONSUMPTION, consumption); + enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou); + + production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"), + HistoricTimedataSaveType.POWER)); + production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"), + HistoricTimedataSaveType.POWER)); + consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"), + HistoricTimedataSaveType.POWER)); + consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"), + HistoricTimedataSaveType.POWER)); + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + + return new XlsxExportDetailData(enumMap, CurrencyConfig.EUR); + } + + /** + * Main Method for creating a excel export with mocked data. + * + * @param args not used + * @throws IOException if file cant be written + * @throws OpenemsNamedException requests fails + */ + public static void main(String[] args) throws IOException, OpenemsNamedException { + createFullXlsx(); + createHalfXlsx(); + createConsumptionOnlyXlsx(); + createProductionOnlyXlsx(); + createTouOnlyXlsx(); + createProductionAndTouXlsx(); + createConsumptionAndTouXlsx(); + createnSingleOfAllXlsx(); + } + + private static void createFullXlsx() throws IOException, OpenemsNamedException { + var fromDate = ZonedDateTime.of(2020, 07, 01, 0, 0, 0, 0, ZoneId.systemDefault()); + var toDate = ZonedDateTime.of(2020, 07, 02, 0, 0, 0, 0, ZoneId.systemDefault()); + + var powerData = CreateXlxsTest.getMockedPowerData(); + var energyData = CreateXlxsTest.getMockedEnergyData(); + var detailData = CreateXlxsTest.getMockedDetailData(); + + final var request = new QueryHistoricTimeseriesExportXlsxResponse(UUID.randomUUID(), "edge0", fromDate, toDate, + powerData, energyData, Language.EN, detailData); + + var payload = request.getPayload(); + + byte[] excelData = Base64.getDecoder().decode(payload); + + String filePath = ".\\..\\build\\fullTestPrint.xlsx"; + + try (FileOutputStream fos = new FileOutputStream(filePath)) { + fos.write(excelData); + System.out.println("Testfile created under: " + filePath); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void createHalfXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\emptyTestPrint.xlsx", null, null, null); + } + + private static void createTestPrint(String filePath, Consumer> consProd, + Consumer> consCons, Consumer> consTou) + throws IOException, OpenemsNamedException { + final var enumMap = new EnumMap>(XlsxExportCategory.class); + final var consumption = new ArrayList(); + final var production = new ArrayList(); + final var tou = new ArrayList(); + + if (consProd != null) { + consProd.accept(production); + } + + if (consCons != null) { + consCons.accept(consumption); + } + + if (consTou != null) { + consTou.accept(tou); + } + + enumMap.put(XlsxExportCategory.PRODUCTION, production); + enumMap.put(XlsxExportCategory.CONSUMPTION, consumption); + enumMap.put(XlsxExportCategory.TIME_OF_USE_TARIFF, tou); + + var detailData = new XlsxExportDetailData(enumMap, CurrencyConfig.EUR); + + var fromDate = ZonedDateTime.of(2020, 07, 01, 0, 0, 0, 0, ZoneId.systemDefault()); + var toDate = ZonedDateTime.of(2020, 07, 02, 0, 0, 0, 0, ZoneId.systemDefault()); + + var powerData = CreateXlxsTest.getMockedPowerData(); + var energyData = CreateXlxsTest.getMockedEnergyData(); + + final var request = new QueryHistoricTimeseriesExportXlsxResponse(UUID.randomUUID(), "edge0", fromDate, toDate, + powerData, energyData, Language.EN, detailData); + + var payload = request.getPayload(); + + byte[] excelData = Base64.getDecoder().decode(payload); + + try (FileOutputStream fos = new FileOutputStream(filePath)) { + fos.write(excelData); + System.out.println("Testfile created under: " + filePath); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void createProductionOnlyXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\prodTestPrint.xlsx", production -> { + production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"), + HistoricTimedataSaveType.POWER)); + production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"), + HistoricTimedataSaveType.POWER)); + }, null, null); + } + + private static void createConsumptionOnlyXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\consTestPrint.xlsx", null, consumption -> { + consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"), + HistoricTimedataSaveType.POWER)); + consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"), + HistoricTimedataSaveType.POWER)); + }, null); + } + + private static void createTouOnlyXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\touPrint.xlsx", null, null, tou -> { + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + }); + } + + private static void createProductionAndTouXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\prodAndTouPrint.xlsx", production -> { + production.add(new XlsxExportDataEntry("PV-Dach", new ChannelAddress("meter0", "ActivePower"), + HistoricTimedataSaveType.POWER)); + production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"), + HistoricTimedataSaveType.POWER)); + }, null, tou -> { + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + }); + + } + + private static void createConsumptionAndTouXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\consAndTouPrint.xlsx", null, consumption -> { + consumption.add(new XlsxExportDataEntry("Consumption Meter", new ChannelAddress("meter2", "ActivePower"), + HistoricTimedataSaveType.POWER)); + consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"), + HistoricTimedataSaveType.POWER)); + }, tou -> { + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + }); + } + + private static void createnSingleOfAllXlsx() throws IOException, OpenemsNamedException { + createTestPrint(".\\..\\build\\singleOfAllPrint.xlsx", production -> { + production.add(new XlsxExportDataEntry("PV-Alm", new ChannelAddress("meter1", "ActivePower"), + HistoricTimedataSaveType.POWER)); + }, consumption -> { + consumption.add(new XlsxExportDataEntry("Wallbox Garage", new ChannelAddress("evcs0", "ChargePower"), + HistoricTimedataSaveType.POWER)); + }, tou -> { + tou.add(new XlsxExportDataEntry("Dynamisch Gut", new ChannelAddress("_sum", "GridBuyPower"), + HistoricTimedataSaveType.POWER)); + }); + + } +} diff --git a/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java b/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java index de029ad3ba5..74b3d0f4895 100644 --- a/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java +++ b/io.openems.edge.battery.api/test/io/openems/edge/battery/protection/BatteryProtectionTest.java @@ -1,14 +1,24 @@ package io.openems.edge.battery.protection; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_TEMPERATURE; +import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_TEMPERATURE; +import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_VOLTAGE; +import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_BMS; +import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_DISCHARGE_BMS; +import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.channel.Unit; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.edge.battery.protection.currenthandler.ChargeMaxCurrentHandler; import io.openems.edge.battery.protection.currenthandler.DischargeMaxCurrentHandler; @@ -18,7 +28,6 @@ import io.openems.edge.common.channel.Doc; import io.openems.edge.common.linecharacteristic.PolyLine; import io.openems.edge.common.startstop.StartStop; -import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -102,22 +111,6 @@ public Doc doc() { private static final String BATTERY_ID = "battery0"; - private static final ChannelAddress BATTERY_START_STOP = new ChannelAddress(BATTERY_ID, - StartStoppable.ChannelId.START_STOP.id()); - private static final ChannelAddress BATTERY_BP_CHARGE_BMS = new ChannelAddress(BATTERY_ID, - BatteryProtection.ChannelId.BP_CHARGE_BMS.id()); - private static final ChannelAddress BATTERY_BP_DISCHARGE_BMS = new ChannelAddress(BATTERY_ID, - BatteryProtection.ChannelId.BP_DISCHARGE_BMS.id()); - private static final ChannelAddress BATTERY_MIN_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, "MinCellVoltage"); - private static final ChannelAddress BATTERY_MAX_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, "MaxCellVoltage"); - private static final ChannelAddress BATTERY_MIN_CELL_TEMPERATURE = new ChannelAddress(BATTERY_ID, - "MinCellTemperature"); - private static final ChannelAddress BATTERY_MAX_CELL_TEMPERATURE = new ChannelAddress(BATTERY_ID, - "MaxCellTemperature"); - private static final ChannelAddress BATTERY_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent"); - private static final ChannelAddress BATTERY_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, - "DischargeMaxCurrent"); - @Test public void test() throws Exception { final var battery = new DummyBattery(BATTERY_ID); @@ -137,169 +130,168 @@ public void test() throws Exception { .setForceCharge(FORCE_CHARGE) // .build()) // .build(); - new ComponentTest(new DummyBattery(BATTERY_ID)) // - .addComponent(battery) // + new ComponentTest(battery) // .next(new TestCase() // - .input(BATTERY_START_STOP, StartStop.START) // - .input(BATTERY_BP_CHARGE_BMS, 80) // - .input(BATTERY_BP_DISCHARGE_BMS, 80) // - .input(BATTERY_MIN_CELL_VOLTAGE, 2950) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3300) // - .input(BATTERY_MIN_CELL_TEMPERATURE, 16) // - .input(BATTERY_MAX_CELL_TEMPERATURE, 17) // + .input(START_STOP, StartStop.START) // + .input(BP_CHARGE_BMS, 80) // + .input(BP_DISCHARGE_BMS, 80) // + .input(MIN_CELL_VOLTAGE, 2950) // + .input(MAX_CELL_VOLTAGE, 3300) // + .input(MIN_CELL_TEMPERATURE, 16) // + .input(MAX_CELL_TEMPERATURE, 17) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 0)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 0)) // .next(new TestCase("open, but maxIncreaseAmpereLimit") // - .timeleap(clock, 2, ChronoUnit.SECONDS) // - .input(BATTERY_MIN_CELL_VOLTAGE, 3000) // + .timeleap(clock, 2, SECONDS) // + .input(MIN_CELL_VOLTAGE, 3000) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 1)) // + .output(CHARGE_MAX_CURRENT, 1) // + .output(DISCHARGE_MAX_CURRENT, 1)) // .next(new TestCase() // - .timeleap(clock, 2, ChronoUnit.SECONDS) // - .input(BATTERY_MIN_CELL_VOLTAGE, 3050) // + .timeleap(clock, 2, SECONDS) // + .input(MIN_CELL_VOLTAGE, 3050) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 2)) // + .output(CHARGE_MAX_CURRENT, 2) // + .output(DISCHARGE_MAX_CURRENT, 2)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.SECONDS) // + .timeleap(clock, 10, SECONDS) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 7) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 7)) // + .output(CHARGE_MAX_CURRENT, 7) // + .output(DISCHARGE_MAX_CURRENT, 7)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3300) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3300) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 80) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 80) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3499) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3499) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 54) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 54) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3649) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3649) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3649) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3649) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 10, ChronoUnit.MINUTES) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3650) // + .timeleap(clock, 10, MINUTES) // + .input(MAX_CELL_VOLTAGE, 3650) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Start Force-Discharge: wait 60 seconds") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3660) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3660) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Start Force-Discharge") // - .timeleap(clock, 60, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3660) // + .timeleap(clock, 60, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3660) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Force-Discharge") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3640) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3640) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3639) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3639) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1 still reduce by 1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3638) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3638) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1 still reduce by 1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3610) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3610) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #2") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3600) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3600) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Start Force-Discharge again") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3660) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3660) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Force-Discharge") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3640) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3640) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -2) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -2) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3639) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3639) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1 still reduce by 1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3638) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3638) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, -1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, -1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #1 still reduce by 1") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3637) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3637) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #2") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3600) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3600) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Block Charge #3") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3450) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3450) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Finish Force-Discharge") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3449) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3449) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase() // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3400) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3400) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 0) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 0) // + .output(DISCHARGE_MAX_CURRENT, 80)) // .next(new TestCase("Allow Charge") // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(BATTERY_MAX_CELL_VOLTAGE, 3350) // + .timeleap(clock, 1, SECONDS) // + .input(MAX_CELL_VOLTAGE, 3350) // .onAfterProcessImage(() -> sut.apply()) // - .output(BATTERY_CHARGE_MAX_CURRENT, 1) // - .output(BATTERY_DISCHARGE_MAX_CURRENT, 80)) // + .output(CHARGE_MAX_CURRENT, 1) // + .output(DISCHARGE_MAX_CURRENT, 80)) // ; } diff --git a/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java b/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java index fd5aa8847db..8ded6b2514c 100644 --- a/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java +++ b/io.openems.edge.battery.bmw/test/io/openems/edge/battery/bmw/BmwBatteryImplTest.java @@ -1,33 +1,33 @@ package io.openems.edge.battery.bmw; +import static io.openems.edge.battery.bmw.enums.BatteryState.DEFAULT; + import org.junit.Test; -import io.openems.edge.battery.bmw.enums.BatteryState; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; public class BmwBatteryImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BmwBatteryImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // - .setBatteryState(BatteryState.DEFAULT) // + .setId("battery0") // + .setModbusId("modbus0") // + .setBatteryState(DEFAULT) // .setErrorDelay(0) // .setMaxStartAttempts(0) // .setMaxStartTime(0) // .setPendingTolerance(0) // .setStartUnsuccessfulDelay(0) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java b/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java index 91d0b85e1c1..1b87571c888 100644 --- a/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java +++ b/io.openems.edge.battery.bydcommercial/test/io/openems/edge/battery/bydcommercial/BydBatteryBoxCommercialC130ImplTest.java @@ -1,28 +1,30 @@ package io.openems.edge.battery.bydcommercial; +import static io.openems.edge.common.startstop.StartStopConfig.AUTO; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; -import io.openems.edge.common.startstop.StartStopConfig; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; public class BydBatteryBoxCommercialC130ImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BydBatteryBoxCommercialC130Impl()) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // - .setStartStop(StartStopConfig.AUTO) // + .setId("battery0") // + .setModbusId("modbus0") // + .setStartStop(AUTO) // .build()) // - ; + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java index 1226ccc9ffc..8fec9839f16 100644 --- a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java +++ b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/BatteryFeneconCommercialImplTest.java @@ -1,17 +1,20 @@ package io.openems.edge.battery.fenecon.commercial; -import java.time.Instant; -import java.time.ZoneOffset; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.SOC; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.BATTERY_SOC; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.RUNNING; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.STATE_MACHINE; +import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT7; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.fenecon.commercial.statemachine.StateMachine; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; -import io.openems.edge.common.startstop.StartStoppable; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -20,42 +23,23 @@ public class BatteryFeneconCommercialImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_ID, - BatteryFeneconCommercial.ChannelId.STATE_MACHINE.id()); - private static final ChannelAddress RUNNING = new ChannelAddress(BATTERY_ID, - BatteryFeneconCommercial.ChannelId.RUNNING.id()); - private static final ChannelAddress BATTERY_RELAY = new ChannelAddress(IO_ID, "InputOutput7"); - private static final ChannelAddress START_STOP = new ChannelAddress(BATTERY_ID, - StartStoppable.ChannelId.START_STOP.id()); - private static final ChannelAddress BATTERY_SOC = new ChannelAddress(BATTERY_ID, - BatteryFeneconCommercial.ChannelId.BATTERY_SOC.id()); - private static final ChannelAddress BATTERY_MAX_DISCHARGE_CURRENT = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.DISCHARGE_MAX_CURRENT.id()); - private static final ChannelAddress BATTERY_MAX_CHARGE_CURRENT = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.CHARGE_MAX_CURRENT.id()); - private static final ChannelAddress SOC = new ChannelAddress(BATTERY_ID, Battery.ChannelId.SOC.id()); - @Test public void startBattery() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ComponentTest(new BatteryFeneconCommercialImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .setStartStop(StartStopConfig.START) // .setBatteryStartStopRelay("io0/InputOutput7")// .build())// .next(new TestCase("Battery Relay false, starting") // - .input(BATTERY_RELAY, false)// + .input("io0", INPUT_OUTPUT7, false)// .input(RUNNING, false)// Switched Off .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// .next(new TestCase() // @@ -63,18 +47,18 @@ public void startBattery() throws Exception { .next(new TestCase()// .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase()// - .input(BATTERY_RELAY, true))// + .input("io0", INPUT_OUTPUT7, true))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase()// .input(RUNNING, true)// - .input(BATTERY_RELAY, false))// + .input("io0", INPUT_OUTPUT7, false))// .next(new TestCase() // .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// .next(new TestCase("Battery Running")// .output(STATE_MACHINE, StateMachine.State.RUNNING))// .next(new TestCase("Battery Running")// - .output(BATTERY_RELAY, false)// + .output("io0", INPUT_OUTPUT7, false)// .output(RUNNING, true) // .output(STATE_MACHINE, StateMachine.State.RUNNING))// @@ -83,21 +67,21 @@ public void startBattery() throws Exception { @Test public void stopBattery() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ComponentTest(new BatteryFeneconCommercialImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .setStartStop(StartStopConfig.STOP) // .setBatteryStartStopRelay("io0/InputOutput7")// .build())// .next(new TestCase("Battery Running")// - .input(BATTERY_RELAY, false)// + .input("io0", INPUT_OUTPUT7, false)// .input(RUNNING, true) // .input(STATE_MACHINE, StateMachine.State.RUNNING))// .next(new TestCase("Stopping") // @@ -105,7 +89,7 @@ public void stopBattery() throws Exception { .next(new TestCase()// .output(STATE_MACHINE, StateMachine.State.GO_STOPPED))// .next(new TestCase()// - .input(BATTERY_RELAY, true)) // + .input("io0", INPUT_OUTPUT7, true)) // .next(new TestCase()// .output(STATE_MACHINE, StateMachine.State.STOPPED))// @@ -114,15 +98,15 @@ public void stopBattery() throws Exception { @Test public void socManipulationMin() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ComponentTest(new BatteryFeneconCommercialImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .setStartStop(StartStopConfig.START) // .setBatteryStartStopRelay("io0/InputOutput7")// @@ -130,22 +114,22 @@ public void socManipulationMin() throws Exception { .next(new TestCase("Soc")// .input(RUNNING, true) // .input(BATTERY_SOC, 10) // - .input(BATTERY_MAX_DISCHARGE_CURRENT, 0) // - .input(BATTERY_MAX_CHARGE_CURRENT, 10000) // + .input(DISCHARGE_MAX_CURRENT, 0) // + .input(CHARGE_MAX_CURRENT, 10000) // .output(SOC, 10)); } @Test public void socManipulationMax() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ComponentTest(new BatteryFeneconCommercialImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // .setStartStop(StartStopConfig.START) // .setBatteryStartStopRelay("io0/InputOutput7")// @@ -153,8 +137,8 @@ public void socManipulationMax() throws Exception { .next(new TestCase("Soc")// .input(RUNNING, true) // .input(BATTERY_SOC, 98) // - .input(BATTERY_MAX_DISCHARGE_CURRENT, 100000) // - .input(BATTERY_MAX_CHARGE_CURRENT, 0) // + .input(DISCHARGE_MAX_CURRENT, 100000) // + .input(CHARGE_MAX_CURRENT, 0) // .output(SOC, 100)); } } diff --git a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java index b28b1513542..a9db6249dc3 100644 --- a/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java +++ b/io.openems.edge.battery.fenecon.commercial/test/io/openems/edge/battery/fenecon/commercial/DynamicChannelsAndSerialNumbersTest.java @@ -1,10 +1,14 @@ package io.openems.edge.battery.fenecon.commercial; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.MASTER_MCU_HARDWARE_VERSION; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_CELLS_PER_MODULE; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_MODULES_PER_TOWER; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercial.ChannelId.NUMBER_OF_TOWERS; +import static io.openems.edge.battery.fenecon.commercial.BatteryFeneconCommercialImpl.VERSION_CONVERTER; import static org.junit.Assert.assertEquals; import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; @@ -16,24 +20,10 @@ public class DynamicChannelsAndSerialNumbersTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - private static final String IO_ID = "io0"; - private static final int TOWERS = 1; private static final int MODULES = 10; private static final int CELLS = 120;/* Read from register as cells*modules */ - private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID, - "NumberOfModulesPerTower"); - private static final ChannelAddress NUMBER_OF_TOWERS = new ChannelAddress(BATTERY_ID, "NumberOfTowers"); - private static final ChannelAddress NUMBER_OF_CELLS_PER_MODULE = new ChannelAddress(BATTERY_ID, - "NumberOfCellsPerModule"); - private static final ChannelAddress SUB_MASTER_HARDWARE_VERSION = new ChannelAddress(BATTERY_ID, - "Tower0SubMasterHardwareVersion"); - private static final ChannelAddress MASTER_MCU_HARDWARE_VERSION = new ChannelAddress(BATTERY_ID, - "MasterMcuHardwareVersion"); - @Test public void testSerialNum() throws Exception { var battery = new BatteryFeneconCommercialImpl(); @@ -41,11 +31,11 @@ public void testSerialNum() throws Exception { var componentTest = new ComponentTest(battery) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setBatteryStartStopRelay("io0/InputOutput0")// .setStartStop(StartStopConfig.AUTO) // @@ -56,10 +46,10 @@ public void testSerialNum() throws Exception { .input(NUMBER_OF_TOWERS, TOWERS) // .input(NUMBER_OF_MODULES_PER_TOWER, MODULES) // .input(NUMBER_OF_CELLS_PER_MODULE, CELLS) // - .input(SUB_MASTER_HARDWARE_VERSION, "109101BM60")); + .input("battery0", "Tower0SubMasterHardwareVersion", "109101BM60")); checkDynamicChannels(battery, TOWERS, MODULES, CELLS / MODULES); - assertEquals("011910MB06", BatteryFeneconCommercialImpl.VERSION_CONVERTER.elementToChannel("109101BM60")); + assertEquals("011910MB06", VERSION_CONVERTER.elementToChannel("109101BM60")); componentTest.next(new TestCase()); componentTest.next(new TestCase()); @@ -72,7 +62,7 @@ public void testSerialNum() throws Exception { .input(NUMBER_OF_CELLS_PER_MODULE, CELLS) // .input(MASTER_MCU_HARDWARE_VERSION, "100201MS50")); - assertEquals("012010SM05", BatteryFeneconCommercialImpl.VERSION_CONVERTER.elementToChannel("100201MS50")); + assertEquals("012010SM05", VERSION_CONVERTER.elementToChannel("100201MS50")); } /** diff --git a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java index bbad1e67bde..65dec26fc83 100644 --- a/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java +++ b/io.openems.edge.battery.fenecon.home/src/io/openems/edge/battery/fenecon/home/FeneconHomeBatteryProtection64.java @@ -25,8 +25,8 @@ public PolyLine getChargeVoltageToPercent() { .addPoint(Math.nextUp(3000), 1) // .addPoint(3450, 1) // .addPoint(3540, 0.08) // - .addPoint(Math.nextDown(3550), 0.08) // - .addPoint(3550, 0) // + .addPoint(Math.nextDown(3580), 0.08) // + .addPoint(3580, 0) // .build(); } @@ -70,7 +70,7 @@ public PolyLine getDischargeSocToPercent() { @Override public ForceDischarge.Params getForceDischargeParams() { - return new ForceDischarge.Params(3600, 3540, 3450); + return new ForceDischarge.Params(3630, 3540, 3450); } @Override diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java index 432eccdcb6e..de0aa103841 100644 --- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java +++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/BatteryFeneconHomeImplTest.java @@ -1,21 +1,38 @@ package io.openems.edge.battery.fenecon.home; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.MAX_CELL_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.MIN_CELL_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.SOC; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.BMS_CONTROL; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_WARNING; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_TOWERS; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.STATE_MACHINE; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_3_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHomeImpl.TIMEOUT; +import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_BMS; +import static io.openems.edge.battery.protection.BatteryProtection.ChannelId.BP_CHARGE_MAX_SOC; +import static io.openems.edge.bridge.modbus.api.ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT4; +import static java.lang.Math.round; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; - import org.junit.Test; import io.openems.common.function.ThrowingRunnable; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.api.Battery; -import io.openems.edge.battery.fenecon.home.statemachine.StateMachine; -import io.openems.edge.battery.protection.BatteryProtection; -import io.openems.edge.bridge.modbus.api.ModbusComponent; +import io.openems.edge.battery.fenecon.home.statemachine.StateMachine.State; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; import io.openems.edge.common.test.AbstractComponentTest.TestCase; @@ -26,51 +43,6 @@ public class BatteryFeneconHomeImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.STATE_MACHINE.id()); - private static final ChannelAddress LOW_MIN_VOLTAGE_WARNING = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_WARNING.id()); - private static final ChannelAddress LOW_MIN_VOLTAGE_FAULT = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT.id()); - private static final ChannelAddress LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED.id()); - private static final ChannelAddress MODBUS_COMMUNICATION_FAILED = new ChannelAddress(BATTERY_ID, - ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED.id()); - private static final ChannelAddress BMS_CONTROL = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.BMS_CONTROL.id()); - private static final ChannelAddress BP_CHARGE_BMS = new ChannelAddress(BATTERY_ID, - BatteryProtection.ChannelId.BP_CHARGE_BMS.id()); - private static final ChannelAddress MAX_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.MAX_CELL_VOLTAGE.id()); - private static final ChannelAddress CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.CHARGE_MAX_CURRENT.id()); - private static final ChannelAddress CURRENT = new ChannelAddress(BATTERY_ID, Battery.ChannelId.CURRENT.id()); - private static final ChannelAddress MIN_CELL_VOLTAGE = new ChannelAddress(BATTERY_ID, - Battery.ChannelId.MIN_CELL_VOLTAGE.id()); - private static final ChannelAddress TOWER_0_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress TOWER_1_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress TOWER_2_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress TOWER_3_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_3_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress TOWER_4_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION.id()); - private static final ChannelAddress NUMBER_OF_TOWERS = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.NUMBER_OF_TOWERS.id()); - private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID, - BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER.id()); - private static final ChannelAddress BP_CHARGE_MAX_SOC = new ChannelAddress(BATTERY_ID, - BatteryProtection.ChannelId.BP_CHARGE_MAX_SOC.id()); - private static final ChannelAddress SOC = new ChannelAddress(BATTERY_ID, Battery.ChannelId.SOC.id()); - - private static final ChannelAddress BATTERY_RELAY = new ChannelAddress(IO_ID, "InputOutput4"); - private static ThrowingRunnable assertLog(BatteryFeneconHomeImpl sut, String message) { return () -> assertEquals(message, sut.stateMachine.debugLog()); } @@ -82,41 +54,41 @@ private static ThrowingRunnable assertLog(BatteryFeneconHomeImpl sut, */ @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build())// .next(new TestCase() // .inputForce(MODBUS_COMMUNICATION_FAILED, true) // - .input(BATTERY_RELAY, false) // Switch OFF + .input("io0", INPUT_OUTPUT4, false) // Switch OFF .input(BMS_CONTROL, false) // Switched OFF .onBeforeProcessImage(assertLog(sut, "Undefined")) // - .output(STATE_MACHINE, StateMachine.State.UNDEFINED)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase()// .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn"))) // .next(new TestCase()// - .input(BATTERY_RELAY, true) // Switch ON + .input("io0", INPUT_OUTPUT4, true) // Switch ON .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn"))) // .next(new TestCase()// - .input(BATTERY_RELAY, true) // Switch ON + .input("io0", INPUT_OUTPUT4, true) // Switch ON .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold")) - .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) // + .onAfterProcessImage(() -> clock.leap(11, SECONDS))) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // Switch OFF + .input("io0", INPUT_OUTPUT4, false) // Switch OFF .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff"))) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication"))) // @@ -129,17 +101,17 @@ public void test() throws Exception { .next(new TestCase()// .onBeforeProcessImage(assertLog(sut, "Running")) // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) // + .output(STATE_MACHINE, State.RUNNING)) // // Ramp-Up ChargeMaxCurrent (0.1 A / Second) .next(new TestCase() // .input(BP_CHARGE_BMS, 40) // .input(MAX_CELL_VOLTAGE, 3000)) // .next(new TestCase("Ramp up") // - .timeleap(clock, 100, ChronoUnit.SECONDS) // + .timeleap(clock, 100, SECONDS) // .output(CHARGE_MAX_CURRENT, 10)) // .next(new TestCase() // - .timeleap(clock, 300, ChronoUnit.SECONDS) // + .timeleap(clock, 300, SECONDS) // .output(CHARGE_MAX_CURRENT, 40)) // Full Battery @@ -151,7 +123,7 @@ public void test() throws Exception { .next(new TestCase() // .input(BP_CHARGE_BMS, 40)) // .next(new TestCase() // - .timeleap(clock, 100, ChronoUnit.SECONDS) // + .timeleap(clock, 100, SECONDS) // .output(CHARGE_MAX_CURRENT, 25)) // ; } @@ -163,54 +135,54 @@ public void test() throws Exception { */ @Test public void test2() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build())// .next(new TestCase()// - .input(BATTERY_RELAY, true) // + .input("io0", INPUT_OUTPUT4, true) // .input(BMS_CONTROL, false) // Switched Off - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase()// - .input(BATTERY_RELAY, true) // Switch ON + .input("io0", INPUT_OUTPUT4, true) // Switch ON .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold")) - .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) // + .onAfterProcessImage(() -> clock.leap(11, SECONDS))) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .input(BATTERY_RELAY, false) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .input("io0", INPUT_OUTPUT4, false) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)); + .output(STATE_MACHINE, State.RUNNING)); } /** @@ -225,37 +197,37 @@ public void test3() throws Exception { new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build()) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)); + .output(STATE_MACHINE, State.RUNNING)); } /** @@ -265,53 +237,53 @@ public void test3() throws Exception { */ @Test public void test4() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4") // .build()) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, false) // Switched Off - .output(STATE_MACHINE, StateMachine.State.UNDEFINED)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // // Ex; after long time if hard switch turned on.... .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOn")) // - .input(BATTERY_RELAY, true) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .input("io0", INPUT_OUTPUT4, true) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase()// - .input(BATTERY_RELAY, true) // Switch ON + .input("io0", INPUT_OUTPUT4, true) // Switch ON .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayHold")) - .onAfterProcessImage(() -> clock.leap(11, ChronoUnit.SECONDS))) // + .onAfterProcessImage(() -> clock.leap(11, SECONDS))) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)); // + .output(STATE_MACHINE, State.GO_RUNNING)); // } @Test @@ -334,224 +306,221 @@ public void testGetHardwareTypeFromRegisterValue() { @Test public void testMinVoltageGoStopped() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build())// .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) + .output(STATE_MACHINE, State.RUNNING)) /* * Critical min voltage */ .next(new TestCase("MinCellVoltage below critical value") // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .input(CURRENT, 0) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING) // + .output(STATE_MACHINE, State.RUNNING) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .onAfterControllersCallbacks( - () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) // + .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) // .next(new TestCase("MinCellVoltage below critical value - charging resets time") // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .input(CURRENT, -300) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) // .next(new TestCase("MinCellVoltage below critical value - timer starts again") // .input(CURRENT, 0) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .onAfterControllersCallbacks( - () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) // + .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) // .next(new TestCase("MinCellVoltage below critical value - time not passed") // .input(CURRENT, 0) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .onAfterControllersCallbacks(() -> clock.leap(15, ChronoUnit.SECONDS))) // + .onAfterControllersCallbacks(() -> clock.leap(15, SECONDS))) // .next(new TestCase("MinCellVoltage below critical value - time passed") // .input(CURRENT, 0) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_FAULT, true) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) // + .output(STATE_MACHINE, State.RUNNING)) // .next(new TestCase("MinCellVoltage below critical value - error") // .input(LOW_MIN_VOLTAGE_FAULT, true) // .input(CURRENT, 0) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_FAULT, true) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.ERROR)) // + .output(STATE_MACHINE, State.ERROR)) // .next(new TestCase("MinCellVoltage below critical value - go stopped") // .input(LOW_MIN_VOLTAGE_FAULT, true) // .input(CURRENT, 0) // // MinCellVoltage would be null, but there is not DummyTimedata for not to test // "getPastValues" - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_FAULT, true) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .output(STATE_MACHINE, StateMachine.State.GO_STOPPED) // - .onAfterControllersCallbacks(() -> clock.leap(2_100, ChronoUnit.SECONDS))) // 35 minutes + .output(STATE_MACHINE, State.GO_STOPPED) // + .onAfterControllersCallbacks(() -> clock.leap(2_100, SECONDS))) // 35 minutes .next(new TestCase() // .input(MODBUS_COMMUNICATION_FAILED, true) // ) // .next(new TestCase("MinCellVoltage below critical value - stopped") // .input(CURRENT, 0) // .input(MODBUS_COMMUNICATION_FAILED, true) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT, false) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, true) // - .output(STATE_MACHINE, StateMachine.State.STOPPED) // + .output(STATE_MACHINE, State.STOPPED) // ); } @Test public void testMinVoltageCharging() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build())// .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) + .output(STATE_MACHINE, State.RUNNING)) /* * Critical min voltage */ .next(new TestCase("MinCellVoltage below critical value") // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .input(CURRENT, 0) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING) // + .output(STATE_MACHINE, State.RUNNING) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // - .onAfterControllersCallbacks( - () -> clock.leap(BatteryFeneconHomeImpl.TIMEOUT - 10, ChronoUnit.SECONDS))) // + .onAfterControllersCallbacks(() -> clock.leap(TIMEOUT - 10, SECONDS))) // .next(new TestCase("MinCellVoltage below critical value - charging resets time") // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE - 100)) // .input(CURRENT, -300) // .output(LOW_MIN_VOLTAGE_WARNING, true) // .output(LOW_MIN_VOLTAGE_FAULT, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING) // + .output(STATE_MACHINE, State.RUNNING) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false)) // .next(new TestCase("MinCellVoltage below critical value - charging") // .input(CURRENT, -2000) // - .input(MIN_CELL_VOLTAGE, (BatteryFeneconHomeImpl.DEFAULT_CRITICAL_MIN_VOLTAGE + 50)) // + .input(MIN_CELL_VOLTAGE, (DEFAULT_CRITICAL_MIN_VOLTAGE + 50)) // .output(LOW_MIN_VOLTAGE_WARNING, false) // .output(LOW_MIN_VOLTAGE_FAULT, false) // - .output(STATE_MACHINE, StateMachine.State.RUNNING) // + .output(STATE_MACHINE, State.RUNNING) // .output(LOW_MIN_VOLTAGE_FAULT_BATTERY_STOPPED, false) // ); } @Test public void testNumberOfTowers() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var sut = new BatteryFeneconHomeImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // - .setBatteryStartUpRelay("io0/InputOutput4")// - .build())// + .setBatteryStartUpRelay("io0/InputOutput4") // + .build()) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) + .output(STATE_MACHINE, State.RUNNING)) .next(new TestCase() // .output(NUMBER_OF_TOWERS, null)) .next(new TestCase() // @@ -610,37 +579,37 @@ public void testBatteryProtectionSocLimitations() throws Exception { new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // - .addComponent(new DummyInputOutput(IO_ID))// + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addComponent(new DummyInputOutput("io0"))// .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.START) // .setBatteryStartUpRelay("io0/InputOutput4")// .build()) // .next(new TestCase() // - .input(BATTERY_RELAY, false) // + .input("io0", INPUT_OUTPUT4, false) // .input(BMS_CONTROL, true) // Switched On - .output(STATE_MACHINE, StateMachine.State.UNDEFINED))// + .output(STATE_MACHINE, State.UNDEFINED))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-Undefined")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-StartUpRelayOff")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-RetryModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING))// + .output(STATE_MACHINE, State.GO_RUNNING))// .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForBmsControl")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // .onBeforeProcessImage(assertLog(sut, "GoRunning-WaitForModbusCommunication")) // - .output(STATE_MACHINE, StateMachine.State.GO_RUNNING)) // + .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.RUNNING)) // + .output(STATE_MACHINE, State.RUNNING)) // .next(new TestCase() // .output(BP_CHARGE_MAX_SOC, 40)) // @@ -648,23 +617,23 @@ public void testBatteryProtectionSocLimitations() throws Exception { .input(SOC, 97) // .output(SOC, 97)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.625))) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.625F))) // .next(new TestCase() // .input(SOC, 98)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.4))) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.4F))) // .next(new TestCase() // .input(SOC, 99)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.2))) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.2F))) // .next(new TestCase() // .input(SOC, 100)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.05))) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.05F))) // .next(new TestCase() // .input(SOC, 99)) // .next(new TestCase() // - .output(BP_CHARGE_MAX_SOC, (int) Math.round(40 * 0.2)) // + .output(BP_CHARGE_MAX_SOC, round(40 * 0.2F)) // ); } } diff --git a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java index ff8b7353731..9fff171c345 100644 --- a/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java +++ b/io.openems.edge.battery.fenecon.home/test/io/openems/edge/battery/fenecon/home/TowersAndModulesTest.java @@ -1,11 +1,15 @@ package io.openems.edge.battery.fenecon.home; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.BATTERY_HARDWARE_TYPE; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.NUMBER_OF_MODULES_PER_TOWER; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_0_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_1_BMS_SOFTWARE_VERSION; +import static io.openems.edge.battery.fenecon.home.BatteryFeneconHome.ChannelId.TOWER_2_BMS_SOFTWARE_VERSION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.channel.ChannelId; @@ -17,19 +21,6 @@ public class TowersAndModulesTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - - private static final ChannelAddress NUMBER_OF_MODULES_PER_TOWER = new ChannelAddress(BATTERY_ID, - "NumberOfModulesPerTower"); - private static final ChannelAddress TOWER_0_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - "Tower0BmsSoftwareVersion"); - private static final ChannelAddress TOWER_1_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - "Tower1BmsSoftwareVersion"); - private static final ChannelAddress TOWER_2_BMS_SOFTWARE_VERSION = new ChannelAddress(BATTERY_ID, - "Tower2BmsSoftwareVersion"); - private static final ChannelAddress BATTERY_HARDWARE_TYPE = new ChannelAddress(BATTERY_ID, "BatteryHardwareType"); - private static final int TOWERS = 1; private static final int MODULES = 5; private static final int CELLS = 14; @@ -40,10 +31,10 @@ public void testChannelsCreatedDynamically() throws Exception { var componentTest = new ComponentTest(battery) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setBatteryStartUpRelay("io0/Relay4") // .setStartStop(StartStopConfig.AUTO) // diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java index eda7ce07667..be7f6db76ce 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionb/BatterySoltaroClusterVersionBImplTest.java @@ -1,9 +1,13 @@ package io.openems.edge.battery.soltaro.cluster.versionb; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_1_COMMUNICATION_FAILURE; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_2_COMMUNICATION_FAILURE; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_3_COMMUNICATION_FAILURE; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_4_COMMUNICATION_FAILURE; +import static io.openems.edge.battery.soltaro.cluster.SoltaroCluster.ChannelId.SUB_MASTER_5_COMMUNICATION_FAILURE; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.soltaro.cluster.SoltaroCluster; import io.openems.edge.battery.soltaro.common.enums.BatteryState; import io.openems.edge.battery.soltaro.common.enums.ModuleType; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; @@ -14,30 +18,16 @@ public class BatterySoltaroClusterVersionBImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - - private static final ChannelAddress SUB_MASTER_1_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_1_COMMUNICATION_FAILURE.id()); - private static final ChannelAddress SUB_MASTER_2_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_2_COMMUNICATION_FAILURE.id()); - private static final ChannelAddress SUB_MASTER_3_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_3_COMMUNICATION_FAILURE.id()); - private static final ChannelAddress SUB_MASTER_4_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_4_COMMUNICATION_FAILURE.id()); - private static final ChannelAddress SUB_MASTER_5_COMMUNICATION_FAILURE = new ChannelAddress(BATTERY_ID, - SoltaroCluster.ChannelId.SUB_MASTER_5_COMMUNICATION_FAILURE.id()); - @Test public void test() throws Exception { var sut = new BatterySoltaroClusterVersionBImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setNumberOfSlaves(0) // .setModuleType(ModuleType.MODULE_3_5_KWH) // diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java index aaffbc2cc9e..ebcb21122ce 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/cluster/versionc/BatterySoltaroClusterVersionCImplTest.java @@ -9,17 +9,14 @@ public class BatterySoltaroClusterVersionCImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatterySoltaroClusterVersionCImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setStartStop(StartStopConfig.AUTO) // .build()) // ; diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java index 443dbce24c9..daccfa35f66 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versiona/BatterySoltaroSingleRackVersionAImplTest.java @@ -9,17 +9,14 @@ public class BatterySoltaroSingleRackVersionAImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatterySoltaroSingleRackVersionAImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setErrorLevel2Delay(0) // .setMaxStartTime(0) // diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java index b2b33ff92fb..3fce33da342 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionb/BatterySoltaroSingleRackVersionBImplTest.java @@ -11,18 +11,15 @@ public class BatterySoltaroSingleRackVersionBImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatterySoltaroSingleRackVersionBImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setErrorLevel2Delay(0) // .setMaxStartTime(0) // diff --git a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java index 82e9a8c75d9..504d8d66b2a 100644 --- a/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java +++ b/io.openems.edge.battery.soltaro/test/io/openems/edge/battery/soltaro/single/versionc/BatterySoltaroSingleRackVersionCImplTest.java @@ -9,17 +9,14 @@ public class BatterySoltaroSingleRackVersionCImplTest { - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatterySoltaroSingleRackVersionCImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_ID) // - .setModbusId(MODBUS_ID) // + .setId("battery0") // + .setModbusId("modbus0") // .setModbusUnitId(0) // .setStartStop(StartStopConfig.AUTO) // .build()) // diff --git a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java index 4bc316e97c5..fc68eda27ec 100644 --- a/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java +++ b/io.openems.edge.batteryinverter.kaco.blueplanetgridsave/test/io/openems/edge/batteryinverter/kaco/blueplanetgridsave/BatteryInverterKacoBlueplanetGridsaveImplTest.java @@ -1,15 +1,18 @@ package io.openems.edge.batteryinverter.kaco.blueplanetgridsave; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TRIGGER_SECONDS; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.BatteryInverterKacoBlueplanetGridsave.ChannelId.STATE_MACHINE; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.CURRENT_STATE; +import static io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.WATCHDOG; +import static java.time.temporal.ChronoUnit.SECONDS; import org.junit.Before; import org.junit.Test; import io.openems.common.exceptions.OpenemsException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.kaco.blueplanetgridsave.KacoSunSpecModel.S64201.S64201CurrentState; @@ -24,25 +27,13 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.common.test.TestUtils; public class BatteryInverterKacoBlueplanetGridsaveImplTest { - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_INVERTER_ID, "StateMachine"); - - private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID, - "MaxApparentPower"); - private static final ChannelAddress CURRENT_STATE = new ChannelAddress(BATTERY_INVERTER_ID, - KacoSunSpecModel.S64201.CURRENT_STATE.getChannelId().id()); - private static final ChannelAddress WATCHDOG = new ChannelAddress(BATTERY_INVERTER_ID, - KacoSunSpecModel.S64201.WATCHDOG.getChannelId().id()); - private static class MyComponentTest extends ComponentTest { - private final Battery battery = new DummyBattery(BATTERY_ID); + private final Battery battery = new DummyBattery("battery0"); public MyComponentTest(OpenemsComponent sut) throws OpenemsException { super(sut); @@ -63,15 +54,13 @@ protected void handleEvent(String topic) throws Exception { @Before public void prepareTest() throws Exception { - final var start = 1577836800L; - clock = new TimeLeapClock(Instant.ofEpochSecond(start) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); + clock = TestUtils.createDummyClock(); var sut = new BatteryInverterKacoBlueplanetGridsaveImpl(); test = new MyComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)); + .addReference("setModbus", new DummyModbusBridge("modbus0")); // TODO implement proper Dummy-Modbus-Bridge with SunSpec support. Till then... test.addReference("isSunSpecInitializationCompleted", true); // @@ -86,16 +75,16 @@ public void prepareTest() throws Exception { addChannel.invoke(sut, KacoSunSpecModel.S64202.CHA_MAX_A_0.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64202.EN_LIMIT_0.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64201.REQUESTED_STATE.getChannelId()); - addChannel.invoke(sut, KacoSunSpecModel.S64201.CURRENT_STATE.getChannelId()); - addChannel.invoke(sut, KacoSunSpecModel.S64201.WATCHDOG.getChannelId()); + addChannel.invoke(sut, CURRENT_STATE.getChannelId()); + addChannel.invoke(sut, WATCHDOG.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64201.W_SET_PCT.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64201.WPARAM_RMP_TMS.getChannelId()); addChannel.invoke(sut, KacoSunSpecModel.S64201.ST_VND.getChannelId()); test.activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // + .setId("batteryInverter0") // .setStartStopConfig(StartStopConfig.START) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setActivateWatchdog(true) // .build()); // } @@ -104,16 +93,16 @@ public void prepareTest() throws Exception { public void testStart() throws Exception { test // .next(new TestCase() // - .input(CURRENT_STATE, S64201CurrentState.STANDBY) // + .input(CURRENT_STATE.getChannelId(), S64201CurrentState.STANDBY) // .input(MAX_APPARENT_POWER, 50_000) // .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .timeleap(clock, 4, ChronoUnit.SECONDS) // + .timeleap(clock, 4, SECONDS) // .output(STATE_MACHINE, State.GO_RUNNING)) // .next(new TestCase() // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .input(CURRENT_STATE, S64201CurrentState.GRID_CONNECTED) // - .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) // + .timeleap(clock, 1, SECONDS) // + .input(CURRENT_STATE.getChannelId(), S64201CurrentState.GRID_CONNECTED) // + .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) // .next(new TestCase() // .output(STATE_MACHINE, State.RUNNING)) // ; @@ -123,14 +112,13 @@ public void testStart() throws Exception { public void testWatchdog() throws Exception { test // .next(new TestCase() // - .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) // + .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) // .next(new TestCase() // - .timeleap(clock, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TRIGGER_SECONDS - 1, - ChronoUnit.SECONDS) // - .output(WATCHDOG, null /* waiting till next watchdog trigger */)) // + .timeleap(clock, WATCHDOG_TRIGGER_SECONDS - 1, SECONDS) // + .output(WATCHDOG.getChannelId(), null /* waiting till next watchdog trigger */)) // .next(new TestCase() // - .timeleap(clock, 1, ChronoUnit.SECONDS) // - .output(WATCHDOG, BatteryInverterKacoBlueplanetGridsave.WATCHDOG_TIMEOUT_SECONDS)) // + .timeleap(clock, 1, SECONDS) // + .output(WATCHDOG.getChannelId(), WATCHDOG_TIMEOUT_SECONDS)) // ; } } diff --git a/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java b/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java index 08316f856cf..560aa3bb4f3 100644 --- a/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java +++ b/io.openems.edge.batteryinverter.refu88k/test/io/openems/edge/batteryinverter/refu88k/BatteryInverterRefuStore88kImplTest.java @@ -9,17 +9,14 @@ public class BatteryInverterRefuStore88kImplTest { - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BatteryInverterRefuStore88kImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // .setStartStop(StartStopConfig.AUTO) // .setTimeLimitNoPower(0) // .setWatchdoginterval(0) // diff --git a/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java b/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java index 6092ebde7d6..abf144c927e 100644 --- a/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java +++ b/io.openems.edge.batteryinverter.sinexcel/test/io/openems/edge/batteryinverter/sinexcel/BatteryInverterSinexcelImplTest.java @@ -1,5 +1,11 @@ package io.openems.edge.batteryinverter.sinexcel; +import static io.openems.edge.batteryinverter.api.OffGridBatteryInverter.ChannelId.INVERTER_STATE; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.SET_OFF_GRID_MODE; +import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.SET_ON_GRID_MODE; +import static io.openems.edge.batteryinverter.sinexcel.BatteryInverterSinexcel.ChannelId.STATE_MACHINE; + import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -8,10 +14,8 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; -import io.openems.edge.batteryinverter.api.OffGridBatteryInverter; import io.openems.edge.batteryinverter.api.OffGridBatteryInverter.TargetGridMode; import io.openems.edge.batteryinverter.sinexcel.enums.CountryCode; import io.openems.edge.batteryinverter.sinexcel.enums.EnableDisable; @@ -27,21 +31,9 @@ public class BatteryInverterSinexcelImplTest { - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String BATTERY_ID = "battery0"; - private static final String MODBUS_ID = "modbus0"; - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(BATTERY_INVERTER_ID, "StateMachine"); - private static final ChannelAddress SET_ON_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOnGridMode"); - private static final ChannelAddress SET_OFF_GRID_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "SetOffGridMode"); - private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID, // - "MaxApparentPower"); - - private static final ChannelAddress INVERTER_STATE = new ChannelAddress(BATTERY_INVERTER_ID, // - OffGridBatteryInverter.ChannelId.INVERTER_STATE.id()); - private static class MyComponentTest extends ComponentTest { - private final Battery battery = new DummyBattery(BATTERY_ID); + private final Battery battery = new DummyBattery("battery0"); public MyComponentTest(OpenemsComponent sut) throws OpenemsException { super(sut); @@ -64,11 +56,11 @@ public void testStart() throws Exception { new MyComponentTest(new BatteryInverterSinexcelImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // + .setId("batteryInverter0") // .setStartStopConfig(StartStopConfig.START) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setCountryCode(CountryCode.GERMANY)// .setEmergencyPower(EnableDisable.DISABLE)// .build()) // @@ -100,11 +92,11 @@ public void testOffGrid() throws Exception { new MyComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // + .setId("batteryInverter0") // .setStartStopConfig(StartStopConfig.START) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setCountryCode(CountryCode.GERMANY)// .setEmergencyPower(EnableDisable.DISABLE)// .build()) // diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java index befb0e1ebca..fcd0d24b56d 100644 --- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java +++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpBundle.java @@ -1,5 +1,6 @@ package io.openems.edge.bridge.http.dummy; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.dummyEndpointFetcher; import static java.util.Collections.emptyMap; import java.util.concurrent.CompletableFuture; @@ -7,7 +8,6 @@ import org.osgi.service.event.Event; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; import io.openems.edge.bridge.http.api.BridgeHttpFactory; import io.openems.edge.bridge.http.api.CycleSubscriber; @@ -17,9 +17,8 @@ public class DummyBridgeHttpBundle { - private final DummyEndpointFetcher fetcher = DummyBridgeHttpFactory.dummyEndpointFetcher(); - private final DummyBridgeHttpExecutor pool = DummyBridgeHttpFactory.dummyBridgeHttpExecutor(new TimeLeapClock(), - true); + private final DummyEndpointFetcher fetcher = dummyEndpointFetcher(); + private final DummyBridgeHttpExecutor pool = DummyBridgeHttpFactory.dummyBridgeHttpExecutor(true); private final CycleSubscriber cycleSubscriber = DummyBridgeHttpFactory.cycleSubscriber(); private final DummyBridgeHttpFactory bridgeFactory = DummyBridgeHttpFactory.ofBridgeImpl(() -> this.cycleSubscriber, () -> this.fetcher, () -> this.pool); diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java index 2a74f1c7ea0..cf1d07df092 100644 --- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java +++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpExecutor.java @@ -1,5 +1,7 @@ package io.openems.edge.bridge.http.dummy; +import static io.openems.edge.common.test.TestUtils.createDummyClock; + import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -110,10 +112,18 @@ public DummyBridgeHttpExecutor(Clock clock, boolean handleTasksImmediately) { this.taskExecutor = handleTasksImmediately ? new ImmediateTaskExecutor() : new DelayedTaskExecutor(); } + public DummyBridgeHttpExecutor(boolean handleTasksImmediately) { + this(createDummyClock(), handleTasksImmediately); + } + public DummyBridgeHttpExecutor(Clock clock) { this(clock, false); } + public DummyBridgeHttpExecutor() { + this(false); + } + @Override public ScheduledFuture schedule(Runnable task, Delay.DurationDelay durationDelay) { if (this.isShutdown()) { diff --git a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java index 635b56ad885..01f1f169e83 100644 --- a/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java +++ b/io.openems.edge.bridge.http/src/io/openems/edge/bridge/http/dummy/DummyBridgeHttpFactory.java @@ -124,6 +124,21 @@ public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(// return new DummyBridgeHttpExecutor(clock, handleTasksImmediately); } + /** + * Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the + * requests to fetch an {@link Endpoint}. + * + * @param handleTasksImmediately true if all tasks which are not scheduled + * should be executed immediately in the same + * thread; false if only executed during the + * {@link DummyBridgeHttpExecutor#update()} + * method. + * @return the created {@link DummyBridgeHttpExecutor} + */ + public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(boolean handleTasksImmediately) { + return new DummyBridgeHttpExecutor(handleTasksImmediately); + } + /** * Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the * requests to fetch an {@link Endpoint}. @@ -136,6 +151,16 @@ public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor(Clock clock) { return new DummyBridgeHttpExecutor(clock); } + /** + * Creates a {@link DummyBridgeHttpExecutor} to handle the execution of the + * requests to fetch an {@link Endpoint}. + * + * @return the created {@link DummyBridgeHttpExecutor} + */ + public static DummyBridgeHttpExecutor dummyBridgeHttpExecutor() { + return new DummyBridgeHttpExecutor(); + } + private DummyBridgeHttpFactory(Supplier supplier) { super(new DummyBridgeHttpCso(supplier)); } diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java index 0e4ca875adc..ca2a7d82130 100644 --- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java +++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpCycleTest.java @@ -13,7 +13,6 @@ import org.junit.Test; import org.osgi.service.event.Event; -import io.openems.common.test.TimeLeapClock; import io.openems.common.utils.FunctionUtils; import io.openems.edge.bridge.http.BridgeHttpImpl; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; @@ -33,7 +32,7 @@ public class BridgeHttpCycleTest { public void before() throws Exception { this.cycleSubscriber = new CycleSubscriber(); this.fetcher = new DummyEndpointFetcher(); - this.pool = new DummyBridgeHttpExecutor(new TimeLeapClock()); + this.pool = new DummyBridgeHttpExecutor(); this.bridgeHttp = new BridgeHttpImpl(// this.cycleSubscriber, // this.fetcher, // diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java index 013e2ddc067..253668672e1 100644 --- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java +++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTest.java @@ -1,5 +1,6 @@ package io.openems.edge.bridge.http.api; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.dummyBridgeHttpExecutor; import static java.util.Collections.emptyMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -14,7 +15,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.function.ThrowingFunction; -import io.openems.common.test.TimeLeapClock; import io.openems.common.utils.JsonUtils; import io.openems.edge.bridge.http.BridgeHttpImpl; import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; @@ -31,8 +31,7 @@ public class BridgeHttpTest { public void before() throws Exception { this.cycleSubscriber = DummyBridgeHttpFactory.cycleSubscriber(); this.fetcher = DummyBridgeHttpFactory.dummyEndpointFetcher(); - this.bridgeHttp = new BridgeHttpImpl(this.cycleSubscriber, this.fetcher, - DummyBridgeHttpFactory.dummyBridgeHttpExecutor(new TimeLeapClock(), true)); + this.bridgeHttp = new BridgeHttpImpl(this.cycleSubscriber, this.fetcher, dummyBridgeHttpExecutor(true)); } @After diff --git a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java index 74f89ade961..db098dab702 100644 --- a/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java +++ b/io.openems.edge.bridge.http/test/io/openems/edge/bridge/http/api/BridgeHttpTimeTest.java @@ -1,6 +1,7 @@ package io.openems.edge.bridge.http.api; import static io.openems.edge.bridge.http.time.DelayTimeProviderChain.fixedDelay; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static org.junit.Assert.assertEquals; import java.time.Duration; @@ -35,7 +36,7 @@ public void before() throws Exception { }; }); - this.pool = new DummyBridgeHttpExecutor(this.clock = new TimeLeapClock()); + this.pool = new DummyBridgeHttpExecutor(this.clock = createDummyClock()); this.bridgeHttp = new BridgeHttpImpl(cycleSubscriber, fetcher, this.pool); } diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java index 3645d2d6fdf..1d6d1e780ba 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusSerialImplTest.java @@ -9,13 +9,11 @@ public class BridgeModbusSerialImplTest { - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new BridgeModbusSerialImpl()) // .activate(MyConfigSerial.create() // - .setId(MODBUS_ID) // + .setId("modbus0") // .setPortName("/etc/ttyUSB0") // .setBaudRate(9600) // .setDatabits(8) // diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java index e9400baf670..33c3496c644 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/BridgeModbusTcpImplTest.java @@ -1,5 +1,7 @@ package io.openems.edge.bridge.modbus; +import static io.openems.edge.bridge.modbus.api.ModbusComponent.ChannelId.MODBUS_COMMUNICATION_FAILED; + import org.junit.Test; import com.ghgande.j2mod.modbus.procimg.Register; @@ -10,7 +12,6 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.function.ThrowingRunnable; -import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.edge.bridge.modbus.api.AbstractModbusBridge; import io.openems.edge.bridge.modbus.api.LogVerbosity; @@ -25,15 +26,9 @@ public class BridgeModbusTcpImplTest { - private static final String MODBUS_ID = "modbus0"; - private static final String DEVICE_ID = "device0"; private static final int UNIT_ID = 1; private static final int CYCLE_TIME = 100; - private static final ChannelAddress REGISTER_100 = new ChannelAddress(DEVICE_ID, "Register100"); - private static final ChannelAddress MODBUS_COMMUNICATION_FAILED = new ChannelAddress(DEVICE_ID, - "ModbusCommunicationFailed"); - @Test public void test() throws Exception { final ThrowingRunnable sleep = () -> Thread.sleep(CYCLE_TIME); @@ -57,13 +52,13 @@ public void test() throws Exception { var sut = new BridgeModbusTcpImpl(); var test = new ComponentTest(sut) // .activate(MyConfigTcp.create() // - .setId(MODBUS_ID) // + .setId("modbus0") // .setIp("127.0.0.1") // .setPort(port) // .setInvalidateElementsAfterReadErrors(1) // .setLogVerbosity(LogVerbosity.NONE) // .build()); - test.addComponent(new MyModbusComponent(DEVICE_ID, sut, UNIT_ID)); + test.addComponent(new MyModbusComponent("device0", sut, UNIT_ID)); /* * Successfully read Register @@ -73,21 +68,21 @@ public void test() throws Exception { .onAfterProcessImage(sleep)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .output(REGISTER_100, 123) // - .output(MODBUS_COMMUNICATION_FAILED, false)); // + .output("device0", MyModbusComponent.ChannelId.REGISTER_100, 123) // + .output("device0", MODBUS_COMMUNICATION_FAILED, false)); // /* * Remove Protocol and unset channel values */ - sut.removeProtocol(DEVICE_ID); + sut.removeProtocol("device0"); test // .next(new TestCase() // .onAfterProcessImage(sleep)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .output(REGISTER_100, null) // - .output(MODBUS_COMMUNICATION_FAILED, false)); // + .output("device0", MyModbusComponent.ChannelId.REGISTER_100, null) // + .output("device0", MODBUS_COMMUNICATION_FAILED, false)); // } finally { if (slave != null) { diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java index 615037eb9de..ce0b9f97068 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/DefectiveComponentsTest.java @@ -1,6 +1,7 @@ package io.openems.edge.bridge.modbus.api.worker.internal; import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -9,15 +10,13 @@ import org.junit.Test; -import io.openems.common.test.TimeLeapClock; - public class DefectiveComponentsTest { private static final String CMP = "foo"; @Test public void testIsDueForNextTry() { - var clock = new TimeLeapClock(); + final var clock = createDummyClock(); var sut = new DefectiveComponents(clock, LOG_HANDLER); assertNull(sut.isDueForNextTry(CMP)); @@ -29,7 +28,7 @@ public void testIsDueForNextTry() { @Test public void testAddRemove() { - var clock = new TimeLeapClock(); + final var clock = createDummyClock(); var sut = new DefectiveComponents(clock, LOG_HANDLER); sut.add(CMP); diff --git a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java index 1b016400acf..77e3d8b8f3f 100644 --- a/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java +++ b/io.openems.edge.bridge.modbus/test/io/openems/edge/bridge/modbus/api/worker/internal/TasksSupplierImplTest.java @@ -1,6 +1,7 @@ package io.openems.edge.bridge.modbus.api.worker.internal; import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -37,7 +38,7 @@ public void before() { @Test public void testFull() throws OpenemsException { - var clock = new TimeLeapClock(); + final var clock = createDummyClock(); var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER); var sut = new TasksSupplierImpl(LOG_HANDLER); diff --git a/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java b/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java index 744192de7c8..c81a5ca33c2 100644 --- a/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java +++ b/io.openems.edge.bridge.onewire/test/io/openems/edge/bridge/onewire/impl/BridgeOnewireImplTest.java @@ -6,13 +6,11 @@ public class BridgeOnewireImplTest { - private static final String BRIDGE_ID = "onewire0"; - @Test public void test() throws Exception { new ComponentTest(new BridgeOnewireImpl()) // .activate(MyConfig.create() // - .setId(BRIDGE_ID) // + .setId("onewire0") // .setPort("USB1") // .build()) // ; diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java index 1aaa95601f7..52337d0c78f 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java @@ -1,5 +1,6 @@ package io.openems.edge.common.currency; +import io.openems.common.types.CurrencyConfig; import io.openems.common.types.OptionsEnum; public enum Currency implements OptionsEnum { @@ -30,4 +31,18 @@ public OptionsEnum getUndefined() { return Currency.UNDEFINED; } + /** + * Converts the {@link CurrencyConfig} to the {@link Currency}. + * + * @param config currencyConfig to be transformed + * @return The {@link Currency}. + */ + public static Currency fromCurrencyConfig(CurrencyConfig config) { + return switch (config) { + case EUR -> Currency.EUR; + case SEK -> Currency.SEK; + case CHF -> Currency.CHF; + }; + } + } diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java deleted file mode 100644 index 2448ff5b004..00000000000 --- a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.openems.edge.common.currency; - -import io.openems.edge.common.meta.Meta; -import io.openems.edge.common.meta.Meta.ChannelId; - -/** - * The {@link ChannelId#CURRENCY} mandates the selection of the 'currency' - * configuration property of this specific type. Subsequently, this selected - * property is transformed into the corresponding {@link Currency} type before - * being written through {@link Meta#_setCurrency(Currency)}. - */ -public enum CurrencyConfig { - /** - * Euro. - */ - EUR, - /** - * Swedish Krona. - */ - SEK, - /** - * Swiss Francs. - */ - CHF; - - /** - * Converts the {@link CurrencyConfig} to the {@link Currency}. - * - * @return The {@link Currency}. - */ - public Currency toCurrency() { - return switch (this) { - case EUR -> Currency.EUR; - case SEK -> Currency.SEK; - case CHF -> Currency.CHF; - }; - } -} diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java index f714fb70658..e09c9fc0b4d 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/AbstractComponentTest.java @@ -42,6 +42,10 @@ import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.sum.Sum; +import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ChannelAddressValue; +import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ChannelIdValue; +import io.openems.edge.common.test.AbstractComponentTest.ChannelValue.ComponentChannelIdValue; import io.openems.edge.common.type.TypeUtils; /** @@ -49,10 +53,42 @@ */ public abstract class AbstractComponentTest, SUT extends OpenemsComponent> { - public record ChannelValue(ChannelAddress address, Object value, boolean force) { - @Override - public String toString() { - return this.address.toString() + ":" + this.value; + public sealed interface ChannelValue { + + /** + * Gets the value. + * + * @return the value + */ + public Object value(); + + /** + * Is the value enforced?. + * + * @return true for force + */ + public boolean force(); + + public record ChannelAddressValue(ChannelAddress address, Object value, boolean force) implements ChannelValue { + @Override + public String toString() { + return this.address.toString() + ":" + this.value; + } + } + + public record ChannelIdValue(ChannelId channelId, Object value, boolean force) implements ChannelValue { + @Override + public String toString() { + return this.channelId.id() + ":" + this.value; + } + } + + public record ComponentChannelIdValue(String componentId, ChannelId channelId, Object value, boolean force) + implements ChannelValue { + @Override + public String toString() { + return this.componentId + "/" + this.channelId.id() + ":" + this.value; + } } } @@ -109,19 +145,59 @@ public TestCase(String description) { } /** - * Adds an input value for a Channel. + * Adds an input value for a {@link ChannelAddress}. * * @param address the {@link ChannelAddress} * @param value the value {@link Object} * @return myself */ public TestCase input(ChannelAddress address, Object value) { - this.inputs.add(new ChannelValue(address, value, false)); + this.inputs.add(new ChannelAddressValue(address, value, false)); return this; } /** - * Enforces an input value for a Channel. + * Adds an input value for a ChannelId of the given Component. + * + * @param componentId the Component-ID + * @param channelId the Channel-ID in CamelCase + * @param value the value {@link Object} + * @return myself + */ + public TestCase input(String componentId, String channelId, Object value) { + return this.input(new ChannelAddress(componentId, channelId), value); + } + + /** + * Adds an input value for a {@link ChannelId} of the given Component. + * + * @param componentId the Component-ID + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase input(String componentId, ChannelId channelId, Object value) { + this.inputs.add(new ComponentChannelIdValue(componentId, channelId, value, false)); + return this; + } + + /** + * Adds an input value for a {@link ChannelId} of the system-under-test. + * + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase input(ChannelId channelId, Object value) { + if (channelId instanceof Sum.ChannelId) { + return this.input("_sum", channelId, value); + } + this.inputs.add(new ChannelIdValue(channelId, value, false)); + return this; + } + + /** + * Enforces an input value for a {@link ChannelAddress}. * *

* Use this method if you want to be sure, that the Channel actually applies the @@ -132,19 +208,102 @@ public TestCase input(ChannelAddress address, Object value) { * @return myself */ public TestCase inputForce(ChannelAddress address, Object value) { - this.inputs.add(new ChannelValue(address, value, true)); + this.inputs.add(new ChannelAddressValue(address, value, true)); return this; } /** - * Adds an expected output value for a Channel. + * Enforces an input value for a {@link ChannelAddress}. + * + *

+ * Use this method if you want to be sure, that the Channel actually applies the + * value, e.g. to override a {@link Debounce} setting. + * + * @param componentId the Component-ID + * @param channelId the Channel-ID + * @param value the value {@link Object} + * @return myself + */ + public TestCase inputForce(String componentId, String channelId, Object value) { + return this.inputForce(new ChannelAddress(componentId, channelId), value); + } + + /** + * Enforces an input value for a {@link ChannelId} of the given Component. + * + * @param componentId the Component-ID + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase inputForce(String componentId, ChannelId channelId, Object value) { + this.inputs.add(new ComponentChannelIdValue(componentId, channelId, value, true)); + return this; + } + + /** + * Enforces an input value for a {@link ChannelId} of the system-under-test. + * + *

+ * Use this method if you want to be sure, that the Channel actually applies the + * value, e.g. to override a {@link Debounce} setting. + * + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase inputForce(ChannelId channelId, Object value) { + this.inputs.add(new ChannelIdValue(channelId, value, true)); + return this; + } + + /** + * Adds an expected output value for a {@link ChannelAddress}. * * @param address the {@link ChannelAddress} * @param value the value {@link Object} * @return myself */ public TestCase output(ChannelAddress address, Object value) { - this.outputs.add(new ChannelValue(address, value, false)); + this.outputs.add(new ChannelAddressValue(address, value, false)); + return this; + } + + /** + * Adds an expected output value for a {@link ChannelAddress}. + * + * @param componentId the Component-ID + * @param channelId the Channel-ID in CamelCase + * @param value the value {@link Object} + * @return myself + */ + public TestCase output(String componentId, String channelId, Object value) { + return this.output(new ChannelAddress(componentId, channelId), value); + } + + /** + * Adds an expected output value for a {@link ChannelId} of the given Component. + * + * @param componentId the Component-ID + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase output(String componentId, ChannelId channelId, Object value) { + this.outputs.add(new ComponentChannelIdValue(componentId, channelId, value, true)); + return this; + } + + /** + * Adds an expected output value for a {@link ChannelId} of the + * system-under-test. + * + * @param channelId the {@link ChannelId} + * @param value the value {@link Object} + * @return myself + */ + public TestCase output(ChannelId channelId, Object value) { + this.outputs.add(new ChannelIdValue(channelId, value, false)); return this; } @@ -286,20 +445,14 @@ public void applyTimeLeap() { /** * Applies the values for input channels. * - * @param components Referenced components + * @param act the {@link AbstractComponentTest} * @throws OpenemsNamedException on error * @throws IllegalArgumentException on error */ - protected void applyInputs(Map components) + protected void applyInputs(AbstractComponentTest act) throws IllegalArgumentException, OpenemsNamedException { for (var input : this.inputs) { - var component = components.get(input.address.getComponentId()); - if (component == null) { - throw new IllegalArgumentException("On TestCase [" + this.description + "]: " // - + "the component [" + input.address.getComponentId() + "] " // - + "was not added to the OpenEMS Component test framework!"); - } - var channel = component.channel(input.address.getChannelId()); + final Channel channel = this.getChannel(act, input); // (Force) set the Read-Value do { @@ -317,38 +470,72 @@ protected void applyInputs(Map components) /** * Validates the output values. * - * @param components Referenced components + * @param act the {@link AbstractComponentTest} * @throws Exception on validation failure */ - protected void validateOutputs(Map components) throws Exception { + @SuppressWarnings("unchecked") + protected void validateOutputs(AbstractComponentTest act) throws Exception { for (var output : this.outputs) { - var expected = output.value; - var channel = components.get(output.address.getComponentId()).channel(output.address.getChannelId()); + final Channel channel = this.getChannel(act, output); + Object got; - if (channel instanceof WriteChannel) { - got = ((WriteChannel) channel).getNextWriteValueAndReset().orElse(null); + if (channel instanceof WriteChannel wc) { + got = wc.getNextWriteValueAndReset().orElse(null); } else { var value = channel.getNextValue(); got = value.orElse(null); } - // Try to parse an Enum if (channel.channelDoc() instanceof EnumDoc) { var enumDoc = (EnumDoc) channel.channelDoc(); var intGot = TypeUtils.getAsType(OpenemsType.INTEGER, got); got = enumDoc.getOption(intGot); } - if (!Objects.equals(expected, got)) { + if (!Objects.equals(output.value(), got)) { throw new Exception("On TestCase [" + this.description + "]: " // - + "expected [" + output.value + "] " // + + "expected [" + output.value() + "] " // + "got [" + got + "] " // - + "for Channel [" + output.address.toString() + "] " // + + "for Channel [" + output.toString() + "] " // + "on Inputs [" + this.inputs + "]"); } } } + + private OpenemsComponent getComponent(Map components, String componentId) { + var component = components.get(componentId); + if (component != null) { + return component; + } + throw new IllegalArgumentException("On TestCase [" + this.description + "]: " // + + "the component [" + componentId + "] " // + + "was not added to the OpenEMS Component test framework!"); + } + + private Channel getChannel(AbstractComponentTest act, ChannelValue cv) + throws IllegalArgumentException { + if (cv instanceof ChannelAddressValue cav) { + var component = this.getComponent(act.components, cav.address.getComponentId()); + return component.channel(cav.address.getChannelId()); + } + + if (cv instanceof ChannelIdValue civ) { + return act.sut.channel(civ.channelId); + } + + if (cv instanceof ComponentChannelIdValue cciv) { + var component = this.getComponent(act.components, cciv.componentId()); + return component.channel(cciv.channelId()); + } + + throw new IllegalArgumentException("Unhandled subtype of ChannelValue"); + } } + /** + * The {@link OpenemsComponent} to be tested. "sut" is for system-under-test. + */ + public final SUT sut; + /** * References added by {@link #addReference()}. */ @@ -359,11 +546,6 @@ protected void validateOutputs(Map components) throws */ private final Map components = new HashMap<>(); - /** - * The {@link OpenemsComponent} to be tested. "sut" is for system-under-test. - */ - private final SUT sut; - /** * Constructs the Component-Test and validates the implemented Channel-IDs. * @@ -670,7 +852,7 @@ public SELF next(TestCase testCase) throws Exception { for (Channel channel : this.getSut().channels()) { channel.nextProcessImage(); } - testCase.applyInputs(this.components); + testCase.applyInputs(this); this.onAfterProcessImage(); executeCallbacks(testCase.onAfterProcessImageCallbacks); this.handleEvent(EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE); @@ -691,7 +873,7 @@ public SELF next(TestCase testCase) throws Exception { this.onAfterWrite(); executeCallbacks(testCase.onAfterWriteCallbacks); this.handleEvent(EdgeEventConstants.TOPIC_CYCLE_AFTER_WRITE); - testCase.validateOutputs(this.components); + testCase.validateOutputs(this); return this.self(); } @@ -802,7 +984,5 @@ protected void onExecuteWrite() throws OpenemsNamedException { * @throws OpenemsNamedException on error */ protected void onAfterWrite() throws OpenemsNamedException { - } - } diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java index a3a43bbe07f..db5ebb96825 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java @@ -1,5 +1,7 @@ package io.openems.edge.common.test; +import static io.openems.edge.common.test.TestUtils.createDummyClock; + import java.io.IOException; import java.time.Clock; import java.util.ArrayList; @@ -47,7 +49,7 @@ public class DummyComponentManager implements ComponentManager, ComponentJsonApi private ConfigurationAdmin configurationAdmin = null; public DummyComponentManager() { - this(Clock.systemDefaultZone()); + this(createDummyClock()); } public DummyComponentManager(Clock clock) { diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java b/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java index d58c7dbf484..f8f0e2e7a10 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java @@ -2,7 +2,9 @@ import java.io.IOException; import java.net.ServerSocket; +import java.time.Instant; +import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.ChannelId; import io.openems.edge.common.component.OpenemsComponent; @@ -13,6 +15,15 @@ private TestUtils() { } + /** + * Creates a {@link TimeLeapClock} for 1st January 2000 00:00. + * + * @return the {@link TimeLeapClock} + */ + public static TimeLeapClock createDummyClock() { + return new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */); + } + /** * Finds and returns an open port. * diff --git a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java index 9d594615157..d99ef35e3bf 100644 --- a/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java +++ b/io.openems.edge.controller.api.backend/test/io/openems/edge/controller/api/backend/ControllerApiBackendImplTest.java @@ -17,8 +17,6 @@ public class ControllerApiBackendImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { @@ -44,7 +42,7 @@ public void test() throws Exception { .addReference("oem", new DummyOpenemsEdgeOem()) // .addComponent(new DummySum()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setUri("ws://localhost:" + port) // .setApikey("12345") // .setProxyType(Type.DIRECT) // diff --git a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java index 9bed397860b..64496f773cc 100644 --- a/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java +++ b/io.openems.edge.controller.api.common/src/io/openems/edge/controller/api/common/handler/QueryRequestHandler.java @@ -1,11 +1,17 @@ package io.openems.edge.controller.api.common.handler; +import java.io.IOException; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.TreeSet; + import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesDataRequest; import io.openems.common.jsonrpc.request.QueryHistoricTimeseriesEnergyPerPeriodRequest; @@ -14,6 +20,13 @@ import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesDataResponse; import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyPerPeriodResponse; import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesEnergyResponse; +import io.openems.common.jsonrpc.response.QueryHistoricTimeseriesExportXlsxResponse; +import io.openems.common.session.Language; +import io.openems.common.timedata.Resolution; +import io.openems.common.timedata.XlsxExportDetailData.XlsxExportDataEntry.HistoricTimedataSaveType; +import io.openems.common.timedata.XlsxExportUtil; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.jsonapi.EdgeKeys; import io.openems.edge.common.jsonapi.JsonApi; import io.openems.edge.common.jsonapi.JsonApiBuilder; @@ -29,6 +42,9 @@ public class QueryRequestHandler implements JsonApi { ) private volatile Timedata timedata; + @Reference + private ComponentManager componentManager; + @Override public void buildJsonApiRoutes(JsonApiBuilder builder) { builder.handleRequest(QueryHistoricTimeseriesDataRequest.METHOD, call -> { @@ -54,14 +70,39 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) { request.getFromDate(), request.getToDate(), request.getChannels(), request.getResolution()); return new QueryHistoricTimeseriesEnergyPerPeriodResponse(request.getId(), data); }); - builder.handleRequest(QueryHistoricTimeseriesExportXlxsRequest.METHOD, call -> { final var request = QueryHistoricTimeseriesExportXlxsRequest.from(call.getRequest()); - return this.getTimedata().handleQueryHistoricTimeseriesExportXlxsRequest(null /* ignore Edge-ID */, request, + return this.handleQueryHistoricTimeseriesExportXlxsRequest(request, call.get(EdgeKeys.USER_KEY).getLanguage()); }); } + private QueryHistoricTimeseriesExportXlsxResponse handleQueryHistoricTimeseriesExportXlxsRequest( + QueryHistoricTimeseriesExportXlxsRequest request, Language language) throws OpenemsNamedException { + final var powerChannels = new TreeSet(QueryHistoricTimeseriesExportXlsxResponse.POWER_CHANNELS); + final var energyChannels = new TreeSet( + QueryHistoricTimeseriesExportXlsxResponse.ENERGY_CHANNELS); + final var detailData = XlsxExportUtil.getDetailData(this.componentManager.getEdgeConfig()); + final var channelsByType = detailData.getChannelsBySaveType(); + powerChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.POWER, Collections.emptyList())); + energyChannels.addAll(channelsByType.getOrDefault(HistoricTimedataSaveType.ENERGY, Collections.emptyList())); + var powerData = this.timedata.queryHistoricData(null, request.getFromDate(), request.getToDate(), + powerChannels, new Resolution(15, ChronoUnit.MINUTES)); + + var energyData = this.timedata.queryHistoricEnergy(null, request.getFromDate(), request.getToDate(), + energyChannels); + if (powerData == null || energyData == null) { + return null; + } + try { + return new QueryHistoricTimeseriesExportXlsxResponse(request.getId(), null, request.getFromDate(), + request.getToDate(), powerData, energyData, language, detailData); + + } catch (IOException e) { + throw new OpenemsException("QueryHistoricTimeseriesExportXlxsRequest failed: " + e.getMessage()); + } + } + private final Timedata getTimedata() throws OpenemsException { final var currentTimedata = this.timedata; if (currentTimedata == null) { diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java index a39ef6c3555..6248a9b4e41 100644 --- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java +++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readonly/ControllerApiModbusTcpReadOnlyImplTest.java @@ -1,28 +1,27 @@ package io.openems.edge.controller.api.modbus.readonly; +import static io.openems.edge.controller.api.modbus.AbstractModbusTcpApi.DEFAULT_PORT; + import org.junit.Test; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi; import io.openems.edge.controller.test.ControllerTest; public class ControllerApiModbusTcpReadOnlyImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerApiModbusTcpReadOnlyImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setEnabled(false) // do not actually start server .setComponentIds() // .setMaxConcurrentConnections(5) // - .setPort(AbstractModbusTcpApi.DEFAULT_PORT) // + .setPort(DEFAULT_PORT) // .build()) // .next(new TestCase()) // - ; + .deactivate(); } } diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java index e3b97b5cb79..f87124a5295 100644 --- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java +++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/ControllerApiModbusTcpReadWriteImplTest.java @@ -1,7 +1,9 @@ package io.openems.edge.controller.api.modbus.readwrite; +import static io.openems.edge.controller.api.modbus.AbstractModbusTcpApi.DEFAULT_PORT; import static io.openems.edge.controller.api.modbus.readwrite.ControllerApiModbusTcpReadWriteImpl.getChannelNameCamel; import static io.openems.edge.controller.api.modbus.readwrite.ControllerApiModbusTcpReadWriteImpl.getChannelNameUpper; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -11,27 +13,24 @@ import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.common.test.DummyCycle; -import io.openems.edge.controller.api.modbus.AbstractModbusTcpApi; import io.openems.edge.controller.test.ControllerTest; -import io.openems.edge.ess.api.ManagedSymmetricEss; public class ControllerApiModbusTcpReadWriteImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerApiModbusTcpReadWriteImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setEnabled(false) // do not actually start server .setComponentIds() // .setMaxConcurrentConnections(5) // - .setPort(AbstractModbusTcpApi.DEFAULT_PORT) // + .setPort(DEFAULT_PORT) // .setApiTimeout(60) // .build()) // .next(new TestCase()) // + .deactivate(); ; } @@ -54,17 +53,13 @@ public void testAddFalseComponents() throws Exception { @Test public void testGetChannelNameUpper() { - assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", - getChannelNameUpper("ess0", ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)); - assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", - getChannelNameUpper("Ess0", ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)); + assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", getChannelNameUpper("ess0", SET_ACTIVE_POWER_EQUALS)); + assertEquals("ESS0_SET_ACTIVE_POWER_EQUALS", getChannelNameUpper("Ess0", SET_ACTIVE_POWER_EQUALS)); } @Test public void testGetChannelNameCamel() { - assertEquals("Ess0SetActivePowerEquals", - getChannelNameCamel("ess0", ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)); - assertEquals("Ess0SetActivePowerEquals", - getChannelNameCamel("Ess0", ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS)); + assertEquals("Ess0SetActivePowerEquals", getChannelNameCamel("ess0", SET_ACTIVE_POWER_EQUALS)); + assertEquals("Ess0SetActivePowerEquals", getChannelNameCamel("Ess0", SET_ACTIVE_POWER_EQUALS)); } } diff --git a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java index 5c06b078554..dfbd3ea52b8 100644 --- a/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java +++ b/io.openems.edge.controller.api.modbus/test/io/openems/edge/controller/api/modbus/readwrite/MyConfig.java @@ -1,12 +1,7 @@ package io.openems.edge.controller.api.modbus.readwrite; -import java.nio.channels.Channels; - import io.openems.common.test.AbstractComponentConfig; -import io.openems.common.types.EdgeConfig.Component.Channel; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.common.channel.ChannelId; -import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java b/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java index 10981834b04..89486aa7d0b 100644 --- a/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java +++ b/io.openems.edge.controller.api.mqtt/test/io/openems/edge/controller/api/mqtt/ControllerApiMqttImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.controller.api.mqtt; +import static io.openems.common.channel.PersistencePriority.VERY_LOW; import static io.openems.edge.controller.api.mqtt.ControllerApiMqttImpl.createTopicPrefix; import static org.junit.Assert.assertEquals; @@ -8,7 +9,6 @@ import org.junit.Test; -import io.openems.common.channel.PersistencePriority; import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.ComponentTest; @@ -16,8 +16,6 @@ public class ControllerApiMqttImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800L) /* starts at 1. January 2020 00:00:00 */, @@ -26,13 +24,13 @@ public void test() throws Exception { .addReference("componentManager", new DummyComponentManager(clock)) // .addComponent(new DummySum()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setClientId("edge0") // .setTopicPrefix("") // .setUsername("guest") // .setPassword("guest") // .setUri("ws://localhost:1883") // - .setPersistencePriority(PersistencePriority.VERY_LOW) // + .setPersistencePriority(VERY_LOW) // .setDebugMode(true) // .setCertPem("") // .setPrivateKeyPem("") // diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java index e7bc5a974ef..04c9d124dd6 100644 --- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java +++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readonly/ControllerApiRestReadOnlyImplTest.java @@ -12,8 +12,6 @@ public class ControllerApiRestReadOnlyImplTest { - private static final String CTRL_ID = "ctrlApiRest0"; - @Test public void test() throws OpenemsException, Exception { final var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces(); @@ -23,11 +21,12 @@ public void test() throws OpenemsException, Exception { .addReference("userService", new DummyUserService()) // .addReference("restHandlerFactory", new DummyJsonRpcRestHandlerFactory(JsonRpcRestHandler::new)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrlApiRest0") // .setEnabled(false) // do not actually start server .setConnectionlimit(5) // .setDebugMode(false) // .setPort(port) // - .build()); + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java index 6baf712a911..0dd9165587a 100644 --- a/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java +++ b/io.openems.edge.controller.api.rest/test/io/openems/edge/controller/api/rest/readwrite/ControllerApiRestReadWriteImplTest.java @@ -1,9 +1,11 @@ package io.openems.edge.controller.api.rest.readwrite; +import static io.openems.common.utils.JsonUtils.getAsJsonObject; import static io.openems.edge.common.test.DummyUser.DUMMY_ADMIN; import static io.openems.edge.common.test.DummyUser.DUMMY_GUEST; import static io.openems.edge.common.test.DummyUser.DUMMY_INSTALLER; import static io.openems.edge.common.test.DummyUser.DUMMY_OWNER; +import static io.openems.edge.controller.api.rest.readwrite.ControllerApiRestReadWrite.ChannelId.API_WORKER_LOG; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -27,7 +29,6 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.jsonrpc.request.GetEdgeConfigRequest; -import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.channel.Doc; @@ -51,9 +52,6 @@ public class ControllerApiRestReadWriteImplTest { - private static final String CTRL_ID = "ctrlApiRest0"; - private static final String DUMMY_ID = "dummy0"; - @Test public void test() throws OpenemsException, Exception { final var port = TestUtils.findRandomOpenPortOnAllLocalInterfaces(); @@ -81,10 +79,10 @@ public void test() throws OpenemsException, Exception { .addReference("userService", new DummyUserService(// DUMMY_GUEST, DUMMY_OWNER, DUMMY_INSTALLER, DUMMY_ADMIN)) // .addReference("restHandlerFactory", factory) // - .addComponent(new DummyComponent(DUMMY_ID) // + .addComponent(new DummyComponent("dummy0") // .withReadChannel(1234)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrlApiRest0") // .setApiTimeout(60) // .setConnectionlimit(5) // .setDebugMode(false) // @@ -98,7 +96,7 @@ public void test() throws OpenemsException, Exception { var channelGet = sendGetRequest(port, DUMMY_GUEST.password, "/rest/channel/dummy0/ReadChannel"); assertEquals(JsonUtils.buildJsonObject() // .addProperty("address", "dummy0/ReadChannel") // - .addProperty("type", "INTEGER") // + .addProperty("type", "INTEGER") // s .addProperty("accessMode", "RO") // .addProperty("text", "This is a Read-Channel") // .addProperty("unit", "W") // @@ -113,8 +111,8 @@ public void test() throws OpenemsException, Exception { assertEquals(new JsonObject(), channelPost); test // .next(new TestCase() // - .output(new ChannelAddress("dummy0", "WriteChannel"), 4321) // - .output(new ChannelAddress(CTRL_ID, "ApiWorkerLog"), "dummy0/WriteChannel:4321")); + .output("dummy0", DummyComponent.ChannelId.WRITE_CHANNEL, 4321) // + .output(API_WORKER_LOG, "dummy0/WriteChannel:4321")); // POST fails as GUEST try { @@ -132,7 +130,7 @@ public void test() throws OpenemsException, Exception { // POST successful as OWNER var request = new GetEdgeConfigRequest().toJsonObject(); JsonrpcResponseSuccess.from(// - JsonUtils.getAsJsonObject(// + getAsJsonObject(// sendPostRequest(port, DUMMY_OWNER.password, "/jsonrpc", request))); // POST fails as GUEST diff --git a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java index b37f1fb4929..f2321dcc2cf 100644 --- a/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java +++ b/io.openems.edge.controller.api.websocket/test/io/openems/edge/controller/api/websocket/ControllerApiWebsocketImplTest.java @@ -1,5 +1,7 @@ package io.openems.edge.controller.api.websocket; +import static io.openems.edge.controller.api.websocket.ControllerApiWebsocket.DEFAULT_PORT; + import org.junit.Test; import io.openems.edge.common.test.AbstractComponentTest.TestCase; @@ -8,20 +10,18 @@ public class ControllerApiWebsocketImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerApiWebsocketImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("onRequestFactory", new DummyOnRequestFactory()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setApiTimeout(60) // - .setPort(ControllerApiWebsocket.DEFAULT_PORT) // + .setPort(DEFAULT_PORT) // .build()) // .next(new TestCase()) // - ; + .deactivate(); } } diff --git a/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java b/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java index 8f59d84837e..8f8fee699d0 100644 --- a/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java +++ b/io.openems.edge.controller.asymmetric.balancingcosphi/test/io/openems/edge/controller/asymmetric/balancingcosphi/ControllerAsymmetricBalancingCosPhiImplTest.java @@ -9,24 +9,20 @@ public class ControllerAsymmetricBalancingCosPhiImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerAsymmetricBalancingCosPhiImpl()) // - .addComponent(new DummyManagedAsymmetricEss(ESS_ID)) // - .addComponent(new DummyElectricityMeter(METER_ID)) // + .addComponent(new DummyManagedAsymmetricEss("ess0")) // + .addComponent(new DummyElectricityMeter("meter0")) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setCosPhi(0.9) // .setDirection(CosPhiDirection.CAPACITIVE) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java b/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java index 53a2d3a18c1..f0c26cb202d 100644 --- a/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java +++ b/io.openems.edge.controller.asymmetric.fixreactivepower/test/io/openems/edge/controller/asymmetric/fixreactivepower/ControllerAsymmetricFixReactivePowerImplTest.java @@ -9,23 +9,20 @@ public class ControllerAsymmetricFixReactivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerAsymmetricFixReactivePowerImpl()) // - .addComponent(new DummyManagedAsymmetricEss(ESS_ID)) // + .addComponent(new DummyManagedAsymmetricEss("ess0")) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setPowerL1(0) // .setPowerL2(0) // .setPowerL3(0) // .build()) // - .next(new TestCase()); // - ; + .next(new TestCase()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java b/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java index e8090feb15a..7d59fb27424 100644 --- a/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java +++ b/io.openems.edge.controller.asymmetric.peakshaving/test/io/openems/edge/controller/asymmetric/peakshaving/ControllerAsymmetricPeakShavingImplTest.java @@ -1,8 +1,13 @@ package io.openems.edge.controller.asymmetric.peakshaving; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -12,128 +17,117 @@ public class ControllerAsymmetricPeakShavingImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String METER_ID = "meter0"; - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - private static final ChannelAddress GRID_ACTIVE_POWER_L1 = new ChannelAddress(METER_ID, "ActivePowerL1"); - private static final ChannelAddress GRID_ACTIVE_POWER_L2 = new ChannelAddress(METER_ID, "ActivePowerL2"); - private static final ChannelAddress GRID_ACTIVE_POWER_L3 = new ChannelAddress(METER_ID, "ActivePowerL3"); - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - @Test public void symmetricMeterTest() throws Exception { new ControllerTest(new ControllerAsymmetricPeakShavingImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyElectricityMeter(METER_ID)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyElectricityMeter("meter0")) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .setPower(new DummyPower(0.3, 0.3, 0.1))) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setMeterId("meter0") // + .setEssId("ess0") // .setPeakShavingPower(33333) // .setRechargePower(16666) // .build()) .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(GRID_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) // + .input("ess0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(GRID_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 12001)) // + .input("ess0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 12001)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 3793) // - .input(GRID_ACTIVE_POWER, 120000 - 3793) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 16484)) // + .input("ess0", ACTIVE_POWER, 3793) // + .input("meter0", ACTIVE_POWER, 120000 - 3793) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 16484)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 8981) // - .input(GRID_ACTIVE_POWER, 120000 - 8981) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19650)) // + .input("ess0", ACTIVE_POWER, 8981) // + .input("meter0", ACTIVE_POWER, 120000 - 8981) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19650)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 13723) // - .input(GRID_ACTIVE_POWER, 120000 - 13723) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21578)) // + .input("ess0", ACTIVE_POWER, 13723) // + .input("meter0", ACTIVE_POWER, 120000 - 13723) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21578)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 17469) // - .input(GRID_ACTIVE_POWER, 120000 - 17469) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22437)) // + .input("ess0", ACTIVE_POWER, 17469) // + .input("meter0", ACTIVE_POWER, 120000 - 17469) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22437)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20066) // - .input(GRID_ACTIVE_POWER, 120000 - 20066) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22533)) // + .input("ess0", ACTIVE_POWER, 20066) // + .input("meter0", ACTIVE_POWER, 120000 - 20066) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22533)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21564) // - .input(GRID_ACTIVE_POWER, 120000 - 21564) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22174)) // + .input("ess0", ACTIVE_POWER, 21564) // + .input("meter0", ACTIVE_POWER, 120000 - 21564) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22174)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 22175) // - .input(GRID_ACTIVE_POWER, 120000 - 22175) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21610)) // + .input("ess0", ACTIVE_POWER, 22175) // + .input("meter0", ACTIVE_POWER, 120000 - 22175) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21610)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 22173) // - .input(GRID_ACTIVE_POWER, 120000 - 22173) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21020)) // + .input("ess0", ACTIVE_POWER, 22173) // + .input("meter0", ACTIVE_POWER, 120000 - 22173) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21020)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21816) // - .input(GRID_ACTIVE_POWER, 120000 - 21816) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 20511)) // + .input("ess0", ACTIVE_POWER, 21816) // + .input("meter0", ACTIVE_POWER, 120000 - 21816) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 20511)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21311) // - .input(GRID_ACTIVE_POWER, 120000 - 21311) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 20133)) // + .input("ess0", ACTIVE_POWER, 21311) // + .input("meter0", ACTIVE_POWER, 120000 - 21311) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 20133)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20803) // - .input(GRID_ACTIVE_POWER, 120000 - 20803) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19893)) // + .input("ess0", ACTIVE_POWER, 20803) // + .input("meter0", ACTIVE_POWER, 120000 - 20803) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19893)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20377) // - .input(GRID_ACTIVE_POWER, 120000 - 20377) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19772)); // + .input("ess0", ACTIVE_POWER, 20377) // + .input("meter0", ACTIVE_POWER, 120000 - 20377) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19772)) // + .deactivate(); } @Test public void asymmetricMeterTest() throws Exception { new ControllerTest(new ControllerAsymmetricPeakShavingImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyElectricityMeter(METER_ID)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyElectricityMeter("meter0")) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .setPower(new DummyPower(0.3, 0.3, 0.1))) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setMeterId("meter0") // + .setEssId("ess0") // .setPeakShavingPower(33333) // .setRechargePower(16666) // .build()) .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(GRID_ACTIVE_POWER_L1, 20000) // - .input(GRID_ACTIVE_POWER_L2, 40000) // - .input(GRID_ACTIVE_POWER_L3, 10000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) // - .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(GRID_ACTIVE_POWER_L1, 20000) // - .input(GRID_ACTIVE_POWER_L2, 40000) // - .input(GRID_ACTIVE_POWER_L3, 10000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 12001)) // - .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 3793) // - .input(GRID_ACTIVE_POWER_L1, 20000 - 3793 / 3) // - .input(GRID_ACTIVE_POWER_L2, 40000 - 3793 / 3) // - .input(GRID_ACTIVE_POWER_L3, 10000 - 3793 / 3) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 16484)) // - .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 8981) // - .input(GRID_ACTIVE_POWER_L1, 20000 - 8981 / 3) // - .input(GRID_ACTIVE_POWER_L2, 40000 - 8981 / 3) // - .input(GRID_ACTIVE_POWER_L3, 10000 - 8981 / 3) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19651)); // + .input("ess0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 20000) // + .input("meter0", ACTIVE_POWER_L2, 40000) // + .input("meter0", ACTIVE_POWER_L3, 10000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) // + .next(new TestCase() // + .input("ess0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 20000) // + .input("meter0", ACTIVE_POWER_L2, 40000) // + .input("meter0", ACTIVE_POWER_L3, 10000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 12001)) // + .next(new TestCase() // + .input("ess0", ACTIVE_POWER, 3793) // + .input("meter0", ACTIVE_POWER_L1, 20000 - 3793 / 3) // + .input("meter0", ACTIVE_POWER_L2, 40000 - 3793 / 3) // + .input("meter0", ACTIVE_POWER_L3, 10000 - 3793 / 3) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 16484)) // + .next(new TestCase() // + .input("ess0", ACTIVE_POWER, 8981) // + .input("meter0", ACTIVE_POWER_L1, 20000 - 8981 / 3) // + .input("meter0", ACTIVE_POWER_L2, 40000 - 8981 / 3) // + .input("meter0", ACTIVE_POWER_L3, 10000 - 8981 / 3) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19651)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java b/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java index f18c8669b3a..882d083baed 100644 --- a/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java +++ b/io.openems.edge.controller.asymmetric.phaserectification/test/io/openems/edge/controller/asymmetric/phaserectification/ControllerAsymmetricPhaseRectificationImplTest.java @@ -9,22 +9,18 @@ public class ControllerAsymmetricPhaseRectificationImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String METER_ID = "meter0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerAsymmetricPhaseRectificationImpl()) // - .addComponent(new DummyManagedAsymmetricEss(ESS_ID)) // - .addComponent(new DummyElectricityMeter(METER_ID)) // + .addComponent(new DummyManagedAsymmetricEss("ess0")) // + .addComponent(new DummyElectricityMeter("meter0")) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // - .build()); // - ; + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java index b5418c94413..5c2501c8981 100644 --- a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java +++ b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/ControllerChannelThresholdImplTest.java @@ -8,7 +8,6 @@ public class ControllerChannelThresholdImplTest { - private static final String CTRL_ID = "ctrl0"; private static final ChannelAddress IO0_INPUT = new ChannelAddress("io0", "Input0"); private static final ChannelAddress IO0_OUTPUT = new ChannelAddress("io0", "Output0"); @@ -17,12 +16,12 @@ public void test() throws Exception { new ControllerTest(new ControllerChannelThresholdImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setInputChannelAddress(IO0_INPUT.toString()) // - .setOutputChannelAddress(IO0_OUTPUT.toString()) // + .setId("ctrl0") // + .setInputChannelAddress(IO0_INPUT) // + .setOutputChannelAddress(IO0_OUTPUT) // .setLowThreshold(40) // .setHighThreshold(80) // - .build()); // + .build()) // + .deactivate(); } - } diff --git a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java index ab9677f984e..7bb1199c7bc 100644 --- a/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java +++ b/io.openems.edge.controller.channelthreshold/test/io/openems/edge/controller/channelthreshold/MyConfig.java @@ -1,6 +1,7 @@ package io.openems.edge.controller.channelthreshold; import io.openems.common.test.AbstractComponentConfig; +import io.openems.common.types.ChannelAddress; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { @@ -22,13 +23,13 @@ public Builder setId(String id) { return this; } - public Builder setInputChannelAddress(String inputChannelAddress) { - this.inputChannelAddress = inputChannelAddress; + public Builder setInputChannelAddress(ChannelAddress inputChannelAddress) { + this.inputChannelAddress = inputChannelAddress.toString(); return this; } - public Builder setOutputChannelAddress(String outputChannelAddress) { - this.outputChannelAddress = outputChannelAddress; + public Builder setOutputChannelAddress(ChannelAddress outputChannelAddress) { + this.outputChannelAddress = outputChannelAddress.toString(); return this; } diff --git a/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java b/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java index 8937f49f4ce..a469e21f2bd 100644 --- a/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java +++ b/io.openems.edge.controller.chp.soc/test/io/openems/edge/controller/chp/soc/ControllerChpSocImplTest.java @@ -1,8 +1,10 @@ package io.openems.edge.controller.chp.soc; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -11,56 +13,49 @@ public class ControllerChpSocImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - @Test public void test() throws Exception { new ControllerTest(new ControllerChpSocImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyManagedSymmetricEss("ess0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setInputChannelAddress(ESS_SOC.toString()) // - .setOutputChannelAddress(IO_OUTPUT0.toString()) // + .setId("ctrl0") // + .setInputChannelAddress("ess0/Soc") // + .setOutputChannelAddress("io0/InputOutput0") // .setLowThreshold(15) // .setHighThreshold(85) // .setMode(Mode.AUTOMATIC) // .setInvert(false) // .build()) .next(new TestCase() // - .input(ESS_SOC, 14) // - .output(IO_OUTPUT0, true)) // + .input("ess0", SOC, 14) // + .output("io0", INPUT_OUTPUT0, true)) // .next(new TestCase() // - .input(ESS_SOC, 50) // - .output(IO_OUTPUT0, null)) // + .input("ess0", SOC, 50) // + .output("io0", INPUT_OUTPUT0, null)) // .next(new TestCase() // - .input(ESS_SOC, 90) // - .output(IO_OUTPUT0, false)) // + .input("ess0", SOC, 90) // + .output("io0", INPUT_OUTPUT0, false)) // .next(new TestCase() // - .input(ESS_SOC, 50) // - .output(IO_OUTPUT0, null)) // + .input("ess0", SOC, 50) // + .output("io0", INPUT_OUTPUT0, null)) // .next(new TestCase() // - .input(ESS_SOC, 15) // - .output(IO_OUTPUT0, true)) // + .input("ess0", SOC, 15) // + .output("io0", INPUT_OUTPUT0, true)) // .next(new TestCase() // - .input(ESS_SOC, 85) // - .output(IO_OUTPUT0, false)) // + .input("ess0", SOC, 85) // + .output("io0", INPUT_OUTPUT0, false)) // .next(new TestCase() // - .input(ESS_SOC, 86) // - .output(IO_OUTPUT0, false)) // + .input("ess0", SOC, 86) // + .output("io0", INPUT_OUTPUT0, false)) // .next(new TestCase() // - .input(ESS_SOC, 14) // - .output(IO_OUTPUT0, true)) // + .input("ess0", SOC, 14) // + .output("io0", INPUT_OUTPUT0, true)) // .next(new TestCase() // - .input(ESS_SOC, 45) // - .output(IO_OUTPUT0, null)); + .input("ess0", SOC, 45) // + .output("io0", INPUT_OUTPUT0, null)) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java b/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java index ed138710c30..0ae17386d3d 100644 --- a/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java +++ b/io.openems.edge.controller.debug.detailedlog/test/io/openems/edge/controller/debug/detailedlog/ControllerDebugDetailedLogImplTest.java @@ -7,16 +7,15 @@ public class ControllerDebugDetailedLogImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerDebugDetailedLogImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setComponentIds() // - .build()); // + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java index 0f0d97848c6..beabd1ba311 100644 --- a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java +++ b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/ControllerDebugLogImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.controller.debuglog; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC; import static org.junit.Assert.assertEquals; import java.util.ArrayList; @@ -7,7 +8,6 @@ import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; @@ -16,20 +16,6 @@ public class ControllerDebugLogImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String DUMMY0_ID = "dummy0"; - private static final String DUMMY1_ID = "dummy1"; - private static final String DUMMY1_ALIAS = "This is Dummy1"; - private static final String DUMMY2_ID = "dummy2"; - private static final String DUMMY2_ALIAS = DUMMY2_ID; - private static final String DUMMY10_ID = "dummy10"; - - private static final String ANY_DUMMY = "dummy*"; - - private static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc"); - private static final ChannelAddress SUM_FOO_BAR = new ChannelAddress("_sum", "FooBar"); - @Test public void test() throws Exception { List components = new ArrayList<>(); @@ -39,25 +25,26 @@ public String debugLog() { return "foo:bar"; } }); - components.add(new DummyController(DUMMY0_ID) { + components.add(new DummyController("dummy0") { @Override public String debugLog() { return "abc:xyz"; } }); - components.add(new DummyController(DUMMY1_ID, DUMMY1_ALIAS) { + components.add(new DummyController("dummy1", "This is Dummy1") { + @Override public String debugLog() { return "def:uvw"; } }); - components.add(new DummyController(DUMMY2_ID, DUMMY2_ALIAS) { + components.add(new DummyController("dummy2", "dummy2") { @Override public String debugLog() { return "ghi:rst"; } }); - components.add(new DummyController(DUMMY10_ID) { + components.add(new DummyController("dummy10") { @Override public String debugLog() { return "jkl:opq"; @@ -68,19 +55,14 @@ public String debugLog() { new ControllerTest(sut) // .addReference("components", components) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setShowAlias(true) // .setCondensedOutput(true) // - .setAdditionalChannels(new String[] { // - SUM_ESS_SOC.toString(), // - SUM_FOO_BAR.toString() // - }) // - .setIgnoreComponents(new String[] { // - DUMMY0_ID // - }) // + .setAdditionalChannels("_sum/EssSoc", "_sum/FooBar") // + .setIgnoreComponents("dummy0") // .build()) // .next(new TestCase() // - .input(SUM_ESS_SOC, 50)); + .input(ESS_SOC, 50)); assertEquals( "_sum[Core.Sum|foo:bar|EssSoc:50 %|FooBar:CHANNEL_IS_NOT_DEFINED] dummy1[This is Dummy1|def:uvw] dummy2[ghi:rst] dummy10[jkl:opq]", @@ -97,13 +79,14 @@ public String debugLog() { return "foo:bar"; } }); - components.add(new DummyController(DUMMY0_ID) { + components.add(new DummyController("dummy0") { @Override public String debugLog() { return "abc:xyz"; } }); - components.add(new DummyController(DUMMY1_ID) { + components.add(new DummyController("dummy1") { + @Override public String debugLog() { return "def:uvw"; @@ -116,17 +99,14 @@ public String debugLog() { .addComponent(components.get(0)) // .addComponent(components.get(1)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setCondensedOutput(true) // - .setAdditionalChannels(new String[] { // - SUM_ESS_SOC.toString() // - }) // - .setIgnoreComponents(new String[] { // - ANY_DUMMY // - }) // + .setAdditionalChannels("_sum/EssSoc") // + .setIgnoreComponents("dummy*") // .build()) // .next(new TestCase() // - .input(SUM_ESS_SOC, 50)); + .input(ESS_SOC, 50)) // + .deactivate(); assertEquals("_sum[foo:bar|EssSoc:50 %]", sut.getLogMessage()); diff --git a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java index f23d1d89e1a..b8edc931126 100644 --- a/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java +++ b/io.openems.edge.controller.debug.log/test/io/openems/edge/controller/debuglog/MyConfig.java @@ -25,12 +25,12 @@ public Builder setShowAlias(boolean showAlias) { return this; } - public Builder setAdditionalChannels(String[] additionalChannels) { + public Builder setAdditionalChannels(String... additionalChannels) { this.additionalChannels = additionalChannels; return this; } - public Builder setIgnoreComponents(String[] ignoreComponents) { + public Builder setIgnoreComponents(String... ignoreComponents) { this.ignoreComponents = ignoreComponents; return this; } diff --git a/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java b/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java index 1a25df66607..7208178a501 100644 --- a/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java +++ b/io.openems.edge.controller.ess.acisland/test/io/openems/edge/controller/ess/acisland/ControllerEssAcIslandImplTest.java @@ -2,35 +2,26 @@ import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; public class ControllerEssAcIslandImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_OUTPUT0 = new ChannelAddress(IO_ID, "Output0"); - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "Output1"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssAcIslandImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setInvertOffGridOutput(false) // .setInvertOnGridOutput(false) // .setMaxSoc(90) // .setMinSoc(4) // - .setOffGridOutputChannelAddress(IO_OUTPUT0.toString()) // - .setOnGridOutputChannelAddress(IO_OUTPUT1.toString()) // + .setOffGridOutputChannelAddress("io0/Output0") // + .setOnGridOutputChannelAddress("io0/Output1") // .build()) // - ; + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java b/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java index 26f3eb8d98f..1c1cdea75dc 100644 --- a/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java +++ b/io.openems.edge.controller.ess.activepowervoltagecharacteristic/test/io/openems/edge/controller/ess/activepowervoltagecharacteristic/CharacteristicImplTest.java @@ -1,5 +1,9 @@ package io.openems.edge.controller.ess.activepowervoltagecharacteristic; +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; + import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -8,7 +12,6 @@ import io.openems.common.test.TimeLeapClock; import io.openems.common.types.ChannelAddress; -import io.openems.common.utils.JsonUtils; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -18,11 +21,7 @@ public class CharacteristicImplTest { - private static final String CTRL_ID = "ctrlActivePowerVoltageCharacteristic0"; - private static final String ESS_ID = "ess1"; - private static final String METER_ID = "meter0"; - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "SetActivePowerEquals"); - private static final ChannelAddress METER_VOLTAGE = new ChannelAddress(METER_ID, "Voltage"); + private static final ChannelAddress METER_VOLTAGE = new ChannelAddress("meter0", "Voltage"); @Test public void test() throws Exception { @@ -30,36 +29,36 @@ public void test() throws Exception { new ControllerTest(new ControllerEssActivePowerVoltageCharacteristicImpl())// .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // + .addReference("ess", new DummyManagedSymmetricEss("ess1")) // .activate(MyConfig.create()// - .setId(CTRL_ID)// - .setEssId(ESS_ID)// - .setMeterId(METER_ID)// + .setId("ctrlActivePowerVoltageCharacteristic0")// + .setEssId("ess1")// + .setMeterId("meter0")// .setNominalVoltage(240)// .setWaitForHysteresis(5)// - .setPowerVoltConfig(JsonUtils.buildJsonArray()// - .add(JsonUtils.buildJsonObject()// + .setPowerVoltConfig(buildJsonArray()// + .add(buildJsonObject()// .addProperty("voltageRatio", 0.95) // .addProperty("power", 4000) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 0.98) // .addProperty("power", 1000) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 0.98001) // .addProperty("power", 0) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 1.02999) // .addProperty("power", 0) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 1.03) // .addProperty("power", -1000) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 1.05) // .addProperty("power", -4000) // .build() // @@ -67,52 +66,52 @@ public void test() throws Exception { ).build()) // .next(new TestCase("First Input") // .input(METER_VOLTAGE, 250_000) // [mV] - .output(ESS_ACTIVE_POWER, -2749)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, -2749)) // .next(new TestCase("Second Input, \"Power: -1500 \"") // .timeleap(clock, 5, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 248_000) // [mV] - .output(ESS_ACTIVE_POWER, -1499))// + .output("ess1", SET_ACTIVE_POWER_EQUALS, -1499))// .next(new TestCase() // .input(METER_VOLTAGE, 240_200) // [mV] - .output(ESS_ACTIVE_POWER, null)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) // .next(new TestCase("Third Input, \"Power: 0 \"") // .timeleap(clock, 5, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 238_100) // [mV] - .output(ESS_ACTIVE_POWER, 0)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // .input(METER_VOLTAGE, 240_000) // [mV] - .output(ESS_ACTIVE_POWER, null)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) // .next(new TestCase() // .input(METER_VOLTAGE, 238_800)// [mV] - .output(ESS_ACTIVE_POWER, null)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) // .next(new TestCase("Fourth Input, \"Power: 0 \"") // .timeleap(clock, 5, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 235_200) // [mV] - .output(ESS_ACTIVE_POWER, 998)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, 998)) // .next(new TestCase() // .timeleap(clock, 2, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 235_600) // [mV] - .output(ESS_ACTIVE_POWER, null)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) // .next(new TestCase() // .timeleap(clock, 2, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 234_000) // [mV] - .output(ESS_ACTIVE_POWER, null)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, null)) // .next(new TestCase("Fifth Input, \"Power: 1625 \"") // .timeleap(clock, 1, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 233_700) // [mV] - .output(ESS_ACTIVE_POWER, 1625)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, 1625)) // .next(new TestCase("Fourth Input, \"Power: 0 \"") // .timeleap(clock, 5, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 225_000) // [mV] - .output(ESS_ACTIVE_POWER, 4000)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, 4000)) // .next(new TestCase("Smaller then Min Key, \"Power: 0 \"") // .timeleap(clock, 5, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 255_000) // [mV] - .output(ESS_ACTIVE_POWER, -4000)) // + .output("ess1", SET_ACTIVE_POWER_EQUALS, -4000)) // .next(new TestCase("Bigger than Max Key, \"Power: 0 \"") // .timeleap(clock, 5, ChronoUnit.SECONDS) // .input(METER_VOLTAGE, 270_000) // [mV] - .output(ESS_ACTIVE_POWER, -4000)) // - ; + .output("ess1", SET_ACTIVE_POWER_EQUALS, -4000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java b/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java index e6a320f6733..4cab4cf6d5a 100644 --- a/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java +++ b/io.openems.edge.controller.ess.balancing/test/io/openems/edge/controller/ess/balancing/BalancingImplTest.java @@ -1,96 +1,90 @@ package io.openems.edge.controller.ess.balancing; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyPower; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class BalancingImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssBalancingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .setPower(new DummyPower(0.3, 0.3, 0.1))) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setTargetGridSetpoint(0) // .build()) .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 20000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 20000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 12000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 12000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 3793) // - .input(METER_ACTIVE_POWER, 20000 - 3793) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 16483)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3793) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 3793) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 16483)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 8981) // - .input(METER_ACTIVE_POWER, 20000 - 8981) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19649)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 8981) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 8981) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19649)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 13723) // - .input(METER_ACTIVE_POWER, 20000 - 13723) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21577)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 13723) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 13723) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21577)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 17469) // - .input(METER_ACTIVE_POWER, 20000 - 17469) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22436)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 17469) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 17469) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22436)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20066) // - .input(METER_ACTIVE_POWER, 20000 - 20066) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22531)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20066) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20066) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22531)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21564) // - .input(METER_ACTIVE_POWER, 20000 - 21564) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22171)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21564) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21564) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22171)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 22175) // - .input(METER_ACTIVE_POWER, 20000 - 22175) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21608)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22175) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 22175) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21608)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 22173) // - .input(METER_ACTIVE_POWER, 20000 - 22173) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21017)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22173) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 22173) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21017)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21816) // - .input(METER_ACTIVE_POWER, 20000 - 21816) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 20508)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21816) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21816) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 20508)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21311) // - .input(METER_ACTIVE_POWER, 20000 - 21311) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 20129)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21311) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 21311) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 20129)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20803) // - .input(METER_ACTIVE_POWER, 20000 - 20803) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19889)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20803) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20803) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19889)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20377) // - .input(METER_ACTIVE_POWER, 20000 - 20377) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19767)); + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20377) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 20000 - 20377) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19767)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java b/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java index 3018b8a775c..93c3db68b5e 100644 --- a/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java +++ b/io.openems.edge.controller.ess.cycle/test/io/openems/edge/controller/ess/cycle/ControllerEssCycleImplTest.java @@ -1,5 +1,15 @@ package io.openems.edge.controller.ess.cycle; +import static io.openems.edge.controller.ess.cycle.ControllerEssCycle.ChannelId.COMPLETED_CYCLES; +import static io.openems.edge.controller.ess.cycle.ControllerEssCycle.ChannelId.STATE_MACHINE; +import static io.openems.edge.controller.ess.cycle.CycleOrder.START_WITH_DISCHARGE; +import static io.openems.edge.controller.ess.cycle.HybridEssMode.TARGET_AC; +import static io.openems.edge.controller.ess.cycle.Mode.MANUAL_ON; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import java.time.Instant; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; @@ -7,7 +17,6 @@ import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -18,74 +27,61 @@ public class ControllerEssCycleImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, - ControllerEssCycle.ChannelId.STATE_MACHINE.id()); - - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress MAX_CHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedChargePower"); - private static final ChannelAddress MAX_DISCHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedDischargePower"); - private static final ChannelAddress SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, "SetActivePowerEquals"); - - private static final ChannelAddress COMPLETED_CYCLES = new ChannelAddress(CTRL_ID, "CompletedCycles"); - @Test public void test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2000-01-01T01:00:00.00Z"), ZoneOffset.UTC); final var power = new DummyPower(10_000); - final var ess = new DummyManagedSymmetricEss(ESS_ID) // + final var ess = new DummyManagedSymmetricEss("ess0") // .setPower(power); final var test = new ControllerTest(new ControllerEssCycleImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyConfig.create()// - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setCycleOrder(CycleOrder.START_WITH_DISCHARGE) // + .setId("ctrl0") // + .setEssId("ess0") // + .setCycleOrder(START_WITH_DISCHARGE) // .setStandbyTime(10)// .setStartTime("2000-01-01 01:00")// .setMaxSoc(100)// .setMinSoc(0)// .setPower(10000)// - .setMode(Mode.MANUAL_ON)// - .setHybridEssMode(HybridEssMode.TARGET_AC)// + .setMode(MANUAL_ON)// + .setHybridEssMode(TARGET_AC)// .setTotalCycleNumber(3)// .setFinalSoc(50)// .build()); power.addEss(ess); test.next(new TestCase()// .input(STATE_MACHINE, State.UNDEFINED)// - .input(MAX_CHARGE_POWER, -10_000)// - .input(MAX_DISCHARGE_POWER, 10_000)// - .input(SET_ACTIVE_POWER_EQUALS, 10_000) // - .input(ESS_SOC, 50)) // + .input("ess0", ALLOWED_CHARGE_POWER, -10_000)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000)// + .input("ess0", SET_ACTIVE_POWER_EQUALS, 10_000) // + .input("ess0", SOC, 50)) // .next(new TestCase("First Discharge") // .output(STATE_MACHINE, State.START_DISCHARGE))// .next(new TestCase()// - .input(MAX_CHARGE_POWER, -10_000)// - .input(MAX_DISCHARGE_POWER, 1000))// + .input("ess0", ALLOWED_CHARGE_POWER, -10_000)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 1000))// .next(new TestCase()// .timeleap(clock, 10, ChronoUnit.MINUTES))// .next(new TestCase()// .output(STATE_MACHINE, State.START_DISCHARGE))// .next(new TestCase()// - .input(MAX_CHARGE_POWER, -10_000)// - .input(MAX_DISCHARGE_POWER, 0))// + .input("ess0", ALLOWED_CHARGE_POWER, -10_000)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 0))// .next(new TestCase()// .timeleap(clock, 11, ChronoUnit.MINUTES))// .next(new TestCase("First Charge")// .output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))// .next(new TestCase()// - .input(MAX_CHARGE_POWER, -1000)// - .input(MAX_DISCHARGE_POWER, 10_000))// + .input("ess0", ALLOWED_CHARGE_POWER, -1000)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))// .next(new TestCase()// .output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))// .next(new TestCase()// - .input(MAX_CHARGE_POWER, 0)// - .input(MAX_DISCHARGE_POWER, 10_000))// + .input("ess0", ALLOWED_CHARGE_POWER, 0)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))// .next(new TestCase()// .timeleap(clock, 11, ChronoUnit.MINUTES))// .next(new TestCase() // @@ -94,17 +90,17 @@ public void test() throws Exception { .output(COMPLETED_CYCLES, 1)// .output(STATE_MACHINE, State.START_DISCHARGE))// .next(new TestCase("Second Discharge")// - .input(ESS_SOC, 0)// - .input(MAX_CHARGE_POWER, -10_000)// - .input(MAX_DISCHARGE_POWER, 0))// + .input("ess0", SOC, 0)// + .input("ess0", ALLOWED_CHARGE_POWER, -10_000)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 0))// .next(new TestCase()// .timeleap(clock, 11, ChronoUnit.MINUTES))// .next(new TestCase()// .output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))// .next(new TestCase("Second Charge")// - .input(ESS_SOC, 100)// - .input(MAX_CHARGE_POWER, 0)// - .input(MAX_DISCHARGE_POWER, 10_000))// + .input("ess0", SOC, 100)// + .input("ess0", ALLOWED_CHARGE_POWER, 0)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))// .next(new TestCase()// .timeleap(clock, 11, ChronoUnit.MINUTES))// .next(new TestCase("Second completed cycle") // @@ -113,17 +109,17 @@ public void test() throws Exception { .output(COMPLETED_CYCLES, 2)// .output(STATE_MACHINE, State.START_DISCHARGE))// .next(new TestCase("Third Discharge")// - .input(ESS_SOC, 0)// - .input(MAX_CHARGE_POWER, -10_000)// - .input(MAX_DISCHARGE_POWER, 0))// + .input("ess0", SOC, 0)// + .input("ess0", ALLOWED_CHARGE_POWER, -10_000)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 0))// .next(new TestCase()// .timeleap(clock, 11, ChronoUnit.MINUTES))// .next(new TestCase()// .output(STATE_MACHINE, State.CONTINUE_WITH_CHARGE))// .next(new TestCase("Third Charge")// - .input(ESS_SOC, 100)// - .input(MAX_CHARGE_POWER, 0)// - .input(MAX_DISCHARGE_POWER, 10_000))// + .input("ess0", SOC, 100)// + .input("ess0", ALLOWED_CHARGE_POWER, 0)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))// .next(new TestCase()// .timeleap(clock, 11, ChronoUnit.MINUTES))// .next(new TestCase("Cycle Number 3 Test")// @@ -132,11 +128,11 @@ public void test() throws Exception { .next(new TestCase()// .output(STATE_MACHINE, State.FINAL_SOC))// .next(new TestCase()// - .input(ESS_SOC, 50)// - .input(MAX_CHARGE_POWER, -10_000)// - .input(MAX_DISCHARGE_POWER, 10_000))// + .input("ess0", SOC, 50)// + .input("ess0", ALLOWED_CHARGE_POWER, -10_000)// + .input("ess0", ALLOWED_DISCHARGE_POWER, 10_000))// .next(new TestCase() // - .output(STATE_MACHINE, State.FINISHED))// - ; // + .output(STATE_MACHINE, State.FINISHED)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java b/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java index 4c495a15a52..7f8dc9e62c8 100644 --- a/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java +++ b/io.openems.edge.controller.ess.delaycharge/test/io/openems/edge/controller/ess/delaycharge/ControllerEssDelayChargeImplTest.java @@ -1,13 +1,14 @@ package io.openems.edge.controller.ess.delaycharge; +import static io.openems.edge.controller.ess.delaycharge.ControllerEssDelayCharge.ChannelId.CHARGE_POWER_LIMIT; +import static java.time.temporal.ChronoUnit.HOURS; + import java.time.Instant; import java.time.ZoneId; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -15,11 +16,6 @@ public class ControllerEssDelayChargeImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final ChannelAddress CTRL_CHARGE_POWER_LIMIT = new ChannelAddress(CTRL_ID, "ChargePowerLimit"); - - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { // Initialize mocked Clock @@ -27,32 +23,32 @@ public void test() throws Exception { Instant.ofEpochMilli(1546300800000L /* Tuesday, 1. January 2019 00:00:00 */), ZoneId.of("UTC")); new ControllerTest(new ControllerEssDelayChargeImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .withSoc(20) // .withCapacity(9000)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setTargetHour(15) // .build()) .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.HOURS) // = 6 am - .output(CTRL_CHARGE_POWER_LIMIT, 800)) + .timeleap(clock, 6, HOURS) // = 6 am + .output(CHARGE_POWER_LIMIT, 800)) .next(new TestCase() // - .timeleap(clock, 2, ChronoUnit.HOURS) // = 8 am - .output(CTRL_CHARGE_POWER_LIMIT, 1028)) + .timeleap(clock, 2, HOURS) // = 8 am + .output(CHARGE_POWER_LIMIT, 1028)) .next(new TestCase() // - .timeleap(clock, 2, ChronoUnit.HOURS) // = 10 am - .output(CTRL_CHARGE_POWER_LIMIT, 1440)) + .timeleap(clock, 2, HOURS) // = 10 am + .output(CHARGE_POWER_LIMIT, 1440)) .next(new TestCase() // - .timeleap(clock, 2, ChronoUnit.HOURS) // = 12 am - .output(CTRL_CHARGE_POWER_LIMIT, 2400)) + .timeleap(clock, 2, HOURS) // = 12 am + .output(CHARGE_POWER_LIMIT, 2400)) .next(new TestCase() // - .timeleap(clock, 2, ChronoUnit.HOURS) // = 14 am - .output(CTRL_CHARGE_POWER_LIMIT, 7200)) + .timeleap(clock, 2, HOURS) // = 14 am + .output(CHARGE_POWER_LIMIT, 7200)) .next(new TestCase() // - .timeleap(clock, 3, ChronoUnit.HOURS) // = 16 am - .output(CTRL_CHARGE_POWER_LIMIT, 0)); + .timeleap(clock, 3, HOURS) // = 16 am + .output(CHARGE_POWER_LIMIT, 0)) // + .deactivate(); } - } diff --git a/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java b/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java index 3d258054b4f..6843f1ca737 100644 --- a/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java +++ b/io.openems.edge.controller.ess.delayedselltogrid/test/io/openems/edge/controller/ess/delayedselltogrid/ControllerEssDelayedSellToGridImplTest.java @@ -1,76 +1,70 @@ package io.openems.edge.controller.ess.delayedselltogrid; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssDelayedSellToGridImplTest { - private static final String CTRL_ID = "ctrlDelayedSellToGrid0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssDelayedSellToGridImpl())// .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create()// - .setId(CTRL_ID)// - .setEssId(ESS_ID)// - .setMeterId(METER_ID)// + .setId("ctrlDelayedSellToGrid0")// + .setEssId("ess0")// + .setMeterId("meter0")// .setSellToGridPowerLimit(12_500_000)// .setContinuousSellToGridPower(500_000).build())// .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 0) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500_000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500_000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -30_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, 470_000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -30_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 470_000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 500_000) // - .input(METER_ACTIVE_POWER, -500_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500_000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 500_000) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500_000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 50_000) // - .input(METER_ACTIVE_POWER, -500_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, 50_000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 50_000) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 50_000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, -50_000) // - .input(METER_ACTIVE_POWER, -500_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -50_000) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 150_000) // - .input(METER_ACTIVE_POWER, -500_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, 150_000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 150_000) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -500_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 150_000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -1_500_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -1_500_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, -100_000) // - .input(METER_ACTIVE_POWER, -15_000_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2_600_000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -100_000) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -15_000_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2_600_000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, -1_000_000) // - .input(METER_ACTIVE_POWER, -16_000_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, -4_500_000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1_000_000) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -16_000_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, -4_500_000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -16_000_000)// - .output(ESS_SET_ACTIVE_POWER_EQUALS, -3_500_000)) // - ; + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", SymmetricEss.ChannelId.ACTIVE_POWER, -16_000_000)// + .output("ess0", SET_ACTIVE_POWER_EQUALS, -3_500_000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java b/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java index 127acdc997d..4b8e05a2819 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/test/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImplTest.java @@ -1,9 +1,19 @@ package io.openems.edge.controller.ess.emergencycapacityreserve; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_AC_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_DC_ACTUAL_POWER; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_RAMP_POWER; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.DEBUG_TARGET_POWER; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE; +import static io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve.ChannelId.STATE_MACHINE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import org.junit.Test; import io.openems.common.function.ThrowingRunnable; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; @@ -14,100 +24,82 @@ public class ControllerEssEmergencyCapacityReserveImplTest { - private static final String CTRL_ID = "ctrlEmergencyCapacityReserve0"; - private static final String ESS_ID = "ess0"; - private static final String SUM_ID = "_sum"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - private static final ChannelAddress DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(CTRL_ID, - "DebugSetActivePowerLessOrEquals"); - private static final ChannelAddress DEBUG_TARGET_POWER = new ChannelAddress(CTRL_ID, "DebugTargetPower"); - private static final ChannelAddress DEBUG_RAMP_POWER = new ChannelAddress(CTRL_ID, "DebugRampPower"); - - private static final ChannelAddress RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE = new ChannelAddress(CTRL_ID, - "RangeOfReserveSocOutsideAllowedValue"); - - private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower"); - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerLessOrEquals"); - - private static final ChannelAddress PRODUCTION_DC_ACTUAL_POWER = new ChannelAddress(SUM_ID, - "ProductionDcActualPower"); - private static final ChannelAddress PRODUCTION_AC_ACTIVE_POWER = new ChannelAddress(SUM_ID, - "ProductionAcActivePower"); - @Test public void testReserveSocRange() throws Exception { new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) // + .deactivate(); new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(5) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) // + .deactivate(); new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(4) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)) // + .deactivate(); new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(100) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, false)) // + .deactivate(); new ControllerTest(new ControllerEssEmergencyCapacityReserveImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(101) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)); + .output(RANGE_OF_RESERVE_SOC_OUTSIDE_ALLOWED_VALUE, true)) // + .deactivate(); } @Test @@ -116,19 +108,19 @@ public void testReachTargetPower() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.NO_LIMIT)); // var maxApparentPower = 10000; @@ -141,14 +133,16 @@ public void testReachTargetPower() throws Exception { result -= rampPower; } - controllerTest.next(new TestCase().input(ESS_SOC, 21) // + controllerTest.next(new TestCase().input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 0) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, result) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, result) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, result) // .output(DEBUG_TARGET_POWER, targetPower.floatValue()) // ); } + + controllerTest.deactivate(); } @Test @@ -157,41 +151,42 @@ public void testAllStates() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .output(STATE_MACHINE, State.FORCE_CHARGE)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 22) // + .input("ess0", SOC, 22) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .output(STATE_MACHINE, State.NO_LIMIT)); + .input("ess0", SOC, 22) // + .output(STATE_MACHINE, State.NO_LIMIT)) // + .deactivate(); } @Test @@ -200,23 +195,24 @@ public void testIncreaseRampByNoLimitState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 80)) // + .input("ess0", SOC, 80)) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_MAX_APPARENT_POWER, 10000) // + .input("ess0", SOC, 80) // + .input("ess0", MAX_APPARENT_POWER, 10000) // .output(STATE_MACHINE, State.NO_LIMIT) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // .output(DEBUG_TARGET_POWER, 10000f) // - .output(DEBUG_RAMP_POWER, 100f)); + .output(DEBUG_RAMP_POWER, 100f)) // + .deactivate(); } @Test @@ -225,65 +221,66 @@ public void testDecreaseRampByAboveReserveSocState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.NO_LIMIT)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // // to reach 50% of maxApparentPower .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 0) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .input("ess0", SOC, 21) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// + .input("ess0", SOC, 21) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) // // to reach is DC-PV .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 6000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)) .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 10000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 10000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 6000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(PRODUCTION_DC_ACTUAL_POWER, 6000) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)); + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)) // + .deactivate(); } @Test @@ -292,41 +289,42 @@ public void testDecreaseRampByAtReserveSocState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .output(STATE_MACHINE, State.NO_LIMIT)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // .next(new TestCase() // - .input(ESS_SOC, 20) // + .input("ess0", SOC, 20) // .input(PRODUCTION_DC_ACTUAL_POWER, 0) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// + .input("ess0", SOC, 20) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)); + .input("ess0", SOC, 20) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9600)) // + .deactivate(); } @Test @@ -335,45 +333,46 @@ public void testDecreaseRampByUnderReserveSocState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.NO_LIMIT)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // .next(new TestCase() // - .input(ESS_SOC, 19) // + .input("ess0", SOC, 19) // .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)) // .next(new TestCase() // - .input(ESS_SOC, 19) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)// + .input("ess0", SOC, 19) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)) // .next(new TestCase() // - .input(ESS_SOC, 19) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)); + .input("ess0", SOC, 19) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8300)) // + .deactivate(); } @Test @@ -382,62 +381,63 @@ public void testDecreaseRampByForceStartChargeState() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.NO_LIMIT)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9900)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.AT_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9800)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9300)) // .next(new TestCase() // - .input(ESS_SOC, 16) // + .input("ess0", SOC, 16) // .input(PRODUCTION_AC_ACTIVE_POWER, 100) // .output(STATE_MACHINE, State.FORCE_CHARGE)// - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)// + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9200)) // .next(new TestCase() // - .input(ESS_SOC, 19) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)// + .input("ess0", SOC, 19) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)// .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9100)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.FORCE_CHARGE) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9000)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.AT_RESERVE_SOC) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8900)) // .next(new TestCase() // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)// - .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)); + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)// + .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 8800)) // + .deactivate(); } @Test @@ -451,11 +451,11 @@ public void testUndefinedSoc() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(10000)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // @@ -464,16 +464,17 @@ public void testUndefinedSoc() throws Exception { .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .input(ESS_SOC, 16)) // + .input("ess0", SOC, 16)) // .next(new TestCase() // .onAfterProcessImage(sleep) // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC))// .next(new TestCase() // .onAfterProcessImage(sleep) // - .input(ESS_SOC, null)) // + .input("ess0", SOC, null)) // .next(new TestCase() // .onAfterProcessImage(sleep) // - .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)); + .output(STATE_MACHINE, State.BELOW_RESERVE_SOC)) // + .deactivate(); } @Test @@ -482,18 +483,18 @@ public void testIncreaseRampToMaxApparentPower() throws Exception { .addReference("componentManager", new DummyComponentManager()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID) // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(10000)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setReserveSoc(20) // .setReserveSocEnabled(true) // .build()) // .next(new TestCase() // .output(STATE_MACHINE, State.NO_LIMIT)) // .next(new TestCase() // - .input(ESS_SOC, 21)) // + .input("ess0", SOC, 21)) // .next(new TestCase() // .output(STATE_MACHINE, State.ABOVE_RESERVE_SOC) // .output(DEBUG_TARGET_POWER, 5000f) // @@ -510,7 +511,7 @@ public void testIncreaseRampToMaxApparentPower() throws Exception { .output(DEBUG_RAMP_POWER, 100f) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, 9700)) // .next(new TestCase() // - .input(ESS_SOC, 22)) // + .input("ess0", SOC, 22)) // .next(new TestCase() // .output(STATE_MACHINE, State.NO_LIMIT) // .output(DEBUG_TARGET_POWER, 10000f) // @@ -531,13 +532,14 @@ public void testIncreaseRampToMaxApparentPower() throws Exception { .output(DEBUG_TARGET_POWER, 10000f) // .output(DEBUG_RAMP_POWER, 100f) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // .output(STATE_MACHINE, State.NO_LIMIT) // .output(DEBUG_TARGET_POWER, 10000f) // .output(DEBUG_RAMP_POWER, 100f) // .output(DEBUG_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // - .output(SET_ACTIVE_POWER_LESS_OR_EQUALS, null)); + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java index bdf301e2827..57af0c89ba9 100644 --- a/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/src/io/openems/edge/controller/ess/fastfrequencyreserve/statemachine/ActivationTimeHandler.java @@ -3,8 +3,8 @@ import java.time.Duration; import java.time.Instant; -import io.openems.common.exceptions.OpenemsException; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.exceptions.OpenemsException; import io.openems.common.utils.EnumUtils; import io.openems.edge.common.statemachine.StateHandler; import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java similarity index 51% rename from io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest.java rename to io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java index 942975af03b..e704c552b50 100644 --- a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest.java +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest.java @@ -1,65 +1,57 @@ package io.openems.edge.controller.ess.fastfrequencyreserve; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE; +import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime.LONG_ACTIVATION_RUN; +import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration.LONG_SUPPORT_DURATION; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.FREQUENCY; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import java.util.List; import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.sum.GridMode; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; -import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; import io.openems.edge.controller.ess.fastfrequencyreserve.jsonrpc.SetActivateFastFreqReserveRequest.ActivateFastFreqReserveSchedule; import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.meter.test.DummyElectricityMeter; -public class MyControllerTest { - - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - - private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID); - private static final DummyElectricityMeter METER = new DummyElectricityMeter(METER_ID); - - private static final ChannelAddress METER_FREQUENCY = new ChannelAddress(METER_ID, "Frequency"); - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "SetActivePowerEquals"); +public class ControllerFastFrequencyReserveImplTest { @Test public void testFfrController() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); - - final var cm = new DummyComponentManager(clock); - new ControllerTest(new ControllerFastFrequencyReserveImpl()) // - - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", ESS // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .withGridMode(GridMode.ON_GRID)// .withMaxApparentPower(92000)// .withAllowedChargePower(-92000)// .withAllowedDischargePower(92000)) // - .addReference("meter", METER) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .setEssId("ess0") // + .setId("ctrl0") // + .setMeterId("meter0") // .setMode(ControlMode.MANUAL_ON) // .setPreActivationTime(15)// .setactivationScheduleJson(JsonUtils.buildJsonArray() // @@ -68,219 +60,216 @@ public void testFfrController() throws Exception { .addProperty("duration", 86400) // .addProperty("dischargePowerSetPoint", 92000) // .addProperty("frequencyLimit", 49500) // - .addProperty("activationRunTime", ActivationTime.LONG_ACTIVATION_RUN) // - .addProperty("supportDuration", SupportDuration.LONG_SUPPORT_DURATION) // + .addProperty("activationRunTime", LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", LONG_SUPPORT_DURATION) // .build()) .add(JsonUtils.buildJsonObject() // .addProperty("startTimestamp", 1689331500) // Fri Jul 14 2023 10:45:00 GMT+0000 .addProperty("duration", 86400) // .addProperty("dischargePowerSetPoint", 92000) // .addProperty("frequencyLimit", 49500) // - .addProperty("activationRunTime", ActivationTime.LONG_ACTIVATION_RUN) // - .addProperty("supportDuration", SupportDuration.LONG_SUPPORT_DURATION) // + .addProperty("activationRunTime", LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", LONG_SUPPORT_DURATION) // .build()) .build()// .toString())// .build()) .next(new TestCase("1") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.UNDEFINED) // - .output(ESS_ACTIVE_POWER, null))// + .output("ess0", SET_ACTIVE_POWER_EQUALS, null))// .next(new TestCase("2") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.UNDEFINED) // - .output(ESS_ACTIVE_POWER, null))// + .output("ess0", SET_ACTIVE_POWER_EQUALS, null))// .next(new TestCase("3") // - .timeleap(clock, 1, ChronoUnit.HOURS) // - .input(METER_FREQUENCY, 50000))// + .timeleap(clock, 1, HOURS) // + .input("meter0", FREQUENCY, 50000))// .next(new TestCase("4"))// .next(new TestCase("5") // .output(STATE_MACHINE, State.UNDEFINED)) .next(new TestCase("6") // - .timeleap(clock, 10, ChronoUnit.MINUTES)// + .timeleap(clock, 10, MINUTES)// .output(STATE_MACHINE, State.PRE_ACTIVATION_STATE)) .next(new TestCase("7") // - .timeleap(clock, 10, ChronoUnit.MINUTES)) + .timeleap(clock, 10, MINUTES)) .next(new TestCase("8") // .output(STATE_MACHINE, State.ACTIVATION_TIME)// - .output(ESS_ACTIVE_POWER, 0)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("9") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.ACTIVATION_TIME)// - .output(ESS_ACTIVE_POWER, 0)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("10") // - .input(METER_FREQUENCY, 49400)// + .input("meter0", FREQUENCY, 49400)// .output(STATE_MACHINE, State.ACTIVATION_TIME)// - .output(ESS_ACTIVE_POWER, 92000)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) .next(new TestCase("11") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("12") // .output(STATE_MACHINE, State.SUPPORT_DURATION)// - .output(ESS_ACTIVE_POWER, 92000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // .next(new TestCase("13") // - .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.SECONDS) // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) // .output(STATE_MACHINE, State.SUPPORT_DURATION)) .next(new TestCase("14") // - .output(STATE_MACHINE, State.DEACTIVATION_TIME).output(ESS_ACTIVE_POWER, 0)) + .output(STATE_MACHINE, State.DEACTIVATION_TIME) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("15") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("16") // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // - .output(ESS_ACTIVE_POWER, 0)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("17") // - .timeleap(clock, 16, ChronoUnit.SECONDS) // + .timeleap(clock, 16, SECONDS) // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) .next(new TestCase("18") // - .timeleap(clock, 12, ChronoUnit.MINUTES)) // + .timeleap(clock, 12, MINUTES)) // .next(new TestCase("19") // .output(STATE_MACHINE, State.RECOVERY_TIME) // - .output(ESS_ACTIVE_POWER, -16560)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, -16560)) .next(new TestCase("20") // .output(STATE_MACHINE, State.RECOVERY_TIME)) .next(new TestCase("21") // .output(STATE_MACHINE, State.RECOVERY_TIME)) .next(new TestCase("22") // - .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.MINUTES)) + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES)) .next(new TestCase("23") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.ACTIVATION_TIME)) .next(new TestCase("24") // - .timeleap(clock, 1, ChronoUnit.DAYS)) // + .timeleap(clock, 1, DAYS)) // .next(new TestCase("25") // .output(STATE_MACHINE, State.ACTIVATION_TIME)) .next(new TestCase("26") // - .timeleap(clock, 4, ChronoUnit.HOURS)) // + .timeleap(clock, 4, HOURS)) // .next(new TestCase("27") // .output(STATE_MACHINE, State.ACTIVATION_TIME)) .next(new TestCase("28")// - .input(METER_FREQUENCY, 49400)) + .input("meter0", FREQUENCY, 49400)) .next(new TestCase("29") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("30") // .output(STATE_MACHINE, State.SUPPORT_DURATION)// - .output(ESS_ACTIVE_POWER, 92000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // .next(new TestCase("31") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("32") // .output(STATE_MACHINE, State.SUPPORT_DURATION)// - .output(ESS_ACTIVE_POWER, 92000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // .next(new TestCase("33") // - .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.SECONDS) // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) // .output(STATE_MACHINE, State.SUPPORT_DURATION)) .next(new TestCase("34") // - .output(STATE_MACHINE, State.DEACTIVATION_TIME).output(ESS_ACTIVE_POWER, 0)) + .output(STATE_MACHINE, State.DEACTIVATION_TIME).output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("35") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("36") // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // - .output(ESS_ACTIVE_POWER, 0)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("37") // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) .next(new TestCase("38") // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) .next(new TestCase("39") // - .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.MINUTES)) + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES)) .next(new TestCase("40") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) .next(new TestCase("41") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.RECOVERY_TIME)) .next(new TestCase("42") // - .timeleap(clock, 1, ChronoUnit.DAYS)); + .timeleap(clock, 1, DAYS)) // + .deactivate(); } @Test public void testInvalidJsonSchedule() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); - - final var cm = new DummyComponentManager(clock); - + final var clock = createDummyClock(); new ControllerTest(new ControllerFastFrequencyReserveImpl()) // - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", ESS // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .withGridMode(GridMode.ON_GRID)// .withMaxApparentPower(92000)// .withAllowedChargePower(-92000)// .withAllowedDischargePower(92000)) // - .addReference("meter", METER) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .setEssId("ess0") // + .setId("ctrl0") // + .setMeterId("meter0") // .setMode(ControlMode.MANUAL_ON) // .setPreActivationTime(15)// .setactivationScheduleJson("foo")// - .build()); + .build()) // + .deactivate(); } @Test public void testInvalidJsonSchedule1() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); - - final var cm = new DummyComponentManager(clock); - + final var clock = createDummyClock(); new ControllerTest(new ControllerFastFrequencyReserveImpl()) // - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", ESS // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .withGridMode(GridMode.ON_GRID)// .withMaxApparentPower(92000)// .withAllowedChargePower(-92000)// .withAllowedDischargePower(92000)) // - .addReference("meter", METER) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .setEssId("ess0") // + .setId("ctrl0") // + .setMeterId("meter0") // .setMode(ControlMode.MANUAL_ON) // .setPreActivationTime(15)// .setactivationScheduleJson("[foo]")// - .build()); - + .build()) // + .deactivate(); } - public static final String myJson = "[\r\n" + " " // - + "{\r\n" + " " // - + "\"startTimestamp\": \"1701738000\",\r\n"// - + " \"duration\": \"10800\",\r\n" // - + " \"dischargePowerSetPoint\": \"92000\",\r\n"// - + " \"frequencyLimit\": \"50000\",\r\n"// - + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\r\n"// - + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\r\n" // - + " },\r\n" // - + "{\r\n" + " " // - + "\"startTimestamp\": \"1701738000\",\r\n"// - + " \"duration\": \"10800\",\r\n" // - + " \"dischargePowerSetPoint\": \"92000\",\r\n"// - + " \"frequencyLimit\": \"50000\",\r\n"// - + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\r\n"// - + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\r\n" // - + " },\r\n" // - + " {\r\n"// - + " \"startTimestamp\": \"1701752400\",\r\n"// - + " \"duration\": \"10800\",\r\n"// - + " \"dischargePowerSetPoint\": \"92000\",\r\n"// - + " \"frequencyLimit\": \"50000\",\r\n"// - + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\r\n"// - + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\r\n" // - + " },\r\n" // - + " {\r\n"// - + " \"startTimestamp\": \"1701777600\",\r\n"// - + " \"duration\": \"10800\",\r\n"// - + " \"dischargePowerSetPoint\": \"92000\",\r\n"// - + " \"frequencyLimit\": \"50000\",\r\n"// - + " \"activationRunTime\": \"LONG_ACTIVATION_RUN\",\r\n"// - + " \"supportDuration\": \"LONG_SUPPORT_DURATION\"\r\n" // - + " }\r\n" + "]";// + public static final String MY_JSON = """ + [ + { + "startTimestamp":"1701738000", + "duration":"10800", + "dischargePowerSetPoint":"92000", + "frequencyLimit":"50000", + "activationRunTime":"LONG_ACTIVATION_RUN", + "supportDuration":"LONG_SUPPORT_DURATION" + }, + { + "startTimestamp":"1701738000", + "duration":"10800", + "dischargePowerSetPoint":"92000", + "frequencyLimit":"50000", + "activationRunTime":"LONG_ACTIVATION_RUN", + "supportDuration":"LONG_SUPPORT_DURATION" + }, + { + "startTimestamp":"1701752400", + "duration":"10800", + "dischargePowerSetPoint":"92000", + "frequencyLimit":"50000", + "activationRunTime":"LONG_ACTIVATION_RUN", + "supportDuration":"LONG_SUPPORT_DURATION" + }, + { + "startTimestamp":"1701777600", + "duration":"10800", + "dischargePowerSetPoint":"92000", + "frequencyLimit":"50000", + "activationRunTime":"LONG_ACTIVATION_RUN", + "supportDuration":"LONG_SUPPORT_DURATION" + } + ] + """; @Test public void testFromMethod() throws OpenemsNamedException { - - var scheduleElement = JsonUtils.parse(myJson); - var scheduleArray = JsonUtils.getAsJsonArray(scheduleElement); - + var scheduleArray = JsonUtils.parseToJsonArray(MY_JSON); try { List scheduleList = ActivateFastFreqReserveSchedule.from(scheduleArray); diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest2.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java similarity index 53% rename from io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest2.java rename to io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java index 69fc5e7e463..d1bbb7c9da1 100644 --- a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyControllerTest2.java +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/ControllerFastFrequencyReserveImplTest2.java @@ -1,185 +1,182 @@ package io.openems.edge.controller.ess.fastfrequencyreserve; +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.edge.controller.ess.fastfrequencyreserve.ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE; +import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime.LONG_ACTIVATION_RUN; +import static io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration.LONG_SUPPORT_DURATION; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.FREQUENCY; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.common.utils.JsonUtils; import io.openems.edge.common.sum.GridMode; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; -import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; import io.openems.edge.controller.ess.fastfrequencyreserve.statemachine.StateMachine.State; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.meter.test.DummyElectricityMeter; -public class MyControllerTest2 { - - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - - private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID); - private static final DummyElectricityMeter METER = new DummyElectricityMeter(METER_ID); - - private static final ChannelAddress METER_FREQUENCY = new ChannelAddress(METER_ID, "Frequency"); - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "SetActivePowerEquals"); +public class ControllerFastFrequencyReserveImplTest2 { @Test public void test1() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2023-07-13T08:45:00.00Z"), ZoneOffset.UTC); - final var cm = new DummyComponentManager(clock); - new ControllerTest(new ControllerFastFrequencyReserveImpl()) // - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", ESS // + .addReference("ess", new DummyManagedSymmetricEss("ess0") // .withGridMode(GridMode.ON_GRID)// .withMaxApparentPower(92000)// .withAllowedChargePower(-92000)// .withAllowedDischargePower(92000)) // - .addReference("meter", METER) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .setEssId("ess0") // + .setId("ctrl0") // + .setMeterId("meter0") // .setMode(ControlMode.MANUAL_ON) // .setPreActivationTime(15)// - .setactivationScheduleJson(JsonUtils.buildJsonArray() // - .add(JsonUtils.buildJsonObject() // + .setactivationScheduleJson(buildJsonArray() // + .add(buildJsonObject() // .addProperty("startTimestamp", 1689242400) // Thu Jul 13 2023 10:00:00 GMT+0000 .addProperty("duration", 86400) // .addProperty("dischargePowerSetPoint", 92000) // .addProperty("frequencyLimit", 49500) // - .addProperty("activationRunTime", ActivationTime.LONG_ACTIVATION_RUN) // - .addProperty("supportDuration", SupportDuration.LONG_SUPPORT_DURATION) // + .addProperty("activationRunTime", LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", LONG_SUPPORT_DURATION) // .build()) - .add(JsonUtils.buildJsonObject() // + .add(buildJsonObject() // .addProperty("startTimestamp", 1689331500) // Fri Jul 14 2023 10:45:00 GMT+0000 .addProperty("duration", 86400) // .addProperty("dischargePowerSetPoint", 92000) // .addProperty("frequencyLimit", 49500) // - .addProperty("activationRunTime", ActivationTime.LONG_ACTIVATION_RUN) // - .addProperty("supportDuration", SupportDuration.LONG_SUPPORT_DURATION) // + .addProperty("activationRunTime", LONG_ACTIVATION_RUN) // + .addProperty("supportDuration", LONG_SUPPORT_DURATION) // .build()) .build()// .toString())// .build()) .next(new TestCase("1") // - .input(METER_FREQUENCY, 50000)// - .output(STATE_MACHINE, State.UNDEFINED) // - .output(ESS_ACTIVE_POWER, null))// + .input("meter0", FREQUENCY, 50000)// + .output(ControllerFastFrequencyReserve.ChannelId.STATE_MACHINE, State.UNDEFINED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null))// .next(new TestCase("2") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.UNDEFINED) // - .output(ESS_ACTIVE_POWER, null))// + .output("ess0", SET_ACTIVE_POWER_EQUALS, null))// .next(new TestCase("3") // - .timeleap(clock, 1, ChronoUnit.HOURS) // - .input(METER_FREQUENCY, 50000))// + .timeleap(clock, 1, HOURS) // + .input("meter0", FREQUENCY, 50000))// .next(new TestCase("4"))// .next(new TestCase("5") // .output(STATE_MACHINE, State.UNDEFINED)) .next(new TestCase("6") // - .timeleap(clock, 10, ChronoUnit.MINUTES)// + .timeleap(clock, 10, MINUTES)// .output(STATE_MACHINE, State.PRE_ACTIVATION_STATE)) .next(new TestCase("7") // - .timeleap(clock, 10, ChronoUnit.MINUTES)) + .timeleap(clock, 10, MINUTES)) .next(new TestCase("8") // .output(STATE_MACHINE, State.ACTIVATION_TIME)// - .output(ESS_ACTIVE_POWER, 0)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("9") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.ACTIVATION_TIME)// - .output(ESS_ACTIVE_POWER, 0)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("10") // - .input(METER_FREQUENCY, 49400)// + .input("meter0", FREQUENCY, 49400)// .output(STATE_MACHINE, State.ACTIVATION_TIME)// - .output(ESS_ACTIVE_POWER, 92000)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) .next(new TestCase("11") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("12") // .output(STATE_MACHINE, State.SUPPORT_DURATION)// - .output(ESS_ACTIVE_POWER, 92000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // .next(new TestCase("13") // - .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.SECONDS) // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) // .output(STATE_MACHINE, State.SUPPORT_DURATION)) .next(new TestCase("14") // - .output(STATE_MACHINE, State.DEACTIVATION_TIME).output(ESS_ACTIVE_POWER, 0)) + .output(STATE_MACHINE, State.DEACTIVATION_TIME).output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("15") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("16") // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // - .output(ESS_ACTIVE_POWER, 0)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("17") // - .timeleap(clock, 16, ChronoUnit.SECONDS) // + .timeleap(clock, 16, SECONDS) // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) .next(new TestCase("18") // - .timeleap(clock, 12, ChronoUnit.MINUTES)) // + .timeleap(clock, 12, MINUTES)) // .next(new TestCase("19") // .output(STATE_MACHINE, State.RECOVERY_TIME) // - .output(ESS_ACTIVE_POWER, -16560)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, -16560)) .next(new TestCase("20") // .output(STATE_MACHINE, State.RECOVERY_TIME)) .next(new TestCase("21") // .output(STATE_MACHINE, State.RECOVERY_TIME)) .next(new TestCase("22") // - .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.MINUTES)) + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES)) .next(new TestCase("23") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.ACTIVATION_TIME)) .next(new TestCase("24") // - .timeleap(clock, 1, ChronoUnit.DAYS)) // + .timeleap(clock, 1, DAYS)) // .next(new TestCase("25") // .output(STATE_MACHINE, State.ACTIVATION_TIME)) .next(new TestCase("26") // - .timeleap(clock, 4, ChronoUnit.HOURS)) // + .timeleap(clock, 4, HOURS)) // .next(new TestCase("27") // .output(STATE_MACHINE, State.ACTIVATION_TIME)) .next(new TestCase("28")// - .input(METER_FREQUENCY, 49400)) + .input("meter0", FREQUENCY, 49400)) .next(new TestCase("29") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("30") // .output(STATE_MACHINE, State.SUPPORT_DURATION)// - .output(ESS_ACTIVE_POWER, 92000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // .next(new TestCase("31") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("32") // .output(STATE_MACHINE, State.SUPPORT_DURATION)// - .output(ESS_ACTIVE_POWER, 92000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 92000)) // .next(new TestCase("33") // - .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.SECONDS) // + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), SECONDS) // .output(STATE_MACHINE, State.SUPPORT_DURATION)) .next(new TestCase("34") // - .output(STATE_MACHINE, State.DEACTIVATION_TIME).output(ESS_ACTIVE_POWER, 0)) + .output(STATE_MACHINE, State.DEACTIVATION_TIME) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("35") // - .timeleap(clock, ActivationTime.LONG_ACTIVATION_RUN.getValue(), ChronoUnit.MILLIS)) // + .timeleap(clock, LONG_ACTIVATION_RUN.getValue(), MILLIS)) // .next(new TestCase("36") // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY) // - .output(ESS_ACTIVE_POWER, 0)) + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) .next(new TestCase("37") // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) .next(new TestCase("38") // .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) .next(new TestCase("39") // - .timeleap(clock, SupportDuration.LONG_SUPPORT_DURATION.getValue(), ChronoUnit.MINUTES)) + .timeleap(clock, LONG_SUPPORT_DURATION.getValue(), MINUTES)) .next(new TestCase("40") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.BUFFERED_TIME_BEFORE_RECOVERY)) .next(new TestCase("41") // - .input(METER_FREQUENCY, 50000)// + .input("meter0", FREQUENCY, 50000)// .output(STATE_MACHINE, State.RECOVERY_TIME)) .next(new TestCase("42") // - .timeleap(clock, 1, ChronoUnit.DAYS)); + .timeleap(clock, 1, DAYS)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java index efc5087e1f3..948d3e0fb3a 100644 --- a/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java +++ b/io.openems.edge.controller.ess.fastfrequencyreserve/test/io/openems/edge/controller/ess/fastfrequencyreserve/MyConfig.java @@ -2,9 +2,7 @@ import io.openems.common.test.AbstractComponentConfig; import io.openems.common.utils.ConfigUtils; -import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ActivationTime; import io.openems.edge.controller.ess.fastfrequencyreserve.enums.ControlMode; -import io.openems.edge.controller.ess.fastfrequencyreserve.enums.SupportDuration; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java b/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java index d26fa505e44..ebcde754b1d 100644 --- a/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java +++ b/io.openems.edge.controller.ess.fixactivepower/test/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.controller.ess.fixactivepower; +import static io.openems.edge.controller.ess.fixactivepower.ControllerEssFixActivePowerImpl.getAcPower; import static org.junit.Assert.assertEquals; import org.junit.Test; @@ -14,45 +15,44 @@ public class ControllerEssFixActivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void testOn() throws OpenemsException, Exception { - final var ess = new DummyManagedAsymmetricEss(ESS_ID); + final var ess = new DummyManagedAsymmetricEss("ess0"); new ControllerTest(new ControllerEssFixActivePowerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMode(Mode.MANUAL_ON) // .setHybridEssMode(HybridEssMode.TARGET_DC) // .setPower(1234) // .setPhase(Phase.ALL) // .setRelationship(Relationship.EQUALS) // - .build()); // + .build()) // + .deactivate(); } @Test public void testOff() throws OpenemsException, Exception { new ControllerTest(new ControllerEssFixActivePowerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", new DummyManagedAsymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedAsymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMode(Mode.MANUAL_OFF) // .setHybridEssMode(HybridEssMode.TARGET_DC) // .setPower(1234) // .setPhase(Phase.ALL) // .setRelationship(Relationship.EQUALS) // - .build()); // + .build()) // + .deactivate(); } @Test public void testGetAcPower() throws OpenemsException, Exception { - var hybridEss = new DummyHybridEss(ESS_ID) // + var hybridEss = new DummyHybridEss("ess0") // .withActivePower(7000) // .withMaxApparentPower(10000) // .withAllowedChargePower(-5000) // @@ -60,9 +60,9 @@ public void testGetAcPower() throws OpenemsException, Exception { .withDcDischargePower(3000); // assertEquals(Integer.valueOf(5000), // - ControllerEssFixActivePowerImpl.getAcPower(hybridEss, HybridEssMode.TARGET_AC, 5000)); + getAcPower(hybridEss, HybridEssMode.TARGET_AC, 5000)); assertEquals(Integer.valueOf(9000), // - ControllerEssFixActivePowerImpl.getAcPower(hybridEss, HybridEssMode.TARGET_DC, 5000)); + getAcPower(hybridEss, HybridEssMode.TARGET_DC, 5000)); } } diff --git a/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java b/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java index 48553f06491..70d0b636428 100644 --- a/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java +++ b/io.openems.edge.controller.ess.fixstateofcharge/test/io/openems/edge/controller/ess/fixstateofcharge/ControllerEssFixStateOfChargeImplTest.java @@ -1,10 +1,23 @@ package io.openems.edge.controller.ess.fixstateofcharge; +import static io.openems.edge.controller.ess.fixstateofcharge.api.AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR; +import static io.openems.edge.controller.ess.fixstateofcharge.api.EndCondition.CAPACITY_CHANGED; +import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.DEBUG_SET_ACTIVE_POWER; +import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.DEBUG_SET_ACTIVE_POWER_RAW; +import static io.openems.edge.controller.ess.fixstateofcharge.api.FixStateOfCharge.ChannelId.STATE_MACHINE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.CAPACITY; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; +import static java.lang.Math.min; +import static java.lang.Math.round; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; + import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import org.junit.Test; @@ -14,54 +27,30 @@ import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.controller.ess.fixstateofcharge.api.AbstractFixStateOfCharge; -import io.openems.edge.controller.ess.fixstateofcharge.api.EndCondition; -import io.openems.edge.controller.ess.fixstateofcharge.statemachine.StateMachine; +import io.openems.edge.controller.ess.fixstateofcharge.statemachine.StateMachine.State; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.timedata.test.DummyTimedata; public class ControllerEssFixStateOfChargeImplTest { - // Ids - private static final String CTRL_ID = "ctrlFixStateOfCharge0"; - private static final String ESS_ID = "ess0"; - - // Components - private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID) // + private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(10_000); - - // Defaults private static final String DEFAULT_TARGET_TIME = "2022-10-27T10:30:00+01:00"; - - // Ess channels - private static final ChannelAddress ESS_CAPACITY = new ChannelAddress(ESS_ID, "Capacity"); - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - - // Controller channels - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - private static final ChannelAddress DEBUG_SET_ACTIVE_POWER = new ChannelAddress(CTRL_ID, "DebugSetActivePower"); - private static final ChannelAddress DEBUG_SET_ACTIVE_POWER_RAW = new ChannelAddress(CTRL_ID, - "DebugSetActivePowerRaw"); - private static final ChannelAddress CTRL_ESS_CAPACITY = new ChannelAddress(CTRL_ID, "EssCapacity"); + private static final ChannelAddress CTRL_ESS_CAPACITY = new ChannelAddress("ctrl0", "EssCapacity"); @Test public void testNotRunning() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("sum", new DummySum()) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(false) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -69,29 +58,30 @@ public void testNotRunning() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, null) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER, null) // .output(DEBUG_SET_ACTIVE_POWER_RAW, null) // - .output(STATE_MACHINE, StateMachine.State.IDLE) // - ); + .output(STATE_MACHINE, State.IDLE)) // + .deactivate(); } @Test public void testAllStates() throws Exception { + final var clock = new TimeLeapClock(Instant.parse("2023-01-01T08:00:00.00Z"), ZoneOffset.UTC); new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // - .addReference("componentManager", new DummyComponentManager()) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -99,39 +89,39 @@ public void testAllStates() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(true) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 21) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) // + .input("ess0", SOC, 20) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 25) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) // + .input("ess0", SOC, 25) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 30)) // + .input("ess0", SOC, 30)) // .next(new TestCase() // - .input(ESS_SOC, 30) /// - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) // + .input("ess0", SOC, 30) /// + .output(STATE_MACHINE, State.AT_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) // - ; + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC)) // + .deactivate(); } @Test public void testCapacityCondition() throws Exception { - + final var clock = new TimeLeapClock(Instant.parse("2023-01-01T08:00:00.00Z"), ZoneOffset.UTC); var timedata = new DummyTimedata("timedata0"); var start = ZonedDateTime.of(2022, 05, 05, 0, 0, 0, 0, ZoneId.of("UTC")); @@ -139,15 +129,15 @@ public void testCapacityCondition() throws Exception { timedata.add(start.plusMinutes(60), CTRL_ESS_CAPACITY, 8_000); timedata.add(start.plusMinutes(90), CTRL_ESS_CAPACITY, 8_000); - var test = new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // - .addReference("componentManager", new DummyComponentManager()) // + new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", timedata) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -155,70 +145,66 @@ public void testCapacityCondition() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(true) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .input(ESS_MAX_APPARENT_POWER, 10000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 22) // + .input("ess0", MAX_APPARENT_POWER, 10000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 21) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 21) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) // + .input("ess0", SOC, 20) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_SOC, 25) // + .input("ess0", SOC, 25) // .input(CTRL_ESS_CAPACITY, 8_000) // - .input(ESS_CAPACITY, 8_000) // + .input("ess0", CAPACITY, 8_000) // .output(CTRL_ESS_CAPACITY, 8_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC)) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC)) // .next(new TestCase() // - .input(ESS_CAPACITY, 8_000) // - .input(ESS_SOC, 30) // + .input("ess0", CAPACITY, 8_000) // + .input("ess0", SOC, 30) // .output(CTRL_ESS_CAPACITY, 8_000)) // .next(new TestCase() // - .input(ESS_SOC, 30) /// - .input(ESS_CAPACITY, 8_000) // + .input("ess0", SOC, 30) /// + .input("ess0", CAPACITY, 8_000) // .output(CTRL_ESS_CAPACITY, 8_000) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC)) // - ; + .output(STATE_MACHINE, State.AT_TARGET_SOC)) // - // EMS restart - test.next(new TestCase() // - .input(ESS_CAPACITY, null) // - .input(CTRL_ESS_CAPACITY, null) // - .output(CTRL_ESS_CAPACITY, null)); // + .next(new TestCase("EMS restart") // + .input("ess0", CAPACITY, null) // + .input(CTRL_ESS_CAPACITY, null) // + .output(CTRL_ESS_CAPACITY, null)) // - // New Ess.Capacity (Ctrl is taking the last one from timedata) - test.next(new TestCase() // - .input(ESS_CAPACITY, 10_000) // - .input(CTRL_ESS_CAPACITY, null) // - .output(CTRL_ESS_CAPACITY, 8_000)); // + .next(new TestCase("New Ess.Capacity (Ctrl is taking the last one from timedata)") // + .input("ess0", CAPACITY, 10_000) // + .input(CTRL_ESS_CAPACITY, null) // + .output(CTRL_ESS_CAPACITY, 8_000)) // - test.next(new TestCase() // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // - ; + .next(new TestCase() // + .output(STATE_MACHINE, State.IDLE)) // + + .deactivate(); } @Test public void testAboveLimit() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -226,44 +212,42 @@ public void testAboveLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 50) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 50) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 50) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 50) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 50) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 50) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 50) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) // + .input("ess0", SOC, 50) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 500) // - .output(DEBUG_SET_ACTIVE_POWER, 500) // Would increase till 10_000 - ); + .output(DEBUG_SET_ACTIVE_POWER, 500)) // Would increase till 10_000 + .deactivate(); } @Test public void testBelowLimit() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-01-01T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -271,44 +255,42 @@ public void testBelowLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, -500) // - .output(DEBUG_SET_ACTIVE_POWER, -500) // Would increase till 10_000 - ); + .output(DEBUG_SET_ACTIVE_POWER, -500)) // Would increase till 10_000 + .deactivate(); } @Test public void testAtLimit() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -316,85 +298,85 @@ public void testAtLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, -500) // .output(DEBUG_SET_ACTIVE_POWER, -500)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1500)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1500)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2000)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2000)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2500)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2500)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -3000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -3000)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -3500)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -3500)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -4000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -4000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -5000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -5000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -6000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -6000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -7000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -7000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -8000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -8000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -10000)) // + .input("ess0", SOC, 10) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -10000)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -8000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -8000)) // .next(new TestCase()) // .next(new TestCase()) // .next(new TestCase()) // @@ -402,26 +384,24 @@ public void testAtLimit() throws Exception { .next(new TestCase()) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // - ; + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } @Test public void testAtLimitDeadBand() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -429,78 +409,76 @@ public void testAtLimitDeadBand() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 0) // .output(DEBUG_SET_ACTIVE_POWER, 0)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SOC, 30) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // - .input(ESS_SOC, 31) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SOC, 31) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // - .input(ESS_SOC, 29) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SOC, 29) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // .next(new TestCase() // - .input(ESS_SOC, 28)) // + .input("ess0", SOC, 28)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500)) // + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1500)) // + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1500)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1 * Math.round(// - Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR, + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1 * round(// + min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR, 10_000 /* capacity */ * (1f / 6f))) // 1467 - ))// - ; + )) // + .deactivate(); } @Test public void testBoundaries() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2022-10-27T08:00:00.00Z"), ZoneOffset.UTC); - final var componentManager = new DummyComponentManager(clock); - /* * Below target SoC */ new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -508,67 +486,67 @@ public void testBoundaries() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 10_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 10_000) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, -500) // .output(DEBUG_SET_ACTIVE_POWER, -500)) // .next(new TestCase() // - .input(ESS_SOC, 22) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .input("ess0", SOC, 22) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // // Skip Ramp .next(new TestCase(), 17) // .next(new TestCase() // - .input(ESS_SOC, 27) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -10000)) // + .input("ess0", SOC, 27) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -10000)) // .next(new TestCase() // - .input(ESS_SOC, 28)) // + .input("ess0", SOC, 28)) // .next(new TestCase() // - .input(ESS_SOC, 28) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -9500)) // + .input("ess0", SOC, 28) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -9500)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -9000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -9000)) // // Skip ramp .next(new TestCase(), 13) // .next(new TestCase() // - .input(ESS_SOC, 29) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2000)) // + .input("ess0", SOC, 29) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2000)) // .next(new TestCase() // - .input(ESS_SOC, 29) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1 * Math.round(// - Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR, + .input("ess0", SOC, 29) // + .output(STATE_MACHINE, State.WITHIN_LOWER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1 * round(// + min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR, 10_000 /* capacity */ * (1f / 6f))) // ))// 1667 .next(new TestCase() // - .input(ESS_SOC, 30)) // + .input("ess0", SOC, 30)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -667)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -667)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // ; /* @@ -576,13 +554,13 @@ public void testBoundaries() throws Exception { */ new ControllerTest(new ControllerEssFixStateOfChargeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", componentManager) // + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(false) // @@ -590,66 +568,66 @@ public void testBoundaries() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 40) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 40) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 40) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 40) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 40) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 40) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // .next(new TestCase() // - .input(ESS_SOC, 40) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) // + .input("ess0", SOC, 40) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 500) // .output(DEBUG_SET_ACTIVE_POWER, 500)) // .next(new TestCase() // - .input(ESS_SOC, 40) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) // + .input("ess0", SOC, 40) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) // // Skip ramp .next(new TestCase(), 18) // .next(new TestCase() // - .input(ESS_SOC, 33)) // + .input("ess0", SOC, 33)) // .next(new TestCase() // - .input(ESS_SOC, 32)) // + .input("ess0", SOC, 32)) // .next(new TestCase() // - .input(ESS_SOC, 32) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 9500)) // + .input("ess0", SOC, 32) // + .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 9500)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 9000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 9000)) // // Skip ramp .next(new TestCase(), 13) // .next(new TestCase() // - .input(ESS_SOC, 31) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 2000)) // + .input("ess0", SOC, 31) // + .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 2000)) // .next(new TestCase() // - .input(ESS_SOC, 31) // - .output(STATE_MACHINE, StateMachine.State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, Math.round(// - Math.min(10_000/* maxApparentPower */ * AbstractFixStateOfCharge.DEFAULT_POWER_FACTOR, + .input("ess0", SOC, 31) // + .output(STATE_MACHINE, State.WITHIN_UPPER_TARGET_SOC_BOUNDARIES) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, round(// + min(10_000/* maxApparentPower */ * DEFAULT_POWER_FACTOR, 10_000 /* capacity */ * (1f / 6f))) // ))// 1667 .next(new TestCase() // - .input(ESS_SOC, 30)) // + .input("ess0", SOC, 30)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 667)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 667)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // - ; + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } @Test @@ -665,8 +643,8 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception { .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -674,35 +652,35 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 10) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // // Start time = 2022-10-27T09:14:24+01:00, Current: 2022-10-27T09:00:00+01:00 .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 30_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, null) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 30_000) // + .output(STATE_MACHINE, State.NOT_STARTED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER_RAW, null) // .output(DEBUG_SET_ACTIVE_POWER, null)) // .next(new TestCase() // - .timeleap(clock, 15, ChronoUnit.MINUTES))// + .timeleap(clock, 15, MINUTES))// .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -500)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -500)) // .next(new TestCase()) // .next(new TestCase()) // .next(new TestCase()) // @@ -712,33 +690,33 @@ public void testLimitWithSpecifiedTimeBelowLimit() throws Exception { .next(new TestCase()) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -5000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -5000)) // .next(new TestCase() // - .input(ESS_SOC, 10) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 30_000) // - .output(STATE_MACHINE, StateMachine.State.BELOW_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -5040) // + .input("ess0", SOC, 10) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 30_000) // + .output(STATE_MACHINE, State.BELOW_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -5040) // .output(DEBUG_SET_ACTIVE_POWER_RAW, -5040) // .output(DEBUG_SET_ACTIVE_POWER, -5040)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -4040)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -4040)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -3040)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -3040)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2040)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2040)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1040)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1040)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -40)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -40)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // - ; + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } @Test @@ -753,8 +731,8 @@ public void testLimitWithSpecifiedTimeAboveLimit() throws Exception { .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("ess", ESS) // .activate(FixStateOfChargeConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setRunning(true) // .setTargetSoc(30) // .setSpecifyTargetTime(true) // @@ -763,85 +741,85 @@ public void testLimitWithSpecifiedTimeAboveLimit() throws Exception { .setSelfTermination(false) // .setTerminationBuffer(720) // .setConditionalTermination(false) // - .setEndCondition(EndCondition.CAPACITY_CHANGED) // + .setEndCondition(CAPACITY_CHANGED) // .build()) .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.IDLE)) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.IDLE)) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED)) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED)) // // Start time = 2022-10-27T06:26:24, Current: 2022-10-26T23:00 .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.NOT_STARTED) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, null) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.NOT_STARTED) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null) // .output(DEBUG_SET_ACTIVE_POWER_RAW, null) // .output(DEBUG_SET_ACTIVE_POWER, null)) // .next(new TestCase() // - .timeleap(clock, 7, ChronoUnit.HOURS)) // + .timeleap(clock, 7, HOURS)) // .next(new TestCase() // - .timeleap(clock, 31, ChronoUnit.MINUTES)) // + .timeleap(clock, 31, MINUTES)) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_CAPACITY, 30_000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500) // + .input("ess0", SOC, 80) // + .input("ess0", CAPACITY, 30_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 500) // .output(DEBUG_SET_ACTIVE_POWER, 500)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 2000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 2000)) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 3000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 3000)) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 4000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 4000)) // .next(new TestCase()) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 5000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 5000)) // .next(new TestCase()) // .next(new TestCase() // - .input(ESS_SOC, 80) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_CAPACITY, 30_000) // - .output(STATE_MACHINE, StateMachine.State.ABOVE_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 5128) // + .input("ess0", SOC, 80) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", CAPACITY, 30_000) // + .output(STATE_MACHINE, State.ABOVE_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 5128) // .output(DEBUG_SET_ACTIVE_POWER_RAW, 5128) // .output(DEBUG_SET_ACTIVE_POWER, 5128)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .input(ESS_MAX_APPARENT_POWER, 10_000)) // + .input("ess0", SOC, 30) // + .input("ess0", MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // - .input(ESS_SOC, 30) // - .output(STATE_MACHINE, StateMachine.State.AT_TARGET_SOC) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 4128)) // + .input("ess0", SOC, 30) // + .output(STATE_MACHINE, State.AT_TARGET_SOC) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 4128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 3128)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 3128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 2128)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 2128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 1128)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 1128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 128)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 128)) // .next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)) // - ; + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java index 009f2fb33f2..7ac9440a6ba 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java @@ -1,5 +1,20 @@ package io.openems.edge.controller.ess.gridoptimizedcharge; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_DC_ACTUAL_POWER; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.DELAY_CHARGE_STATE; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.PREDICTED_TARGET_MINUTE; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.PREDICTED_TARGET_MINUTE_ADJUSTED; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.SELL_TO_GRID_LIMIT_STATE; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.START_EPOCH_SECONDS; +import static io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedCharge.ChannelId.TARGET_MINUTE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.CAPACITY; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; import static io.openems.edge.predictor.api.prediction.Prediction.EMPTY_PREDICTION; import static java.time.temporal.ChronoUnit.DAYS; import static org.junit.Assert.assertEquals; @@ -34,8 +49,10 @@ import io.openems.edge.common.test.Plot.AxisFormat; import io.openems.edge.common.test.Plot.Data; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyHybridEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; import io.openems.edge.predictor.api.prediction.Prediction; import io.openems.edge.predictor.api.test.DummyPredictor; @@ -43,50 +60,13 @@ public class ControllerEssGridOptimizedChargeImplTest { - // Ids - private static final String CTRL_ID = "ctrlGridOptimizedCharge0"; - private static final String PREDICTOR_ID = "predictor0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - // Components - private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss(ESS_ID); - private static final DummyElectricityMeter METER = new DummyElectricityMeter(METER_ID); - private static final DummyHybridEss HYBRID_ESS = new DummyHybridEss(ESS_ID); - private static final DummyManagedSymmetricEss ESS_WITH_NONE_APPARENT_POWER = new DummyManagedSymmetricEss(ESS_ID) // + private static final DummyManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0"); + private static final DummyElectricityMeter METER = new DummyElectricityMeter("meter0"); + private static final DummyHybridEss HYBRID_ESS = new DummyHybridEss("ess0"); + private static final DummyManagedSymmetricEss ESS_WITH_NONE_APPARENT_POWER = new DummyManagedSymmetricEss("ess0") // .withMaxApparentPower(0); - // Ess channels - private static final ChannelAddress ESS_CAPACITY = new ChannelAddress(ESS_ID, "Capacity"); - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress ESS_MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower"); - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerLessOrEquals"); - - // Meter channels - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - - // Controller channels - private static final ChannelAddress PREDICTED_TARGET_MINUTE = new ChannelAddress(CTRL_ID, "PredictedTargetMinute"); - private static final ChannelAddress PREDICTED_TARGET_MINUTE_ADJUSTED = new ChannelAddress(CTRL_ID, - "PredictedTargetMinuteAdjusted"); - private static final ChannelAddress TARGET_MINUTE = new ChannelAddress(CTRL_ID, "TargetMinute"); - private static final ChannelAddress DELAY_CHARGE_STATE = new ChannelAddress(CTRL_ID, "DelayChargeState"); - private static final ChannelAddress SELL_TO_GRID_LIMIT_STATE = new ChannelAddress(CTRL_ID, "SellToGridLimitState"); - private static final ChannelAddress DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID, - "DelayChargeMaximumChargeLimit"); - private static final ChannelAddress RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID, - "RawDelayChargeMaximumChargeLimit"); - private static final ChannelAddress SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT = new ChannelAddress(CTRL_ID, - "SellToGridLimitMinimumChargeLimit"); - private static final ChannelAddress RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT = new ChannelAddress(CTRL_ID, - "RawSellToGridLimitChargeLimit"); - private static final ChannelAddress START_EPOCH_SECONDS = new ChannelAddress(CTRL_ID, "StartEpochSeconds"); - - // Sum channels - private static final ChannelAddress SUM_PRODUCTION_DC_ACTUAL_POWER = new ChannelAddress("_sum", - "ProductionDcActualPower"); private static final ChannelAddress SUM_PRODUCTION_ACTIVE_POWER = new ChannelAddress("_sum", "ProductionActivePower"); private static final ChannelAddress SUM_CONSUMPTION_ACTIVE_POWER = new ChannelAddress("_sum", @@ -157,11 +137,11 @@ public void automatic_default_predictions_at_midnight_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -173,10 +153,10 @@ public void automatic_default_predictions_at_midnight_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -185,18 +165,19 @@ public void automatic_default_predictions_at_midnight_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // .output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // .output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // Avoid low charge power + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // Avoid low charge power + .deactivate(); } @Test @@ -207,11 +188,11 @@ public void automatic_default_predictions_at_midday_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -223,10 +204,10 @@ public void automatic_default_predictions_at_midday_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -235,11 +216,11 @@ public void automatic_default_predictions_at_midday_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // .output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // @@ -248,7 +229,8 @@ public void automatic_default_predictions_at_midday_test() throws Exception { .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // // If Energy calculation would be applied on medium risk level - Predicted // available Energy is not enough to reach 100% - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700)); + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2700)) // + .deactivate(); } @Test @@ -264,11 +246,11 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -280,10 +262,10 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -293,11 +275,11 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .next(new TestCase() // .onAfterProcessImage(sleep) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // .output(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .output(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // @@ -309,7 +291,7 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .next(new TestCase() // .onAfterProcessImage(sleep) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(ESS_SOC, 21) // + .input("ess0", SOC, 21) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2683) // @@ -329,6 +311,7 @@ public void automatic_default_predictions_at_midday_averaged_test() throws Excep .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2673) // .output(RAW_DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2666)) // + .deactivate(); ; } @@ -340,11 +323,11 @@ public void automatic_default_predictions_at_evening_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -356,10 +339,10 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -368,11 +351,11 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(PREDICTED_TARGET_MINUTE, /* QuarterHour */ 68 * 15) // .input(START_EPOCH_SECONDS, 1630566000) // .input(PREDICTED_TARGET_MINUTE_ADJUSTED, /* QuarterHour */ 68 * 15 - 120) // @@ -401,7 +384,8 @@ public void automatic_default_predictions_at_evening_test() throws Exception { .output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2075)); + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 2075)) // + .deactivate(); } @Test @@ -409,8 +393,8 @@ public void automatic_no_predictions_test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); final var cm = new DummyComponentManager(clock); final var predictorManager = new DummyPredictorManager( - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) // .addReference("predictorManager", predictorManager) // @@ -420,10 +404,10 @@ public void automatic_no_predictions_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -432,15 +416,16 @@ public void automatic_no_predictions_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // - .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED)); + .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED)) // + .deactivate(); } @Test @@ -448,8 +433,8 @@ public void automatic_sell_to_grid_limit_test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); final var cm = new DummyComponentManager(clock); final var predictorManager = new DummyPredictorManager( - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) // .addReference("predictorManager", predictorManager) // @@ -459,10 +444,10 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -471,51 +456,51 @@ public void automatic_sell_to_grid_limit_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -7500) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -12000) // - .input(ESS_ACTIVE_POWER, -850) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -850) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6200) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6200) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6200) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6200) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -6200) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6200) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6550) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6550) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6550) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6550) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -5000) // - .input(ESS_ACTIVE_POWER, -6550) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6550) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6050) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6050) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6050) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6050) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -8000) // - .input(ESS_ACTIVE_POWER, -6050) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6050) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7400) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7400) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7400) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7400) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // @@ -523,22 +508,23 @@ public void automatic_sell_to_grid_limit_test() throws Exception { // Difference between last limit and current lower than the ramp - ramp is not // applied .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -7400) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -7400) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7750) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7750) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7750) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7750) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -6000) // - .input(ESS_ACTIVE_POWER, -7750) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -7750) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -7250) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -7250) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -7250) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 7250) // - .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)); + .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // + .deactivate(); } @Test @@ -546,8 +532,8 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); final var cm = new DummyComponentManager(clock); final var predictorManager = new DummyPredictorManager( - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) // .addReference("predictorManager", predictorManager) // @@ -557,10 +543,10 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -568,77 +554,78 @@ public void automatic_sell_to_grid_limit_test_with_full_ess() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7500) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 100) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 100) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -500) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -500) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -500) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 500) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -12000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, -1000) // - .input(ESS_SOC, 100) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1000) // + .input("ess0", SOC, 100) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6000) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6000) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -7000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, -6000) // - .input(ESS_SOC, 100) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SOC, 100) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6000) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6000) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6000) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -5000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, -6000) // - .input(ESS_SOC, 100) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5500) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SOC, 100) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5500) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5500) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5500) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -8000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, -5500) // - .input(ESS_SOC, 100) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6500) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -5500) // + .input("ess0", SOC, 100) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6500) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6500) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6500) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // // Difference between last limit and current lower than the ramp - ramp is not // applied - .input(METER_ACTIVE_POWER, -7000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, -6300) // - .input(ESS_SOC, 100) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6300) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6300) // + .input("ess0", SOC, 100) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6300) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6300) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6300) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -6000) // - .input(ESS_ACTIVE_POWER, -6000) // - .input(ESS_SOC, 100) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5800) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SOC, 100) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5800) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5800) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5800) // - .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)); + .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // + .deactivate(); } @Test @@ -646,8 +633,8 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); final var cm = new DummyComponentManager(clock); final var predictorManager = new DummyPredictorManager( - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // - new DummyPredictor(PREDICTOR_ID, cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_PRODUCTION_ACTIVE_POWER), // + new DummyPredictor("predictor0", cm, EMPTY_PREDICTION, SUM_CONSUMPTION_ACTIVE_POWER)); new ControllerTest(new ControllerEssGridOptimizedChargeImpl()) // .addReference("predictorManager", predictorManager) // @@ -657,10 +644,10 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -669,51 +656,51 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -7500) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.TARGET_MINUTE_NOT_CALCULATED) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -12000) // - .input(ESS_ACTIVE_POWER, -1000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -12000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -1000) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6350) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6350) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -6000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6350) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6350) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6350) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -5000) // - .input(ESS_ACTIVE_POWER, -6000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -5850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -5850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -5850) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 5850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -8000) // - .input(ESS_ACTIVE_POWER, -5500) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -8000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -5500) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6850) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6850) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // @@ -721,22 +708,23 @@ public void automatic_sell_to_grid_limit_buffer_test() throws Exception { // Difference between last limit and current lower than the ramp - ramp is not // applied .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -7000) // - .input(ESS_ACTIVE_POWER, -6300) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6300) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6650) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6650) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6650) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -6000) // - .input(ESS_ACTIVE_POWER, -6000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, -6000) // .input(START_EPOCH_SECONDS, 1630566000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -6150) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -6150) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -6150) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 6150) // - .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)); + .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT)) // + .deactivate(); } @Test @@ -747,11 +735,11 @@ public void manual_midnight_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -763,10 +751,10 @@ public void manual_midnight_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.MANUAL) // .setSellToGridLimitEnabled(true) // @@ -774,12 +762,12 @@ public void manual_midnight_test() throws Exception { .setSellToGridLimitRampPercentage(5) // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // .input(START_EPOCH_SECONDS, 1630566000) // - .input(ESS_MAX_APPARENT_POWER, 10_000)) // + .input("ess0", MAX_APPARENT_POWER, 10_000)) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // @@ -787,7 +775,8 @@ public void manual_midnight_test() throws Exception { .output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // 476 W below minimum + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // 476 W below minimum + .deactivate(); } @Test @@ -798,11 +787,11 @@ public void manual_midday_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -814,10 +803,10 @@ public void manual_midday_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.MANUAL) // .setSellToGridLimitEnabled(true) // @@ -826,17 +815,18 @@ public void manual_midday_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // .input(START_EPOCH_SECONDS, 1630566000) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .output(TARGET_MINUTE, /* QuarterHour */ 1020) // .output(DELAY_CHARGE_STATE, DelayChargeState.ACTIVE_LIMIT) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 1620)); + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 1620)) // + .deactivate(); } @Test @@ -847,11 +837,11 @@ public void hybridEss_manual_midday_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -863,10 +853,10 @@ public void hybridEss_manual_midday_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.MANUAL) // .setSellToGridLimitEnabled(true) // @@ -875,18 +865,20 @@ public void hybridEss_manual_midday_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // - .input(SUM_PRODUCTION_DC_ACTUAL_POWER, 10_000).output(TARGET_MINUTE, /* QuarterHour */ 1020) // + .input(PRODUCTION_DC_ACTUAL_POWER, 10_000) // + .output(TARGET_MINUTE, /* QuarterHour */ 1020) // .output(DELAY_CHARGE_STATE, DelayChargeState.NO_CHARGE_LIMIT) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 3350) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_FIXED) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)); + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)) // + .deactivate(); } @@ -898,11 +890,11 @@ public void mode_off_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -914,10 +906,10 @@ public void mode_off_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.OFF) // .setSellToGridLimitEnabled(true) // @@ -926,16 +918,17 @@ public void mode_off_test() throws Exception { .build()) // .next(new TestCase() // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, -7500) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // - .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -7500) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // .input(START_EPOCH_SECONDS, 1630566000) // .output(DELAY_CHARGE_STATE, DelayChargeState.DISABLED) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.ACTIVE_LIMIT_CONSTRAINT) // .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, -850) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, -850) // - .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850)); // + .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, 850)) // + .deactivate(); } @Test @@ -946,11 +939,11 @@ public void no_capacity_left_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -962,10 +955,10 @@ public void no_capacity_left_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.AUTOMATIC) // .setSellToGridLimitEnabled(true) // @@ -973,19 +966,20 @@ public void no_capacity_left_test() throws Exception { .setManualTargetTime("") // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // .input(SUM_PRODUCTION_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 99) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 99) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // .input(START_EPOCH_SECONDS, 1630566000) // // ess.getPower().getMinPower() (Maximum allowed charge power) is '0' because // the referenced // DummyManagedSymmetricEss has an apparent power of zero. .output(DELAY_CHARGE_STATE, DelayChargeState.NO_REMAINING_CAPACITY) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)); // + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, null)) // + .deactivate(); } @Test @@ -1001,11 +995,11 @@ public void start_production_not_enough_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -1017,10 +1011,10 @@ public void start_production_not_enough_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.OFF) // .setSellToGridLimitEnabled(true) // @@ -1047,7 +1041,8 @@ public void start_production_not_enough_test() throws Exception { .input(SUM_CONSUMPTION_ACTIVE_POWER, 6000) // .output(DELAY_CHARGE_STATE, DelayChargeState.NOT_STARTED) // .output(SELL_TO_GRID_LIMIT_STATE, SellToGridLimitState.NOT_STARTED) // - .output(START_EPOCH_SECONDS, null)); // + .output(START_EPOCH_SECONDS, null)) // + .deactivate(); } @Test @@ -1063,11 +1058,11 @@ public void start_production_average_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, now, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, now, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); @@ -1079,10 +1074,10 @@ public void start_production_average_test() throws Exception { .addReference("meter", METER) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setEssId(ESS_ID) // - .setId(CTRL_ID) // + .setEssId("ess0") // + .setId("ctrlGridOptimizedCharge0") // .setMaximumSellToGridPower(7_000) // - .setMeterId(METER_ID) // + .setMeterId("meter0") // .setDelayChargeRiskLevel(DelayChargeRiskLevel.MEDIUM) // .setMode(Mode.MANUAL) // .setSellToGridLimitEnabled(true) // @@ -1188,11 +1183,11 @@ public void start_production_average_test() throws Exception { .input(SUM_PRODUCTION_ACTIVE_POWER, 2000) // Avg: 1166 .input(SUM_CONSUMPTION_ACTIVE_POWER, 1000) // .input(START_EPOCH_SECONDS, null) // - .input(METER_ACTIVE_POWER, 0) // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_CAPACITY, 10_000) // - .input(ESS_SOC, 20) // - .input(ESS_MAX_APPARENT_POWER, 10_000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", CAPACITY, 10_000) // + .input("ess0", SOC, 20) // + .input("ess0", MAX_APPARENT_POWER, 10_000) // // Epoch seconds at 2020-01-01 00:00:00: 1577836800 (Clock is not updated) .output(START_EPOCH_SECONDS, 1577836800L) // @@ -1200,12 +1195,12 @@ public void start_production_average_test() throws Exception { .output(DELAY_CHARGE_STATE, DelayChargeState.AVOID_LOW_CHARGING) // .output(RAW_SELL_TO_GRID_LIMIT_CHARGE_LIMIT, 6650) // .output(SELL_TO_GRID_LIMIT_MINIMUM_CHARGE_LIMIT, -6650) // - .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)); // 506 W is not efficient + .output(DELAY_CHARGE_MAXIMUM_CHARGE_LIMIT, 0)) // 506 W is not efficient + .deactivate(); } @Test public void getCalculatedPowerLimit_middayTest() throws Exception { - /* * Initial values */ @@ -1671,11 +1666,11 @@ public void calculateAvailEnergy_test() throws Exception { final var sum = new DummySum(); final var predictorManager = new DummyPredictorManager( // Production - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_PRODUCTION_ACTIVE_POWER, midnight, DEFAULT_PRODUCTION_PREDICTION), SUM_PRODUCTION_ACTIVE_POWER), // Consumption - new DummyPredictor(PREDICTOR_ID, cm, + new DummyPredictor("predictor0", cm, Prediction.from(sum, SUM_CONSUMPTION_ACTIVE_POWER, midnight, DEFAULT_CONSUMPTION_PREDICTION), SUM_CONSUMPTION_ACTIVE_POWER)); diff --git a/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java b/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java index de89c02eda6..94d0e84fbb1 100644 --- a/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java +++ b/io.openems.edge.controller.ess.hybrid.surplusfeedtogrid/test/io/openems/edge/controller/ess/hybrid/surplusfeedtogrid/ControllerEssHybridSurplusFeedToGridImplTest.java @@ -1,8 +1,10 @@ package io.openems.edge.controller.ess.hybrid.surplusfeedtogrid; +import static io.openems.edge.controller.ess.hybrid.surplusfeedtogrid.ControllerEssHybridSurplusFeedToGrid.ChannelId.SURPLUS_FEED_TO_GRID_IS_LIMITED; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_GREATER_OR_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; @@ -10,41 +12,33 @@ public class ControllerEssHybridSurplusFeedToGridImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final ChannelAddress CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED = new ChannelAddress(CTRL_ID, - "SurplusFeedToGridIsLimited"); - - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerGreaterOrEquals"); - @Test public void test() throws Exception { - final var ess = new DummyHybridEss(ESS_ID); + final var ess = new DummyHybridEss("ess0"); final var test = new ControllerTest(new ControllerEssHybridSurplusFeedToGridImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .build()); ess.withSurplusPower(null); test.next(new TestCase() // - .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)); + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)); ess.withSurplusPower(5000); ess.withMaxApparentPower(10000); test.next(new TestCase() // - .output(CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED, false) // - .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, 5000)); + .output(SURPLUS_FEED_TO_GRID_IS_LIMITED, false) // + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, 5000)); ess.withSurplusPower(5000); ess.withMaxApparentPower(2000); test.next(new TestCase() // - .output(CTRL_SURPLUS_FEED_TO_GRID_IS_LIMITED, true) // - .output(ESS_SET_ACTIVE_POWER_GREATER_OR_EQUALS, 2000)); + .output(SURPLUS_FEED_TO_GRID_IS_LIMITED, true) // + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, 2000)) // + + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java index a9c1082637f..f9ccb3b36ea 100644 --- a/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java +++ b/io.openems.edge.controller.ess.limiter14a/src/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aImpl.java @@ -11,6 +11,7 @@ import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.Designate; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.channel.BooleanReadChannel; diff --git a/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java b/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java index 22405060dc7..b7d9cae4a52 100644 --- a/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java +++ b/io.openems.edge.controller.ess.limiter14a/test/io/openems/edge/controller/ess/limiter14a/ControllerEssLimiter14aTest.java @@ -1,61 +1,57 @@ package io.openems.edge.controller.ess.limiter14a; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MODE; +import static io.openems.edge.controller.ess.limiter14a.ControllerEssLimiter14a.ChannelId.RESTRICTION_MODE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_GREATER_OR_EQUALS; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; + import org.junit.Test; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.sum.GridMode; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.ess.test.DummyManagedSymmetricEss; -import io.openems.edge.timedata.test.DummyTimedata; import io.openems.edge.io.test.DummyInputOutput; +import io.openems.edge.timedata.test.DummyTimedata; public class ControllerEssLimiter14aTest { - private static final String ESS_ID = "ess0"; - private static final String CTRL_ID = "ctrlEssLimiter14a0"; - - private static final ChannelAddress RESTRICTION_MODE = new ChannelAddress(CTRL_ID, "RestrictionMode"); - private static final ChannelAddress GPIO = new ChannelAddress("io0", "InputOutput0"); - private static final ChannelAddress LIMITATION = new ChannelAddress(ESS_ID, "SetActivePowerGreaterOrEquals"); - private static final ChannelAddress GRID_MODE = new ChannelAddress("_sum", "GridMode"); - @Test public void testController() throws OpenemsException, Exception { new ControllerTest(new ControllerEssLimiter14aImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .addReference("sum", new DummySum()) // .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID)// + .setId("ctrl0") // + .setEssId("ess0")// .setInputChannelAddress("io0/InputOutput0")// .build()) .next(new TestCase() // // Since logic is reversed - .input(GPIO, false) // - .input(GRID_MODE, GridMode.ON_GRID) - .output(LIMITATION, -4200) + .input("io0", INPUT_OUTPUT0, false) // + .input(GRID_MODE, GridMode.ON_GRID) // + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, -4200) // .output(RESTRICTION_MODE, RestrictionMode.ON)) // .next(new TestCase() // - .input(GPIO, null) // - .output(LIMITATION, null)) // + .input("io0", INPUT_OUTPUT0, null) // + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)) // .next(new TestCase() // - .input(GPIO, 1) // + .input("io0", INPUT_OUTPUT0, 1) // .input(GRID_MODE, GridMode.OFF_GRID) // .output(RESTRICTION_MODE, RestrictionMode.OFF)) // .next(new TestCase() // - .input(GPIO, false) // + .input("io0", INPUT_OUTPUT0, false) // .input(GRID_MODE, GridMode.OFF_GRID) // - .output(LIMITATION, null)) // - ; + .output("ess0", SET_ACTIVE_POWER_GREATER_OR_EQUALS, null)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java b/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java index d86836ed672..f240abc3ed9 100644 --- a/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java +++ b/io.openems.edge.controller.ess.limittotaldischarge/test/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImplTest.java @@ -1,11 +1,14 @@ package io.openems.edge.controller.ess.limittotaldischarge; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge.ChannelId.AWAITING_HYSTERESIS; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import java.time.temporal.ChronoUnit; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -13,47 +16,39 @@ public class ControllerEssLimitTotalDischargeImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final ChannelAddress CTRL_AWAITING_HYSTERESIS = new ChannelAddress(CTRL_ID, "AwaitingHysteresis"); - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerLessOrEquals"); - @Test public void test() throws Exception { - // Initialize mocked Clock - final var clock = new TimeLeapClock(); + final var clock = createDummyClock(); new ControllerTest(new ControllerEssLimitTotalDischargeImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .withSoc(20) // .withCapacity(9000)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMinSoc(15) // .setForceChargeSoc(10) // .setForceChargePower(1000) // .build()) .next(new TestCase() // - .input(ESS_SOC, 20) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// - .output(CTRL_AWAITING_HYSTERESIS, false)) // + .input("ess0", SOC, 20) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)// + .output(AWAITING_HYSTERESIS, false)) // .next(new TestCase() // - .input(ESS_SOC, 15) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) // - .output(CTRL_AWAITING_HYSTERESIS, false)) // + .input("ess0", SOC, 15) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) // + .output(AWAITING_HYSTERESIS, false)) // .next(new TestCase() // - .input(ESS_SOC, 16) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) // - .output(CTRL_AWAITING_HYSTERESIS, true)) // + .input("ess0", SOC, 16) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 0) // + .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase() // .timeleap(clock, 6, ChronoUnit.MINUTES) // - .input(ESS_SOC, 16) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // - .output(CTRL_AWAITING_HYSTERESIS, false)); + .input("ess0", SOC, 16) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null) // + .output(AWAITING_HYSTERESIS, false)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java b/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java index 676304b3b6e..6e85c146ed7 100644 --- a/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java +++ b/io.openems.edge.controller.ess.linearpowerband/test/io/openems/edge/controller/ess/linearpowerband/ControllerEssLinearPowerBandImplTest.java @@ -1,8 +1,10 @@ package io.openems.edge.controller.ess.linearpowerband; +import static io.openems.edge.controller.ess.linearpowerband.ControllerEssLinearPowerBand.ChannelId.STATE_MACHINE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; @@ -10,21 +12,14 @@ public class ControllerEssLinearPowerBandImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssLinearPowerBandImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMinPower(-1000) // .setMaxPower(1000) // .setAdjustPower(300) // @@ -32,49 +27,49 @@ public void test() throws Exception { .build()) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -300)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -300)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -600)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -600)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -900)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -900)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -1000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -1000)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -700)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -700)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -400)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -400)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -100)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -100)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 200)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 200)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 500)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 500)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 800)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 800)) // .next(new TestCase() // .output(STATE_MACHINE, State.UPWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 1000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 1000)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 700)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 700)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 400)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 400)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 100)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 100)) // .next(new TestCase() // .output(STATE_MACHINE, State.DOWNWARDS) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -200)) // - ; + .output("ess0", SET_ACTIVE_POWER_EQUALS, -200)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java b/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java index e2a90e1983b..53afa7614c7 100644 --- a/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java +++ b/io.openems.edge.controller.ess.mindischargeperiod/test/io/openems/edge/controller/ess/mindischargeperiod/ControllerEssMinimumDischargePowerImplTest.java @@ -7,21 +7,18 @@ public class ControllerEssMinimumDischargePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEssMinimumDischargePowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setActivateDischargePower(0) // .setDischargeTime(0) // .setMinDischargePower(0) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java b/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java index 1b4492ede55..442be9ddfa2 100644 --- a/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java +++ b/io.openems.edge.controller.ess.reactivepowervoltagecharacteristic/test/io/openems/edge/controller/ess/reactivepowervoltagecharacteristic/ControllerEssReactivePowerVoltageCharacteristicImplTest.java @@ -1,14 +1,15 @@ package io.openems.edge.controller.ess.reactivepowervoltagecharacteristic; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_REACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.VOLTAGE; +import static java.time.temporal.ChronoUnit.SECONDS; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.common.utils.JsonUtils; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -18,86 +19,79 @@ public class ControllerEssReactivePowerVoltageCharacteristicImplTest { - private static final String CTRL_ID = "ctrlReactivePowerVoltageCharacteristic0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - private static final ChannelAddress ESS_REACTIVE_POWER = new ChannelAddress(ESS_ID, "SetReactivePowerEquals"); - private static final ChannelAddress METER_VOLTAGE = new ChannelAddress(METER_ID, "Voltage"); - private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(ESS_ID, "MaxApparentPower"); - @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-10-05T14:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerEssReactivePowerVoltageCharacteristicImpl())// .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // + .addReference("meter", new DummyElectricityMeter("meter0")) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create()// - .setId(CTRL_ID)// - .setEssId(ESS_ID)// - .setMeterId(METER_ID)// + .setId("ctrl0")// + .setEssId("ess0")// + .setMeterId("meter0")// .setNominalVoltage(240)// .setWaitForHysteresis(5)// - .setPowerVoltConfig(JsonUtils.buildJsonArray()// - .add(JsonUtils.buildJsonObject()// + .setPowerVoltConfig(buildJsonArray()// + .add(buildJsonObject()// .addProperty("voltageRatio", 0.9) // .addProperty("percent", 60) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 0.93) // .addProperty("percent", 0) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 1.07) // .addProperty("percent", 0) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("voltageRatio", 1.1) // .addProperty("percent", -60) // .build() // ).build().toString() // ).build()) // .next(new TestCase("First Input") // - .input(METER_VOLTAGE, 240_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000)) // [VA] + .input("meter0", VOLTAGE, 240_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000)) // [VA] .next(new TestCase() // - .output(ESS_REACTIVE_POWER, 0))// + .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))// .next(new TestCase("Second Input") // - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 216_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 6000))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 216_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 6000))// .next(new TestCase("Third Input")// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 220_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 2600))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 220_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 2600))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 223_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 100))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 223_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 100))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 223_200) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 0))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 223_200) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 256_800) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, 0))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 256_800) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, 0))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 260_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, -2600))// + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 260_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, -2600))// .next(new TestCase()// - .timeleap(clock, 5, ChronoUnit.SECONDS)// - .input(METER_VOLTAGE, 264_000) // [mV] - .input(MAX_APPARENT_POWER, 10_000) // [VA] - .output(ESS_REACTIVE_POWER, -6000))// - ; + .timeleap(clock, 5, SECONDS)// + .input("meter0", VOLTAGE, 264_000) // [mV] + .input("ess0", MAX_APPARENT_POWER, 10_000) // [VA] + .output("ess0", SET_REACTIVE_POWER_EQUALS, -6000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java b/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java index 5c191d0c4f5..0870799ee2a 100644 --- a/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java +++ b/io.openems.edge.controller.ess.selltogridlimit/test/io/openems/edge/controller/symmetric/selltogridlimit/ControllerEssSellToGridLimitImplTest.java @@ -1,47 +1,39 @@ package io.openems.edge.controller.symmetric.selltogridlimit; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_LESS_OR_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssSellToGridLimitImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerLessOrEquals"); - - private static final String METER_ID = "meter00"; - - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssSellToGridLimitImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setMaximumSellToGridPower(5000) // .build()) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -5000) // - .input(ESS_ACTIVE_POWER, 3000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -5000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, null)) // .next(new TestCase() // - .input(METER_ACTIVE_POWER, -6000) // - .input(ESS_ACTIVE_POWER, 3000) // - .output(ESS_SET_ACTIVE_POWER_LESS_OR_EQUALS, 2000)); + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -6000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3000) // + .output("ess0", SET_ACTIVE_POWER_LESS_OR_EQUALS, 2000)) // + .deactivate(); } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java b/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java index 6f3053a0b16..ac45a6cbb7e 100644 --- a/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java +++ b/io.openems.edge.controller.ess.standby/test/io/openems/edge/controller/ess/standby/ControllerEssStandbyImplTest.java @@ -1,5 +1,15 @@ package io.openems.edge.controller.ess.standby; +import static io.openems.edge.common.sum.Sum.ChannelId.CONSUMPTION_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_ACTIVE_POWER; +import static io.openems.edge.controller.ess.standby.ControllerEssStandby.ChannelId.STATE_MACHINE; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.ACTIVE_POWER; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; + import java.time.DayOfWeek; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -9,10 +19,8 @@ import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.sum.GridMode; -import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.ess.standby.statemachine.StateMachine.State; @@ -23,23 +31,6 @@ public class ControllerEssStandbyImplTest { private static final int MAX_APPARENT_POWER = 50_000; // [W] - private static final String CTRL_ID = "ctrlEssStandby0"; - private static final ChannelAddress STATE_MACHINE = new ChannelAddress(CTRL_ID, "StateMachine"); - - private static final String SUM_ID = Sum.SINGLETON_COMPONENT_ID; - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress(SUM_ID, "GridActivePower"); - private static final ChannelAddress SUM_PRODUCTION_ACTIVE_POWER = new ChannelAddress(SUM_ID, - "ProductionActivePower"); - private static final ChannelAddress SUM_CONSUMPTION_ACTIVE_POWER = new ChannelAddress(SUM_ID, - "ConsumptionActivePower"); - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - private static final ChannelAddress ESS_ALLOWED_CHARGE_POWER = new ChannelAddress(ESS_ID, "AllowedChargePower"); - - // Initialize mocked Clock private static TimeLeapClock clock; @Before @@ -49,7 +40,7 @@ public void initialize() { private static ControllerTest tillDischarge() throws Exception { // Initialize ESS - final var ess = new DummyManagedSymmetricEss(ESS_ID) // + final var ess = new DummyManagedSymmetricEss("ess0") // .withGridMode(GridMode.ON_GRID) // .withMaxApparentPower(MAX_APPARENT_POWER) // .withSoc(70); @@ -59,8 +50,8 @@ private static ControllerTest tillDischarge() throws Exception { .addReference("sum", new DummySum()) // .addComponent(ess) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setStartDate("01.02.2020") // .setEndDate("01.03.2020") // .setDayOfWeek(DayOfWeek.SUNDAY) // @@ -68,7 +59,7 @@ private static ControllerTest tillDischarge() throws Exception { .next(new TestCase() // .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase("1 second before midnight (friday) before 01.02.2020") // - .timeleap(clock, 10, ChronoUnit.MINUTES) // + .timeleap(clock, 10, MINUTES) // .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase("midnight (saturday)") // .timeleap(clock, 1, ChronoUnit.SECONDS) // @@ -80,113 +71,117 @@ private static ControllerTest tillDischarge() throws Exception { * DISCHARGE */ .next(new TestCase("sunday -> switch to DISCHARGE") // - .input(SUM_GRID_ACTIVE_POWER, 10_000 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 100 /* discharge */) // + .input(GRID_ACTIVE_POWER, 10_000 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 100 /* discharge */) // .output(STATE_MACHINE, State.DISCHARGE) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 10_100)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 10_100)) // .next(new TestCase("discharge > 70 % of maxApparentPower") // - .timeleap(clock, 30, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, 29_900 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 10_100 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 40_000)) // + .timeleap(clock, 30, MINUTES) // + .input(GRID_ACTIVE_POWER, 29_900 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 10_100 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 40_000)) // .next(new TestCase("discharge > 70 % of maxApparentPower - 9 minutes") // - .timeleap(clock, 9, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 40_000 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 40_000)) // + .timeleap(clock, 9, MINUTES) // + .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 40_000 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 40_000)) // .next(new TestCase("discharge > 70 % of maxApparentPower - 10 minutes: reduce to 50 %") // - .timeleap(clock, 1, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 40_000 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * 0.5))) + .timeleap(clock, 1, MINUTES) // + .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 40_000 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * 0.5))) .next(new TestCase("do not charge") // - .timeleap(clock, 1, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, -100 /* buy from grid */) // - .input(ESS_ACTIVE_POWER, 0 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)); + .timeleap(clock, 1, MINUTES) // + .input(GRID_ACTIVE_POWER, -100 /* buy from grid */) // + .input("ess0", ACTIVE_POWER, 0 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 0)) // + .deactivate(); } private static ControllerTest tillSlowCharge1_1() throws Exception { return tillDischarge() // .next(new TestCase("production > consumption") // - .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) // - .input(SUM_PRODUCTION_ACTIVE_POWER, 1000) // - .input(SUM_CONSUMPTION_ACTIVE_POWER, 999) // - .input(ESS_ACTIVE_POWER, 10_000 /* discharge */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 10_000)) // + .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) // + .input(PRODUCTION_ACTIVE_POWER, 1000) // + .input(CONSUMPTION_ACTIVE_POWER, 999) // + .input("ess0", ACTIVE_POWER, 10_000 /* discharge */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 10_000)) // .next(new TestCase("production > consumption: more than 1 minute -> SLOW_CHARGE") // - .timeleap(clock, 1, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, 0 /* buy from grid */) // - .input(SUM_PRODUCTION_ACTIVE_POWER, 1000) // - .input(SUM_CONSUMPTION_ACTIVE_POWER, 999)) // + .timeleap(clock, 1, MINUTES) // + .input(GRID_ACTIVE_POWER, 0 /* buy from grid */) // + .input(PRODUCTION_ACTIVE_POWER, 1000) // + .input(CONSUMPTION_ACTIVE_POWER, 999)) // /* * SLOW_CHARGE */ .next(new TestCase("SLOW_CHARGE") // - .output(STATE_MACHINE, State.SLOW_CHARGE_1)); // + .output(STATE_MACHINE, State.SLOW_CHARGE_1)) // + .deactivate(); } private static ControllerTest tillSlowCharge1_2() throws Exception { return tillDischarge() // .next(new TestCase("latest at 12 -> SLOW_CHARGE") // - .timeleap(clock, 12, ChronoUnit.HOURS)) // + .timeleap(clock, 12, HOURS)) // /* * SLOW_CHARGE */ .next(new TestCase("SLOW_CHARGE") // - .output(STATE_MACHINE, State.SLOW_CHARGE_1)); // + .output(STATE_MACHINE, State.SLOW_CHARGE_1)) // + .deactivate(); } private static ControllerTest tillSlowCharge2_1() throws Exception { return tillSlowCharge1_2() // .next(new TestCase("") // - .input(ESS_ACTIVE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -20_000 /* sell to grid */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -20_000)) // + .input("ess0", ACTIVE_POWER, 0) // + .input(GRID_ACTIVE_POWER, -20_000 /* sell to grid */) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -20_000)) // .next(new TestCase("Charge with minimum 20 %") // - .input(ESS_ACTIVE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.19) /* sell to grid */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.20))) // + .input("ess0", ACTIVE_POWER, 0) // + .input(GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.19)) // sell to grid + .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.20))) // .next(new TestCase("Charge with maximum 50 %") // - .input(ESS_ACTIVE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.51) /* sell to grid */) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.50))) // + .input("ess0", ACTIVE_POWER, 0) // + .input(GRID_ACTIVE_POWER, (int) (MAX_APPARENT_POWER * -0.51)) // sell to grid + .output("ess0", SET_ACTIVE_POWER_EQUALS, (int) (MAX_APPARENT_POWER * -0.50))) // .next(new TestCase("after 30 minutes -> FAST_CHARGE") // - .timeleap(clock, 30, ChronoUnit.MINUTES)) // + .timeleap(clock, 30, MINUTES)) // /* * FAST_CHARGE */ .next(new TestCase("FAST_CHARGE with max power") // .output(STATE_MACHINE, State.FAST_CHARGE) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, MAX_APPARENT_POWER * -1)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, MAX_APPARENT_POWER * -1)) // .next(new TestCase("after 10 minutes -> SLOW_CHARGE_2") // - .timeleap(clock, 10, ChronoUnit.MINUTES)) // + .timeleap(clock, 10, MINUTES)) // /* * SLOW_CHARGE_2 */ .next(new TestCase("SLOW_CHARGE_2") // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_ALLOWED_CHARGE_POWER, -60_000) // - .input(SUM_GRID_ACTIVE_POWER, -20_000 /* sell to grid */) // + .input("ess0", ACTIVE_POWER, 0) // + .input("ess0", ALLOWED_CHARGE_POWER, -60_000) // + .input(GRID_ACTIVE_POWER, -20_000 /* sell to grid */) // .output(STATE_MACHINE, State.SLOW_CHARGE_2) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -20_000)) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -20_000)) // /* * FINISHED */ .next(new TestCase("no more charging allowed -> FINISHED") // - .input(ESS_ALLOWED_CHARGE_POWER, 0)) // + .input("ess0", ALLOWED_CHARGE_POWER, 0)) // .next(new TestCase("FINISHED") // .output(STATE_MACHINE, State.FINISHED)) // /* * UNDEFINED */ .next(new TestCase("on day change -> UNDEFINED") // - .timeleap(clock, 11, ChronoUnit.HOURS)) // + .timeleap(clock, 11, HOURS)) // .next(new TestCase("UNDEFINED") // .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase("After test period") // .timeleap(clock, 30, ChronoUnit.DAYS) // - .output(STATE_MACHINE, State.UNDEFINED)); // + .output(STATE_MACHINE, State.UNDEFINED)) // + .deactivate(); } @Test diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java index d3f32289678..a0623215e0f 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java @@ -1,16 +1,14 @@ package io.openems.edge.controller.ess.timeofusetariff; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static io.openems.edge.controller.ess.timeofusetariff.ControlMode.CHARGE_CONSUMPTION; import static io.openems.edge.controller.ess.timeofusetariff.Mode.AUTOMATIC; import static io.openems.edge.controller.ess.timeofusetariff.RiskLevel.MEDIUM; import java.time.Clock; -import java.time.Instant; -import java.time.ZoneOffset; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; @@ -22,13 +20,11 @@ public class TimeOfUseTariffControllerImplTest { - public static final Clock CLOCK = new TimeLeapClock(Instant.parse("2020-03-04T14:19:00.00Z"), ZoneOffset.UTC); - - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { - create(CLOCK); + final var clock = createDummyClock(); + create(clock) // + .deactivate(); } /** @@ -54,7 +50,7 @@ public static TimeOfUseTariffControllerImpl create(Clock clock) throws Exception .withSoc(60) // .withCapacity(10000)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setEnabled(false) // .setEssId("ess0") // .setMode(AUTOMATIC) // diff --git a/io.openems.edge.controller.evcs.fixactivepower/bnd.bnd b/io.openems.edge.controller.evcs.fixactivepower/bnd.bnd index 98e4180dcd7..ba83284def5 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/bnd.bnd +++ b/io.openems.edge.controller.evcs.fixactivepower/bnd.bnd @@ -8,7 +8,8 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java b/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java index e3f8ed55368..85c46e49269 100644 --- a/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java +++ b/io.openems.edge.controller.evcs.fixactivepower/test/io/openems/edge/controller/evcs/fixactivepower/ControllerEvcsFixActivePowerImplTest.java @@ -7,19 +7,16 @@ public class ControllerEvcsFixActivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String EVCS_ID = "evcs0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEvcsFixActivePowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEvcsId(EVCS_ID) // + .setId("ctrl0") // + .setEvcsId("evcs0") // .setPower(0) // .setUpdateFrequency(1) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.evcs/bnd.bnd b/io.openems.edge.controller.evcs/bnd.bnd index c3a69ba9f20..e0be766da07 100644 --- a/io.openems.edge.controller.evcs/bnd.bnd +++ b/io.openems.edge.controller.evcs/bnd.bnd @@ -9,7 +9,8 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.common,\ io.openems.edge.controller.api,\ io.openems.edge.ess.api,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ChargingLowerThanTargetHandler.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ChargingLowerThanTargetHandler.java index 418c50c5e0d..f052d2615bd 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ChargingLowerThanTargetHandler.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ChargingLowerThanTargetHandler.java @@ -64,7 +64,7 @@ protected boolean isLower(ManagedEvcs evcs) throws InvalidValueException { * @throws InvalidValueException invalidValueException */ protected boolean isChargingLowerThanTarget(ManagedEvcs evcs) throws InvalidValueException { - int chargePower = evcs.getChargePower().orElse(0); + int chargePower = evcs.getActivePower().orElse(0); int chargePowerTarget = evcs.getSetChargePowerLimit().orElse(evcs.getMaximumHardwarePower().getOrError()); if (chargePowerTarget - chargePower > chargePowerTarget * CHARGING_TARGET_MAX_DIFFERENCE_PERCENT) { diff --git a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java index 69e67eae105..4a1d1dd5653 100644 --- a/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java +++ b/io.openems.edge.controller.evcs/src/io/openems/edge/controller/evcs/ControllerEvcsImpl.java @@ -193,7 +193,7 @@ public void run() throws OpenemsNamedException { */ if (nextChargePower != 0) { - int chargePower = this.evcs.getChargePower().orElse(0); + int activePower = this.evcs.getActivePower().orElse(0); /** * Check the difference of the current charge power and the previous charging @@ -211,10 +211,10 @@ public void run() throws OpenemsNamedException { int currMax = this.evcs.getMaximumPower().orElse(0); /** - * If the charge power would increases again above the current maximum power, it - * resets the maximum Power. + * If the power would increases again above the current maximum power, it resets + * the maximum Power. */ - if (chargePower > currMax * (1 + DEFAULT_UPPER_TARGET_DIFFERENCE_PERCENT)) { + if (activePower > currMax * (1 + DEFAULT_UPPER_TARGET_DIFFERENCE_PERCENT)) { this.evcs._setMaximumPower(null); } } @@ -271,7 +271,7 @@ private void adaptConfigToHardwareLimits() { private static int calculateChargePowerFromExcessPower(Sum sum, ManagedEvcs evcs) throws OpenemsNamedException { int buyFromGrid = sum.getGridActivePower().orElse(0); int essDischarge = sum.getEssDischargePower().orElse(0); - int evcsCharge = evcs.getChargePower().orElse(0); + int evcsCharge = evcs.getActivePower().orElse(0); return evcsCharge - buyFromGrid - essDischarge; } @@ -285,7 +285,7 @@ private static int calculateChargePowerFromExcessPower(Sum sum, ManagedEvcs evcs */ private static int calculateExcessPowerAfterEss(Sum sum, ManagedEvcs evcs) { int buyFromGrid = sum.getGridActivePower().orElse(0); - int evcsCharge = evcs.getChargePower().orElse(0); + int evcsCharge = evcs.getActivePower().orElse(0); var result = evcsCharge - buyFromGrid; diff --git a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java index 960a2c5789c..b946f3090e8 100644 --- a/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java +++ b/io.openems.edge.controller.evcs/test/io/openems/edge/controller/evcs/ControllerEvcsImplTest.java @@ -1,21 +1,30 @@ package io.openems.edge.controller.evcs; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_DISCHARGE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.controller.evcs.ControllerEvcs.ChannelId.AWAITING_HYSTERESIS; +import static io.openems.edge.controller.evcs.Priority.CAR; +import static io.openems.edge.evcs.api.ChargeMode.EXCESS_POWER; +import static io.openems.edge.evcs.api.ChargeMode.FORCE_CHARGE; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MAXIMUM_HARDWARE_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MAXIMUM_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.STATUS; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.IS_CLUSTERED; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_REQUEST; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static java.time.temporal.ChronoUnit.MINUTES; import static org.junit.Assert.assertEquals; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; - import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.filter.DisabledRampFilter; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; -import io.openems.edge.evcs.api.ChargeMode; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.test.DummyEvcsPower; @@ -26,131 +35,109 @@ public class ControllerEvcsImplTest { private static final DummyEvcsPower EVCS_POWER = new DummyEvcsPower(new DisabledRampFilter()); private static final DummyManagedEvcs EVCS = new DummyManagedEvcs("evcs0", EVCS_POWER); - private static final String EVCS_ID = EVCS.id(); - private static final boolean DEFAULT_ENABLE_CHARGING = true; - private static final ChargeMode DEFAULT_CHARGE_MODE = ChargeMode.EXCESS_POWER; private static final int DEFAULT_FORCE_CHARGE_MIN_POWER = 7360; private static final int DEFAULT_CHARGE_MIN_POWER = 0; - private static final Priority DEFAULT_PRIORITY = Priority.CAR; - private static final int DEFAULT_ENERGY_SESSION_LIMIT = 0; - - private static ChannelAddress sumGridActivePower = new ChannelAddress("_sum", "GridActivePower"); - private static ChannelAddress sumEssDischargePower = new ChannelAddress("_sum", "EssDischargePower"); - private static ChannelAddress sumEssSoc = new ChannelAddress("_sum", "EssSoc"); - private static ChannelAddress evcs0ChargePower = new ChannelAddress("evcs0", "ChargePower"); - private static ChannelAddress evcs0SetChargePowerLimit = new ChannelAddress("evcs0", "SetChargePowerLimit"); - private static ChannelAddress evcs0MaximumPower = new ChannelAddress("evcs0", "MaximumPower"); - private static ChannelAddress evcs0IsClustered = new ChannelAddress("evcs0", "IsClustered"); - private static ChannelAddress evcs0SetPowerRequest = new ChannelAddress("evcs0", "SetChargePowerRequest"); - private static ChannelAddress evcs0Status = new ChannelAddress("evcs0", "Status"); - private static ChannelAddress evcs0MaximumHardwarePower = new ChannelAddress("evcs0", "MaximumHardwarePower"); - private static ChannelAddress evcs0MinimumHardwarePower = new ChannelAddress("evcs0", "MinimumHardwarePower"); - private static ChannelAddress evcsController0AwaitingHysteresis = new ChannelAddress("ctrlEvcs0", - "AwaitingHysteresis"); @Test public void excessChargeTest1() throws Exception { - new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("evcs", EVCS) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, false) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .output(evcs0SetChargePowerLimit, 6000)) // - ; + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, false) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 6000)) // + .deactivate(); } @Test public void excessChargeTest2() throws Exception { - - final var test = new ControllerTest(new ControllerEvcsImpl()) // + new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("evcs", EVCS) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // .setPriority(Priority.STORAGE) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // - .build()); // - - test.next(new TestCase() // - .input(sumEssSoc, 50) // - .input(sumEssDischargePower, -5000) // - .input(evcs0IsClustered, false) // - .input(sumGridActivePower, -40000) // - .input(evcs0ChargePower, 5000) // - .input(evcs0MaximumHardwarePower, 22080) // - .output(evcs0SetChargePowerLimit, 44800)); + .setEnergySessionLimit(0) // + .build()) // + .next(new TestCase() // + .input(ESS_SOC, 50) // + .input(ESS_DISCHARGE_POWER, -5000) // + .input("evcs0", IS_CLUSTERED, false) // + .input(GRID_ACTIVE_POWER, -40000) // + .input("evcs0", ACTIVE_POWER, 5000) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22080) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 44800)) // + .deactivate(); } @Test public void forceChargeTest() throws Exception { - new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("evcs", EVCS) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(ChargeMode.FORCE_CHARGE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(FORCE_CHARGE) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // s + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, -5000) // - .input(evcs0IsClustered, false) // - .input(sumGridActivePower, -40000) // - .input(evcs0ChargePower, 5000) // - .output(evcs0SetChargePowerLimit, 22080)); + .input(ESS_DISCHARGE_POWER, -5000) // + .input("evcs0", IS_CLUSTERED, false) // + .input(GRID_ACTIVE_POWER, -40000) // + .input("evcs0", ACTIVE_POWER, 5000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 22080)) // + .deactivate(); } @Test public void chargingDisabledTest() throws Exception { - new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("evcs", EVCS) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // + .setEvcsId("evcs0") // .setEnableCharging(false) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .output(evcs0SetChargePowerLimit, 0)); + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // + .deactivate(); } @Test public void wrongConfigParametersTest() throws Exception { - var cm = new DummyConfigurationAdmin(); new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", cm) // @@ -158,16 +145,17 @@ public void wrongConfigParametersTest() throws Exception { .addReference("evcs", EVCS) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(30_000) // .setDefaultChargeMinPower(30_000) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(evcs0MaximumHardwarePower, 12000)); + .input("evcs0", MAXIMUM_HARDWARE_POWER, 12000)) // + .deactivate(); assertEquals(12000, (int) (Integer) cm.getConfiguration("ctrlEvcs0").getProperties().get("defaultChargeMinPower")); @@ -175,179 +163,174 @@ public void wrongConfigParametersTest() throws Exception { @Test public void clusterTest() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - + final var clock = createDummyClock(); new ControllerTest(new ControllerEvcsImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("evcs", EVCS) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(3_333) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, -10000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.CHARGING) // - .output(evcs0SetPowerRequest, 10000)) + .input(ESS_DISCHARGE_POWER, -10000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.CHARGING) // + .output("evcs0", SET_CHARGE_POWER_REQUEST, 10000)) .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.NOT_READY_FOR_CHARGING) // - .output(evcs0SetPowerRequest, 0)) // + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.NOT_READY_FOR_CHARGING) // + .output("evcs0", SET_CHARGE_POWER_REQUEST, 0)) // f .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, null) // - .output(evcs0SetPowerRequest, 0)) // + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, null) // + .output("evcs0", SET_CHARGE_POWER_REQUEST, 0)) // .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.CHARGING_REJECTED) // - .output(evcs0SetPowerRequest, 6000) // - .output(evcs0MaximumPower, null)) // - ; + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.CHARGING_REJECTED) // + .output("evcs0", SET_CHARGE_POWER_REQUEST, 6000) // + .output("evcs0", MAXIMUM_POWER, null)) // + .deactivate(); } @Test public void clusterTestDisabledCharging() throws Exception { - new ControllerTest(new ControllerEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("evcs", EVCS) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // + .setEvcsId("evcs0") // .setEnableCharging(false) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(3_333) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, -10000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.CHARGING) // - .output(evcs0SetChargePowerLimit, 0)) + .input(ESS_DISCHARGE_POWER, -10000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.CHARGING) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.NOT_READY_FOR_CHARGING) // - .output(evcs0SetChargePowerLimit, 0)) // + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.NOT_READY_FOR_CHARGING) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, null) // - .output(evcs0SetChargePowerLimit, 0)) // + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, null) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // .next(new TestCase() // - .input(sumEssDischargePower, -6000) // - .input(evcs0IsClustered, true) // - .input(sumGridActivePower, 0) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.CHARGING_REJECTED) // - .output(evcs0SetChargePowerLimit, 0) // - .output(evcs0MaximumPower, null)) // - ; + .input(ESS_DISCHARGE_POWER, -6000) // + .input("evcs0", IS_CLUSTERED, true) // + .input(GRID_ACTIVE_POWER, 0) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs0", STATUS, Status.CHARGING_REJECTED) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0) // + .output("evcs0", MAXIMUM_POWER, null)) // + .deactivate(); } @Test public void hysteresisTest() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - + final var clock = createDummyClock(); new ControllerTest(new ControllerEvcsImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("evcs", EVCS) // .activate(MyConfig.create() // .setId("ctrlEvcs0") // - .setEvcsId(EVCS_ID) // - .setEnableCharging(DEFAULT_ENABLE_CHARGING) // - .setChargeMode(DEFAULT_CHARGE_MODE) // + .setEvcsId("evcs0") // + .setEnableCharging(true) // + .setChargeMode(EXCESS_POWER) // .setForceChargeMinPower(DEFAULT_FORCE_CHARGE_MIN_POWER) // .setDefaultChargeMinPower(DEFAULT_CHARGE_MIN_POWER) // - .setPriority(DEFAULT_PRIORITY) // - .setEnergySessionLimit(DEFAULT_ENERGY_SESSION_LIMIT) // + .setPriority(CAR) // + .setEnergySessionLimit(0) // .setExcessChargeHystersis(120) // .setExcessChargePauseHysteresis(30) // .build()) // .next(new TestCase() // - .input(sumEssDischargePower, 0) // - .input(evcs0IsClustered, false) // - .input(sumGridActivePower, -6_000) // - .input(evcs0ChargePower, 0) // - .output(evcs0SetChargePowerLimit, 6_000)) // + .input(ESS_DISCHARGE_POWER, 0) // + .input("evcs0", IS_CLUSTERED, false) // + .input(GRID_ACTIVE_POWER, -6_000) // + .input("evcs0", ACTIVE_POWER, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 6_000)) // .next(new TestCase() // - .input(sumEssDischargePower, 0) // - .input(sumGridActivePower, -200) // - .input(evcs0ChargePower, 5800) // - .output(evcs0SetChargePowerLimit, 6_000)) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, -200) // + .input("evcs0", ACTIVE_POWER, 5800) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 6_000)) // .next(new TestCase() // - .input(sumEssDischargePower, 0) // - .input(sumGridActivePower, 500) // - .input(evcs0ChargePower, 5800) // - .input(evcs0MinimumHardwarePower, Evcs.DEFAULT_MINIMUM_HARDWARE_POWER) - .output(evcs0SetChargePowerLimit, 5300)) + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, 500) // + .input("evcs0", ACTIVE_POWER, 5800) // + .input("evcs0", Evcs.ChannelId.MINIMUM_HARDWARE_POWER, Evcs.DEFAULT_MINIMUM_HARDWARE_POWER) + .output("evcs0", SET_CHARGE_POWER_LIMIT, 5300)) // Active hysteresis .next(new TestCase() // - .input(sumEssDischargePower, 0) // - .input(sumGridActivePower, 1000) // - .input(evcs0ChargePower, 5000) // - .output(evcs0SetChargePowerLimit, 5_300) // - .output(evcsController0AwaitingHysteresis, true)) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, 1000) // + .input("evcs0", ACTIVE_POWER, 5000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 5_300) // + .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)) // + .timeleap(clock, 6, MINUTES)) // // Passed hysteresis .next(new TestCase() // - .input(sumEssDischargePower, 0) // - .input(sumGridActivePower, 1000) // - .input(evcs0ChargePower, 5000) // - .output(evcs0SetChargePowerLimit, 0) // - .output(evcsController0AwaitingHysteresis, false)) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, 1000) // + .input("evcs0", ACTIVE_POWER, 5000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0) // + .output(AWAITING_HYSTERESIS, false)) // // Active hysteresis .next(new TestCase() // - .input(sumEssDischargePower, 0) // - .input(sumGridActivePower, -5000) // - .input(evcs0ChargePower, 0) // - .output(evcs0SetChargePowerLimit, 0) // - .output(evcsController0AwaitingHysteresis, true)) + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, -5000) // + .input("evcs0", ACTIVE_POWER, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0) // + .output(AWAITING_HYSTERESIS, true)) .next(new TestCase() // - .timeleap(clock, 1, ChronoUnit.MINUTES)) // + .timeleap(clock, 1, MINUTES)) // // New charge process starting after another 30 seconds .next(new TestCase() // - .input(sumEssDischargePower, 0) // - .input(sumGridActivePower, -5000) // - .input(evcs0ChargePower, 0) // - .output(evcs0SetChargePowerLimit, 5000) // - .output(evcsController0AwaitingHysteresis, false)); // + .input(ESS_DISCHARGE_POWER, 0) // + .input(GRID_ACTIVE_POWER, -5000) // + .input("evcs0", ACTIVE_POWER, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 5000) // + .output(AWAITING_HYSTERESIS, false)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest.java b/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest.java index 943591e76dd..f0153a6af4e 100644 --- a/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest.java +++ b/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest.java @@ -1,10 +1,11 @@ package io.openems.edge.controller.generic.jsonlogic; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; -import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -12,40 +13,32 @@ public class ControllerGenericJsonLogicImplTest { - private static final ChannelAddress ESS_SOC = new ChannelAddress(Sum.SINGLETON_COMPONENT_ID, - Sum.ChannelId.ESS_SOC.id()); - - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - @Test public void test() throws Exception { new ControllerTest(new ControllerGenericJsonLogicImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addComponent(new DummySum()) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID)) // + .addComponent(new DummyManagedSymmetricEss("ess0")) // .activate(MyConfig.create() // .setRule("{" // + " \"if\":["// + " {"// + " \"<\": ["// + " {"// - + " \"var\": \"" + ESS_SOC + "\""// + + " \"var\": \"ess0/Soc\""// + " },"// + " 50"// + " ]"// + " },"// + " ["// + " ["// - + " \"" + ESS_SET_ACTIVE_POWER_EQUALS + "\","// + + " \"ess0/SetActivePowerEquals\","// + " 5000"// + " ]"// + " ],"// + " ["// + " ["// - + " \"" + ESS_SET_ACTIVE_POWER_EQUALS + "\","// + + " \"ess0/SetActivePowerEquals\","// + " -2000"// + " ]"// + " ]"// @@ -53,12 +46,12 @@ public void test() throws Exception { + "}") // .build()) .next(new TestCase() // - .input(ESS_SOC, 40) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 5000)) // + .input("ess0", SOC, 40) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 5000)) // .next(new TestCase() // - .input(ESS_SOC, 60) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, -2000) // - ); + .input("ess0", SOC, 60) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, -2000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest2.java b/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest2.java index d12b3dc936c..61d21f86d56 100644 --- a/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest2.java +++ b/io.openems.edge.controller.generic.jsonlogic/test/io/openems/edge/controller/generic/jsonlogic/ControllerGenericJsonLogicImplTest2.java @@ -1,10 +1,14 @@ package io.openems.edge.controller.generic.jsonlogic; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_ACTIVE_POWER; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT2; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; -import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -12,29 +16,19 @@ public class ControllerGenericJsonLogicImplTest2 { - private static final ChannelAddress SUM_PRODUCTION_POWER = new ChannelAddress(Sum.SINGLETON_COMPONENT_ID, - Sum.ChannelId.PRODUCTION_ACTIVE_POWER.id()); - private static final ChannelAddress SUM_SOC = new ChannelAddress(Sum.SINGLETON_COMPONENT_ID, - Sum.ChannelId.ESS_SOC.id()); - - private static final String IO_ID = "io0"; - private static final ChannelAddress INPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - private static final ChannelAddress OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput2"); - @Test public void test() throws Exception { new ControllerTest(new ControllerGenericJsonLogicImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addComponent(new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // .setRule("{"// + " \"if\": ["// + " {"// + " \">\": ["// + " {"// - + " \"var\": \"" + SUM_PRODUCTION_POWER + "\""// + + " \"var\": \"_sum/ProductionActivePower\""// + " },"// + " 2000"// + " ]"// @@ -44,7 +38,7 @@ public void test() throws Exception { + " {"// + " \">\": ["// + " {"// - + " \"var\": \"" + SUM_SOC + "\""// + + " \"var\": \"_sum/EssSoc\""// + " },"// + " 70"// + " ]"// @@ -52,24 +46,24 @@ public void test() throws Exception { + " {"// + " \"if\": ["// + " {"// - + " \"var\": \"" + INPUT0 + "\""// + + " \"var\": \"io0/InputOutput0\""// + " },"// + " ["// + " ],"// + " {"// + " \"if\": ["// + " {"// - + " \"var\": \"" + OUTPUT0 + "\""// + + " \"var\": \"io0/InputOutput1\""// + " },"// + " ["// + " ["// - + " \"" + OUTPUT0 + "\","// + + " \"io0/InputOutput1\","// + " false"// + " ]"// + " ],"// + " ["// + " ["// - + " \"" + OUTPUT1 + "\","// + + " \"io0/InputOutput2\","// + " true"// + " ]"// + " ]"// @@ -86,7 +80,7 @@ public void test() throws Exception { + " {"// + " \"<\": ["// + " {"// - + " \"var\": \"" + SUM_SOC + "\""// + + " \"var\": \"_sum/EssSoc\""// + " },"// + " 40"// + " ]"// @@ -94,24 +88,24 @@ public void test() throws Exception { + " {"// + " \"if\": ["// + " {"// - + " \"var\": \"" + INPUT0 + "\""// + + " \"var\": \"io0/InputOutput0\""// + " },"// + " ["// + " ],"// + " {"// + " \"if\": ["// + " {"// - + " \"var\": \"" + OUTPUT1 + "\""// + + " \"var\": \"io0/InputOutput2\""// + " },"// + " ["// + " ["// - + " \"" + OUTPUT1 + "\","// + + " \"io0/InputOutput2\","// + " false"// + " ]"// + " ],"// + " ["// + " ["// - + " \"" + OUTPUT0 + "\","// + + " \"io0/InputOutput1\","// + " true"// + " ]"// + " ]"// @@ -127,30 +121,30 @@ public void test() throws Exception { + "}") // .build()) .next(new TestCase() // - .input(SUM_PRODUCTION_POWER, 2001) // - .input(SUM_SOC, 71) // - .input(INPUT0, false) // - .input(OUTPUT0, true) // - .output(OUTPUT0, false)) // + .input(PRODUCTION_ACTIVE_POWER, 2001) // + .input(ESS_SOC, 71) // + .input("io0", INPUT_OUTPUT0, false) // + .input("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT1, false)) // .next(new TestCase() // - .input(SUM_PRODUCTION_POWER, 2001) // - .input(SUM_SOC, 71) // - .input(INPUT0, false) // - .input(OUTPUT0, false) // - .output(OUTPUT1, true)) // + .input(PRODUCTION_ACTIVE_POWER, 2001) // + .input(ESS_SOC, 71) // + .input("io0", INPUT_OUTPUT0, false) // + .input("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, true)) // .next(new TestCase() // - .input(SUM_PRODUCTION_POWER, 1999) // - .input(SUM_SOC, 39) // - .input(INPUT0, false) // - .input(OUTPUT1, true) // - .output(OUTPUT1, false)) // + .input(PRODUCTION_ACTIVE_POWER, 1999) // + .input(ESS_SOC, 39) // + .input("io0", INPUT_OUTPUT0, false) // + .input("io0", INPUT_OUTPUT2, true) // + .output("io0", INPUT_OUTPUT2, false)) // .next(new TestCase() // - .input(SUM_PRODUCTION_POWER, 1999) // - .input(SUM_SOC, 39) // - .input(INPUT0, false) // - .input(OUTPUT1, false) // - .output(OUTPUT0, true)) // - ; + .input(PRODUCTION_ACTIVE_POWER, 1999) // + .input(ESS_SOC, 39) // + .input("io0", INPUT_OUTPUT0, false) // + .input("io0", INPUT_OUTPUT2, false) // + .output("io0", INPUT_OUTPUT1, true)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.highloadtimeslot/test/io/openems/edge/controller/highloadtimeslot/ControllerHighLoadTimeslotImplTest.java b/io.openems.edge.controller.highloadtimeslot/test/io/openems/edge/controller/highloadtimeslot/ControllerHighLoadTimeslotImplTest.java index 042f700767e..ad6992ddea2 100644 --- a/io.openems.edge.controller.highloadtimeslot/test/io/openems/edge/controller/highloadtimeslot/ControllerHighLoadTimeslotImplTest.java +++ b/io.openems.edge.controller.highloadtimeslot/test/io/openems/edge/controller/highloadtimeslot/ControllerHighLoadTimeslotImplTest.java @@ -1,5 +1,7 @@ package io.openems.edge.controller.highloadtimeslot; +import static io.openems.edge.controller.highloadtimeslot.WeekdayFilter.EVERDAY; + import org.junit.Test; import io.openems.edge.common.test.DummyComponentManager; @@ -7,24 +9,22 @@ public class ControllerHighLoadTimeslotImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerHighLoadTimeslotImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEss(ESS_ID).setHysteresisSoc(90) // + .setId("ctrl0") // + .setEss("ess0") // + .setHysteresisSoc(90) // .setChargePower(10000) // .setDischargePower(20000) // .setStartDate("01.01.2019") // .setEndDate("01.01.2020") // .setStartTime("08:00") // .setEndTime("13:00") // - .setWeekdayFilter(WeekdayFilter.EVERDAY) // - .build()); // - ; + .setWeekdayFilter(EVERDAY) // + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.alarm/test/io/openems/edge/controller/io/alarm/ControllerIoAlarmImplTest.java b/io.openems.edge.controller.io.alarm/test/io/openems/edge/controller/io/alarm/ControllerIoAlarmImplTest.java index d4adfec3d38..4eb47a82b4b 100644 --- a/io.openems.edge.controller.io.alarm/test/io/openems/edge/controller/io/alarm/ControllerIoAlarmImplTest.java +++ b/io.openems.edge.controller.io.alarm/test/io/openems/edge/controller/io/alarm/ControllerIoAlarmImplTest.java @@ -1,8 +1,11 @@ package io.openems.edge.controller.io.alarm; +import static io.openems.edge.controller.io.alarm.DummyComponent.ChannelId.STATE_0; +import static io.openems.edge.controller.io.alarm.DummyComponent.ChannelId.STATE_1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -10,44 +13,34 @@ public class ControllerIoAlarmImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String DUMMY_ID = "dummy0"; - private static final ChannelAddress DUMMY_STATE0 = new ChannelAddress(DUMMY_ID, "State0"); - private static final ChannelAddress DUMMY_STATE1 = new ChannelAddress(DUMMY_ID, "State1"); - - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_INPUT_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - @Test public void test() throws Exception { new ControllerTest(new ControllerIoAlarmImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyComponent(DUMMY_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyComponent("dummy0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setInputChannelAddresses(// - DUMMY_STATE0.toString(), // - DUMMY_STATE1.toString()) - .setOutputChannelAddress(IO_INPUT_OUTPUT0.toString()) // + .setId("ctrl0") // + .setInputChannelAddresses("dummy0/State0", "dummy0/State1") + .setOutputChannelAddress("io0/InputOutput0") // .build()) .next(new TestCase() // - .input(DUMMY_STATE0, true) // - .input(DUMMY_STATE1, false) // - .output(IO_INPUT_OUTPUT0, true)) // + .input("dummy0", STATE_0, true) // + .input("dummy0", STATE_1, false) // + .output("io0", INPUT_OUTPUT0, true)) // .next(new TestCase() // - .input(DUMMY_STATE0, false) // - .input(DUMMY_STATE1, true) // - .output(IO_INPUT_OUTPUT0, true)) // + .input("dummy0", STATE_0, false) // + .input("dummy0", STATE_1, true) // + .output("io0", INPUT_OUTPUT0, true)) // .next(new TestCase() // - .input(DUMMY_STATE0, true) // - .input(DUMMY_STATE1, true) // - .output(IO_INPUT_OUTPUT0, true)) + .input("dummy0", STATE_0, true) // + .input("dummy0", STATE_1, true) // + .output("io0", INPUT_OUTPUT0, true)) .next(new TestCase() // - .input(DUMMY_STATE0, false) // - .input(DUMMY_STATE1, false) // - .output(IO_INPUT_OUTPUT0, false)); + .input("dummy0", STATE_0, false) // + .input("dummy0", STATE_1, false) // + .output("io0", INPUT_OUTPUT0, false)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.analog/test/io/openems/edge/controller/io/analog/MyControllerTest.java b/io.openems.edge.controller.io.analog/test/io/openems/edge/controller/io/analog/MyControllerTest.java index 6c31ef34601..e2ce7b1917c 100644 --- a/io.openems.edge.controller.io.analog/test/io/openems/edge/controller/io/analog/MyControllerTest.java +++ b/io.openems.edge.controller.io.analog/test/io/openems/edge/controller/io/analog/MyControllerTest.java @@ -1,14 +1,14 @@ package io.openems.edge.controller.io.analog; -import java.time.Instant; -import java.time.ZoneOffset; +import static io.openems.edge.common.test.TestUtils.createDummyClock; + import java.time.temporal.ChronoUnit; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; +import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; @@ -18,139 +18,129 @@ public class MyControllerTest { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "analogIo0"; - - // Sum channels - private static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower"); - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower"); - - // AnalogIO channels - private static final ChannelAddress DEBUG_SET_OUTPUT_VOLTAGE = new ChannelAddress(IO_ID, "DebugSetOutputVoltage"); - private static final ChannelAddress DEBUG_SET_OUTPUT_PERCENT = new ChannelAddress(IO_ID, "DebugSetOutputPercent"); + private static final ChannelAddress DEBUG_SET_OUTPUT_VOLTAGE = new ChannelAddress("analogIo0", + "DebugSetOutputVoltage"); + private static final ChannelAddress DEBUG_SET_OUTPUT_PERCENT = new ChannelAddress("analogIo0", + "DebugSetOutputPercent"); @Test public void testOff() throws Exception { - - final var analogOutput = new DummyAnalogVoltageOutput(IO_ID); - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerIoAnalogImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("analogOutput", analogOutput) // + .addReference("analogOutput", new DummyAnalogVoltageOutput("analogIo0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAnalogOutputId(IO_ID) // + .setId("ctrl0") // + .setAnalogOutputId("analogIo0") // .setManualTarget(6_000) // .setMaximumPower(10_000) // .setMode(Mode.OFF) // .setPowerBehaviour(PowerBehavior.LINEAR) // .build()) .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 2000) // - .input(SUM_GRID_ACTIVE_POWER, -5000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 2000) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -5000)// .output(DEBUG_SET_OUTPUT_PERCENT, 0f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 0)) // - ; + .deactivate(); } @Test public void testOn() throws Exception { - - final var analogOutput = new DummyAnalogVoltageOutput(IO_ID) // - .setRange(new Range(0, 100, 10000)); - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerIoAnalogImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("analogOutput", analogOutput) // + .addReference("analogOutput", new DummyAnalogVoltageOutput("analogIo0") // + .setRange(new Range(0, 100, 10000))) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAnalogOutputId(IO_ID) // + .setId("ctrl0") // + .setAnalogOutputId("analogIo0") // .setManualTarget(6_000) // .setMaximumPower(10_000) // .setMode(Mode.ON) // .setPowerBehaviour(PowerBehavior.LINEAR) // .build()) .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -5000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -5000)// .output(DEBUG_SET_OUTPUT_PERCENT, 60.000004f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 6000)) // - ; + .deactivate(); new ControllerTest(new ControllerIoAnalogImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("analogOutput", analogOutput) // + .addReference("analogOutput", new DummyAnalogVoltageOutput("analogIo0") // + .setRange(new Range(0, 100, 10000))) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAnalogOutputId(IO_ID) // + .setId("ctrl0") // + .setAnalogOutputId("analogIo0") // .setManualTarget(0) // .setMaximumPower(10_000) // .setMode(Mode.ON) // .setPowerBehaviour(PowerBehavior.LINEAR) // .build()) .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -5000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -5000)// .output(DEBUG_SET_OUTPUT_PERCENT, 0f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 0)) // - ; + .deactivate(); } @Test public void testAutomatic() throws Exception { - - final var analogOutput = new DummyAnalogVoltageOutput(IO_ID); - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerIoAnalogImpl(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("sum", new DummySum()) // .addReference("timedata", new DummyTimedata("timedata0")) // - .addReference("analogOutput", analogOutput) // + .addReference("analogOutput", new DummyAnalogVoltageOutput("analogIo0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setAnalogOutputId(IO_ID) // + .setId("ctrl0") // + .setAnalogOutputId("analogIo0") // .setManualTarget(6_000) // .setMaximumPower(10_000) // .setMode(Mode.AUTOMATIC) // .setPowerBehaviour(PowerBehavior.LINEAR) // .build()) .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -5000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -5000)// .output(DEBUG_SET_OUTPUT_PERCENT, 50f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 5000)) // .next(new TestCase() // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -2444)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -2444)// .output(DEBUG_SET_OUTPUT_PERCENT, 50f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 5000)) // .next(new TestCase() // .timeleap(clock, 4, ChronoUnit.SECONDS) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -2444)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -2444)// .output(DEBUG_SET_OUTPUT_PERCENT, 24.439999f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 2400)) // 100mV Steps .next(new TestCase() // .timeleap(clock, 4, ChronoUnit.SECONDS) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, -12444)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, -12444)// .output(DEBUG_SET_OUTPUT_PERCENT, 100f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 10000)) // .next(new TestCase() // .timeleap(clock, 4, ChronoUnit.SECONDS) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_GRID_ACTIVE_POWER, 1000)// + .input(Sum.ChannelId.ESS_DISCHARGE_POWER, 0) // + .input(Sum.ChannelId.GRID_ACTIVE_POWER, 1000)// .output(DEBUG_SET_OUTPUT_PERCENT, 0f) // .output(DEBUG_SET_OUTPUT_VOLTAGE, 0)) // - ; + + .deactivate(); } } diff --git a/io.openems.edge.controller.io.channelsinglethreshold/test/io/openems/edge/controller/io/channelsinglethreshold/ControllerIoChannelSingleThresholdImplTest.java b/io.openems.edge.controller.io.channelsinglethreshold/test/io/openems/edge/controller/io/channelsinglethreshold/ControllerIoChannelSingleThresholdImplTest.java index 7a780ffba36..19494c4a3f3 100644 --- a/io.openems.edge.controller.io.channelsinglethreshold/test/io/openems/edge/controller/io/channelsinglethreshold/ControllerIoChannelSingleThresholdImplTest.java +++ b/io.openems.edge.controller.io.channelsinglethreshold/test/io/openems/edge/controller/io/channelsinglethreshold/ControllerIoChannelSingleThresholdImplTest.java @@ -1,9 +1,11 @@ package io.openems.edge.controller.io.channelsinglethreshold; +import static io.openems.edge.controller.io.channelsinglethreshold.ControllerIoChannelSingleThreshold.ChannelId.AWAITING_HYSTERESIS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; + import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -12,35 +14,26 @@ public class ControllerIoChannelSingleThresholdImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final ChannelAddress CTRL_AWAITING_HYSTERESIS = new ChannelAddress(CTRL_ID, "AwaitingHysteresis"); - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_INPUT_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - @Test public void test() throws Exception { - final var clock = new TimeLeapClock(); new ControllerTest(new ControllerIoChannelSingleThresholdImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID)) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addReference("componentManager", new DummyComponentManager()) // + .addComponent(new DummyManagedSymmetricEss("ess0")) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setMode(Mode.AUTOMATIC) // - .setInputChannelAddress(ESS_SOC.toString()) // - .setOutputChannelAddress(IO_INPUT_OUTPUT0.toString()) // + .setInputChannelAddress("ess0/Soc") // + .setOutputChannelAddress("io0/InputOutput0") // .setThreshold(70) // .setSwitchedLoadPower(0) // .setMinimumSwitchingTime(60).setInvert(false) // .build()) .next(new TestCase() // - .input(ESS_SOC, 50) // - .output(IO_INPUT_OUTPUT0, false) // - .output(CTRL_AWAITING_HYSTERESIS, false)); // + .input("ess0", SOC, 50) // + .output("io0", INPUT_OUTPUT0, false) // + .output(AWAITING_HYSTERESIS, false)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.fixdigitaloutput/test/io/openems/edge/controller/io/fixdigitaloutput/ControllerIoFixDigitalOutputImplTest.java b/io.openems.edge.controller.io.fixdigitaloutput/test/io/openems/edge/controller/io/fixdigitaloutput/ControllerIoFixDigitalOutputImplTest.java index 9a80d917290..87dc6f60fb3 100644 --- a/io.openems.edge.controller.io.fixdigitaloutput/test/io/openems/edge/controller/io/fixdigitaloutput/ControllerIoFixDigitalOutputImplTest.java +++ b/io.openems.edge.controller.io.fixdigitaloutput/test/io/openems/edge/controller/io/fixdigitaloutput/ControllerIoFixDigitalOutputImplTest.java @@ -1,8 +1,9 @@ package io.openems.edge.controller.io.fixdigitaloutput; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; @@ -10,11 +11,6 @@ public class ControllerIoFixDigitalOutputImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_INPUT_OUTPUT0 = new ChannelAddress(IO_ID, "InputOutput0"); - @Test public void testOn() throws Exception { this.testSwitch(true); @@ -28,14 +24,14 @@ public void testOff() throws Exception { private void testSwitch(boolean on) throws Exception { new ControllerTest(new ControllerIoFixDigitalOutputImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelAddress(IO_INPUT_OUTPUT0.toString()) // + .setId("ctrl0") // + .setOutputChannelAddress("io0/InputOutput0") // .setOn(on) // .build()) .next(new TestCase() // - .output(IO_INPUT_OUTPUT0, on)); + .output("io0", INPUT_OUTPUT0, on)) // + .deactivate(); } - } diff --git a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerHeatingElementImplTest4.java b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerHeatingElementImplTest4.java index 854d4f90087..6e5db300d23 100644 --- a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerHeatingElementImplTest4.java +++ b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerHeatingElementImplTest4.java @@ -1,18 +1,18 @@ package io.openems.edge.controller.io.heatingelement; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_DISCHARGE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.LEVEL; +import static java.time.temporal.ChronoUnit.SECONDS; import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; -import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.common.test.TestUtils; import io.openems.edge.controller.io.heatingelement.enums.Level; import io.openems.edge.controller.io.heatingelement.enums.Mode; import io.openems.edge.controller.io.heatingelement.enums.WorkMode; @@ -20,25 +20,20 @@ import io.openems.edge.io.test.DummyInputOutput; public class ControllerHeatingElementImplTest4 { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "io0"; - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3"); - private static final TimeLeapClock clock = new TimeLeapClock( - Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, ZoneOffset.UTC); + private static TimeLeapClock clock; private static ControllerTest prepareTest(Mode mode, Level level) throws OpenemsNamedException, Exception { + clock = TestUtils.createDummyClock(); return new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(mode) // @@ -49,13 +44,6 @@ private static ControllerTest prepareTest(Mode mode, Level level) throws Openems .build()); // } - private static final ChannelAddress ESSO_DISCHARGE_POWER = new ChannelAddress("_sum", - Sum.ChannelId.ESS_DISCHARGE_POWER.id()); - private static final ChannelAddress LEVEL = new ChannelAddress(CTRL_ID, - ControllerIoHeatingElement.ChannelId.LEVEL.id()); - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress("_sum", - Sum.ChannelId.GRID_ACTIVE_POWER.id()); - @Test public void testDischargeTakeIntoAccount() throws OpenemsNamedException, Exception { prepareTest(Mode.AUTOMATIC, Level.LEVEL_3)// @@ -63,20 +51,20 @@ public void testDischargeTakeIntoAccount() throws OpenemsNamedException, Excepti .input(GRID_ACTIVE_POWER, -2500)// .output(LEVEL, Level.LEVEL_1)) // .next(new TestCase() // - .timeleap(clock, 181, ChronoUnit.SECONDS)// + .timeleap(clock, 181, SECONDS)// .input(GRID_ACTIVE_POWER, -2500)// .output(LEVEL, Level.LEVEL_2))// // Grid power reducing because of 2kW heating power .next(new TestCase()// - .timeleap(clock, 181, ChronoUnit.SECONDS)// + .timeleap(clock, 181, SECONDS)// .input(GRID_ACTIVE_POWER, -500) // - .output(LEVEL, Level.LEVEL_2)// - ).next(new TestCase() // - .timeleap(clock, 181, ChronoUnit.SECONDS)// + .output(LEVEL, Level.LEVEL_2)) // + .next(new TestCase() // + .timeleap(clock, 181, SECONDS)// .input(GRID_ACTIVE_POWER, -500) // - .input(ESSO_DISCHARGE_POWER, 2300) // - .output(LEVEL, Level.LEVEL_1)// - ); // ; + .input(ESS_DISCHARGE_POWER, 2300) // + .output(LEVEL, Level.LEVEL_1)) // + .deactivate(); } @Test @@ -87,9 +75,10 @@ public void realDataTest() throws OpenemsNamedException, Exception { .input(GRID_ACTIVE_POWER, -6000)// .output(LEVEL, Level.LEVEL_3)) // .next(new TestCase()// - .timeleap(clock, 181, ChronoUnit.SECONDS)// + .timeleap(clock, 181, SECONDS)// .input(GRID_ACTIVE_POWER, 0)// - .input(ESSO_DISCHARGE_POWER, 2280)// - .output(LEVEL, Level.LEVEL_1)); // ; + .input(ESS_DISCHARGE_POWER, 2280)// + .output(LEVEL, Level.LEVEL_1)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest.java b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest.java index a2c130c4c3a..f874c0994e7 100644 --- a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest.java +++ b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest.java @@ -1,17 +1,22 @@ package io.openems.edge.controller.io.heatingelement; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.PHASE1_TIME; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.PHASE2_TIME; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.PHASE3_TIME; +import static io.openems.edge.controller.io.heatingelement.enums.Level.LEVEL_3; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT2; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; -import io.openems.edge.controller.io.heatingelement.enums.Level; import io.openems.edge.controller.io.heatingelement.enums.Mode; import io.openems.edge.controller.io.heatingelement.enums.WorkMode; import io.openems.edge.controller.test.ControllerTest; @@ -19,36 +24,22 @@ public class ControllerIoHeatingElementImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower"); - - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3"); - - private static final ChannelAddress CTRL_PHASE1TIME = new ChannelAddress(CTRL_ID, "Phase1Time"); - private static final ChannelAddress CTRL_PHASE2TIME = new ChannelAddress(CTRL_ID, "Phase2Time"); - private static final ChannelAddress CTRL_PHASE3TIME = new ChannelAddress(CTRL_ID, "Phase3Time"); - @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); + final var clock = createDummyClock(); new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(Mode.AUTOMATIC) // - .setDefaultLevel(Level.LEVEL_3) // + .setDefaultLevel(LEVEL_3) // .setWorkMode(WorkMode.TIME) // .setMinTime(1) // .setMinimumSwitchingTime(60) // @@ -56,133 +47,134 @@ public void test() throws Exception { .next(new TestCase() // // Grid active power : 0, Excess power : 0, // from -> UNDEFINED --to--> LEVEL_0, no of relais = 0 - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 0) // - .output(CTRL_PHASE1TIME, 0) // - .output(CTRL_PHASE2TIME, 0)) // + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 0) // + .output(PHASE1_TIME, 0) // + .output(PHASE2_TIME, 0)) // .next(new TestCase() // // Grid active power : 0, Excess power : 0, // from -> LEVEL_0 --to--> LEVEL_0, no of relais = 0 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 0) // - .output(CTRL_PHASE2TIME, 0) // - .output(CTRL_PHASE3TIME, 0)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 0) // + .output(PHASE2_TIME, 0) // + .output(PHASE3_TIME, 0)) // .next(new TestCase() // // Grid active power : -2000, Excess power : 2000, // from -> LEVEL_0 --to--> LEVEL_1, no of relais = 1 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -2000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 0) // - .output(CTRL_PHASE2TIME, 0) // - .output(CTRL_PHASE3TIME, 0)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, -2000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 0) // + .output(PHASE2_TIME, 0) // + .output(PHASE3_TIME, 0)) // .next(new TestCase() // // Grid active power : -4000, Excess power : 6000, // from -> LEVEL_1 --to--> LEVEL_3, no of relais = 3 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_PHASE1TIME, 15 * 60) // - .output(CTRL_PHASE2TIME, 0) // - .output(CTRL_PHASE3TIME, 0)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, -4000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(PHASE1_TIME, 15 * 60) // + .output(PHASE2_TIME, 0) // + .output(PHASE3_TIME, 0)) // .next(new TestCase() // // Grid active power : -6000, Excess power : 12000, // from -> LEVEL_3 --to--> LEVEL_3, no of relais = 3 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -6000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_PHASE1TIME, 30 * 60) // - .output(CTRL_PHASE2TIME, 15 * 60) // - .output(CTRL_PHASE3TIME, 15 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, -6000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(PHASE1_TIME, 30 * 60) // + .output(PHASE2_TIME, 15 * 60) // + .output(PHASE3_TIME, 15 * 60)) // .next(new TestCase() // // Grid active power : -7000, Excess power : 13000, // from -> LEVEL_3 --to--> LEVEL_3, no of relais = 3 - .input(SUM_GRID_ACTIVE_POWER, -7000) // - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_PHASE1TIME, 45 * 60) // - .output(CTRL_PHASE2TIME, 30 * 60) // - .output(CTRL_PHASE3TIME, 30 * 60)) // + .input(GRID_ACTIVE_POWER, -7000) // + .timeleap(clock, 15, MINUTES)// + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(PHASE1_TIME, 45 * 60) // + .output(PHASE2_TIME, 30 * 60) // + .output(PHASE3_TIME, 30 * 60)) // .next(new TestCase() // // Grid active power : 0, Excess power : 6000, // from -> LEVEL_3 --to--> LEVEL_3, no of relais = 3 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_PHASE1TIME, 60 * 60) // - .output(CTRL_PHASE2TIME, 45 * 60) // - .output(CTRL_PHASE3TIME, 45 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(PHASE1_TIME, 60 * 60) // + .output(PHASE2_TIME, 45 * 60) // + .output(PHASE3_TIME, 45 * 60)) // .next(new TestCase() // // Grid active power : 1, Excess power : 0, // from -> LEVEL_3 --to--> LEVEL_0, no of relais = 0 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 1) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 75 * 60) // - .output(CTRL_PHASE2TIME, 60 * 60) // - .output(CTRL_PHASE3TIME, 60 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 1) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 75 * 60) // + .output(PHASE2_TIME, 60 * 60) // + .output(PHASE3_TIME, 60 * 60)) // .next(new TestCase() // // Grid active power : 20000, Excess power : 0, // from -> LEVEL_0 --to--> LEVEL_0, no of relais = 0 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 20000) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 75 * 60) // - .output(CTRL_PHASE2TIME, 60 * 60) // - .output(CTRL_PHASE3TIME, 60 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 20000) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 75 * 60) // + .output(PHASE2_TIME, 60 * 60) // + .output(PHASE3_TIME, 60 * 60)) // .next(new TestCase() // // Grid active power : -4000, Excess power : 10000, // from -> LEVEL_0 --to--> LEVEL_2, no of relais = 2 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 75 * 60) // - .output(CTRL_PHASE2TIME, 60 * 60) // - .output(CTRL_PHASE3TIME, 60 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, -4000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 75 * 60) // + .output(PHASE2_TIME, 60 * 60) // + .output(PHASE3_TIME, 60 * 60)) // .next(new TestCase() // // Grid active power : 0, Excess power : 4000, // from -> LEVEL_2 --to--> LEVEL_2, no of relais = 2 - .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 90 * 60) // - .output(CTRL_PHASE2TIME, 75 * 60) // - .output(CTRL_PHASE3TIME, 60 * 60)) // + .timeleap(clock, 15, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 90 * 60) // + .output(PHASE2_TIME, 75 * 60) // + .output(PHASE3_TIME, 60 * 60)) // .next(new TestCase() // // Switch to next day - .timeleap(clock, 22, ChronoUnit.HOURS)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, false) // - .output(CTRL_PHASE1TIME, 30 * 60) // - .output(CTRL_PHASE2TIME, 30 * 60) // - .output(CTRL_PHASE3TIME, 0)); // + .timeleap(clock, 22, HOURS)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, false) // + .output(PHASE1_TIME, 30 * 60) // + .output(PHASE2_TIME, 30 * 60) // + .output(PHASE3_TIME, 0)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest2.java b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest2.java index 31ec267cf14..788715d4715 100644 --- a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest2.java +++ b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest2.java @@ -1,13 +1,20 @@ package io.openems.edge.controller.io.heatingelement; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.FORCE_START_AT_SECONDS_OF_DAY; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.STATUS; +import static io.openems.edge.controller.io.heatingelement.ControllerIoHeatingElement.ChannelId.TOTAL_PHASE_TIME; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT2; +import static java.time.temporal.ChronoUnit.MINUTES; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; @@ -20,20 +27,6 @@ public class ControllerIoHeatingElementImplTest2 { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower"); - - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3"); - - private static final ChannelAddress CTRL_FORCE_START_AT_SECONDS_OF_DAY = new ChannelAddress(CTRL_ID, - "ForceStartAtSecondsOfDay"); - private static final ChannelAddress CTRL_TOTAL_PHASE_TIME = new ChannelAddress(CTRL_ID, "TotalPhaseTime"); - private static final ChannelAddress CTRL_STATUS = new ChannelAddress(CTRL_ID, "Status"); - @Test public void minimumTime_lowerLevel_test() throws Exception { final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, @@ -41,12 +34,12 @@ public void minimumTime_lowerLevel_test() throws Exception { new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(Mode.AUTOMATIC) // @@ -56,20 +49,21 @@ public void minimumTime_lowerLevel_test() throws Exception { .setMinimumSwitchingTime(60) // .build()) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false))// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false))// .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -2000) // - .output(IO_OUTPUT1, true) // - .output(CTRL_TOTAL_PHASE_TIME, 0) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 53_100 /* 14:45 */)) // + .timeleap(clock, 6, MINUTES)// + .input(GRID_ACTIVE_POWER, -2000) // + .output("io0", INPUT_OUTPUT0, true) // + .output(TOTAL_PHASE_TIME, 0) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 53_100 /* 14:45 */)) // .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(CTRL_TOTAL_PHASE_TIME, 360 /* 6 minutes, one phase */) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 53_220 /* 14:47 - two minutes later */)); // + .timeleap(clock, 6, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output(TOTAL_PHASE_TIME, 360 /* 6 minutes, one phase */) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 53_220 /* 14:47 - two minutes later */)) // + .deactivate(); } @Test @@ -79,12 +73,12 @@ public void minimumTime_sameLevel_test() throws Exception { new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(Mode.AUTOMATIC) // @@ -94,24 +88,25 @@ public void minimumTime_sameLevel_test() throws Exception { .setMinimumSwitchingTime(60) // .build()) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false))// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false))// .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -6000) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_TOTAL_PHASE_TIME, 0) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 53_100 /* 14:45 */)) // + .timeleap(clock, 6, MINUTES)// + .input(GRID_ACTIVE_POWER, -6000) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(TOTAL_PHASE_TIME, 0) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 53_100 /* 14:45 */)) // .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_TOTAL_PHASE_TIME, 1080 /* 6 minutes, all three phases */) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 53_460 /* 14:51 - six minutes later */)); // + .timeleap(clock, 6, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(TOTAL_PHASE_TIME, 1080 /* 6 minutes, all three phases */) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 53_460 /* 14:51 - six minutes later */)) // + .deactivate(); } @Test @@ -121,12 +116,12 @@ public void minimumTime_inForceMode_test() throws Exception { new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("16:00:00") // .setPowerOfPhase(2000) // .setMode(Mode.AUTOMATIC) // @@ -136,35 +131,36 @@ public void minimumTime_inForceMode_test() throws Exception { .setMinimumSwitchingTime(60) // .build()) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false))// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false))// .next(new TestCase() // - .timeleap(clock, 6, ChronoUnit.MINUTES)// /* 14:57 */ - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - .output(CTRL_TOTAL_PHASE_TIME, 0) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 54_000 /* 15:00 */)) // + .timeleap(clock, 6, MINUTES)// /* 14:57 */ + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false) // + .output(TOTAL_PHASE_TIME, 0) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 54_000 /* 15:00 */)) // .next(new TestCase() // - .timeleap(clock, 4, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, 0) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 54_000 /* current time */)) + .timeleap(clock, 4, MINUTES)// + .input(GRID_ACTIVE_POWER, 0) // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 54_000 /* current time */)) .next(new TestCase() // - .timeleap(clock, 3, ChronoUnit.MINUTES)// /* 15:03 */ - .input(SUM_GRID_ACTIVE_POWER, 0) // + .timeleap(clock, 3, MINUTES)// /* 15:03 */ + .input(GRID_ACTIVE_POWER, 0) // // Previous duration of each phase cannot be set as input if you want to count // already passed active time // .input(CTRL_PHASE1TIME, 180) // // .input(CTRL_PHASE2TIME, 180) // // .input(CTRL_PHASE3TIME, 180) // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - .output(CTRL_STATUS, Status.ACTIVE_FORCED) // - .output(CTRL_FORCE_START_AT_SECONDS_OF_DAY, 54_180 /* current time */)); // + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true) // + .output(STATUS, Status.ACTIVE_FORCED) // + .output(FORCE_START_AT_SECONDS_OF_DAY, 54_180 /* current time */)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest3.java b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest3.java index f5d21a8d14d..c8d2a90b648 100644 --- a/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest3.java +++ b/io.openems.edge.controller.io.heatingelement/test/io/openems/edge/controller/io/heatingelement/ControllerIoHeatingElementImplTest3.java @@ -1,5 +1,9 @@ package io.openems.edge.controller.io.heatingelement; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT2; + import java.time.Instant; import java.time.ZoneOffset; @@ -7,7 +11,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; @@ -19,13 +22,6 @@ public class ControllerIoHeatingElementImplTest3 { - private static final String CTRL_ID = "ctrl0"; - private static final String IO_ID = "io0"; - - private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3"); - private static ControllerTest prepareTest(Mode mode, Level level) throws OpenemsNamedException, Exception { return new ControllerTest(new ControllerIoHeatingElementImpl()) // .addReference("componentManager", @@ -35,10 +31,10 @@ private static ControllerTest prepareTest(Mode mode, Level level) throws Openems .addReference("sum", new DummySum()) // .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setOutputChannelPhaseL1(IO_OUTPUT1.toString()) // - .setOutputChannelPhaseL2(IO_OUTPUT2.toString()) // - .setOutputChannelPhaseL3(IO_OUTPUT3.toString()) // + .setId("ctrl0") // + .setOutputChannelPhaseL1("io0/InputOutput0") // + .setOutputChannelPhaseL2("io0/InputOutput1") // + .setOutputChannelPhaseL3("io0/InputOutput2") // .setEndTime("15:45:00") // .setPowerOfPhase(2000) // .setMode(mode) // @@ -53,50 +49,50 @@ private static ControllerTest prepareTest(Mode mode, Level level) throws Openems public void testOff() throws Exception { prepareTest(Mode.MANUAL_OFF, Level.LEVEL_3) // .next(new TestCase() // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - ); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false)) // + .deactivate(); } @Test public void testOnLevel0() throws Exception { prepareTest(Mode.MANUAL_ON, Level.LEVEL_0) // .next(new TestCase() // - .output(IO_OUTPUT1, false) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - ); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false)) // + .deactivate(); } @Test public void testOnLevel1() throws Exception { prepareTest(Mode.MANUAL_ON, Level.LEVEL_1) // .next(new TestCase() // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, false) // - .output(IO_OUTPUT3, false) // - ); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, false) // + .output("io0", INPUT_OUTPUT2, false)) // + .deactivate(); } @Test public void testOnLevel2() throws Exception { prepareTest(Mode.MANUAL_ON, Level.LEVEL_2) // .next(new TestCase() // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, false) // - ); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, false)) // + .deactivate(); } @Test public void testOnLevel3() throws Exception { prepareTest(Mode.MANUAL_ON, Level.LEVEL_3) // .next(new TestCase() // - .output(IO_OUTPUT1, true) // - .output(IO_OUTPUT2, true) // - .output(IO_OUTPUT3, true) // - ); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true) // + .output("io0", INPUT_OUTPUT2, true)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.io.heatpump.sgready/test/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImplTest.java b/io.openems.edge.controller.io.heatpump.sgready/test/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImplTest.java index 25c1389f41a..a1e14ab2de5 100644 --- a/io.openems.edge.controller.io.heatpump.sgready/test/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImplTest.java +++ b/io.openems.edge.controller.io.heatpump.sgready/test/io/openems/edge/controller/io/heatpump/sgready/ControllerIoHeatPumpSgReadyImplTest.java @@ -1,13 +1,19 @@ package io.openems.edge.controller.io.heatpump.sgready; -import java.time.Instant; -import java.time.ZoneOffset; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_DISCHARGE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_SOC; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.controller.io.heatpump.sgready.ControllerIoHeatPumpSgReady.ChannelId.AWAITING_HYSTERESIS; +import static io.openems.edge.controller.io.heatpump.sgready.ControllerIoHeatPumpSgReady.ChannelId.STATUS; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT0; +import static io.openems.edge.io.test.DummyInputOutput.ChannelId.INPUT_OUTPUT1; +import static java.time.temporal.ChronoUnit.SECONDS; + import java.time.temporal.ChronoUnit; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; @@ -16,173 +22,136 @@ public class ControllerIoHeatPumpSgReadyImplTest { - private static final String CTRL_ID = "ctrHeatPump0"; - private static final String IO_ID = "io0"; - - private static final String outputChannel1 = "io0/InputOutput0"; - private static final String outputChannel2 = "io0/InputOutput1"; - - private static final ChannelAddress STATUS = new ChannelAddress(CTRL_ID, "Status"); - private static final ChannelAddress AWAITING_HYSTERESIS = new ChannelAddress(CTRL_ID, "AwaitingHysteresis"); - private static final ChannelAddress IO_OUTPUT_CHANNEL1 = new ChannelAddress(IO_ID, "InputOutput0"); - private static final ChannelAddress IO_OUTPUT_CHANNEL2 = new ChannelAddress(IO_ID, "InputOutput1"); - - private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower"); - private static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc"); - private static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower"); - @Test public void manual_undefined_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.UNDEFINED) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.REGULAR) // - .output(IO_OUTPUT_CHANNEL1, false) // - .output(IO_OUTPUT_CHANNEL2, false)); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false)) // + .deactivate(); } @Test public void manual_regular_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.REGULAR) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.REGULAR) // - .output(IO_OUTPUT_CHANNEL1, false) // - .output(IO_OUTPUT_CHANNEL2, false)); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false)) // + .deactivate(); } @Test public void manual_recommendation_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.RECOMMENDATION) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.RECOMMENDATION) // - .output(IO_OUTPUT_CHANNEL1, false) // - .output(IO_OUTPUT_CHANNEL2, true)); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, true)) // + .deactivate(); } @Test public void manual_force_on_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.FORCE_ON) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.FORCE_ON) // - .output(IO_OUTPUT_CHANNEL1, true) // - .output(IO_OUTPUT_CHANNEL2, true)); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, true)) // + .deactivate(); } @Test public void manual_lock_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.MANUAL) // .setManualState(Status.LOCK) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.LOCK) // - .output(IO_OUTPUT_CHANNEL1, true) // - .output(IO_OUTPUT_CHANNEL2, false)); + .output("io0", INPUT_OUTPUT0, true) // + .output("io0", INPUT_OUTPUT1, false)) // + .deactivate(); } @Test public void automatic_regular_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.AUTOMATIC) // .setAutomaticForceOnCtrlEnabled(false) // .setAutomaticRecommendationCtrlEnabled(false) // .setAutomaticLockCtrlEnabled(false) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .build()) .next(new TestCase() // .output(STATUS, Status.REGULAR) // - .output(IO_OUTPUT_CHANNEL1, false) // - .output(IO_OUTPUT_CHANNEL2, false)); + .output("io0", INPUT_OUTPUT0, false) // + .output("io0", INPUT_OUTPUT1, false)) // + .deactivate(); } @Test public void automatic_normal_config_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // - .addReference("componentManager", new DummyComponentManager(clock)) // + .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.AUTOMATIC) // .setAutomaticRecommendationCtrlEnabled(true) // .setAutomaticRecommendationSurplusPower(3000) // @@ -192,59 +161,57 @@ public void automatic_normal_config_test() throws Exception { .setAutomaticLockCtrlEnabled(true) // .setAutomaticLockGridBuyPower(5000) // .setAutomaticLockSoc(20) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .setMinimumSwitchingTime(0) // .build()) .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -4000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, -3000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -3000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, 500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, -2700) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -2700) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, -150) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 88) // + .input(GRID_ACTIVE_POWER, -150) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 88) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 88) // + .input(GRID_ACTIVE_POWER, 500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 88) // .output(STATUS, Status.REGULAR)) .next(new TestCase() // - .input(SUM_GRID_ACTIVE_POWER, 5500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 19) // - .output(STATUS, Status.LOCK)); + .input(GRID_ACTIVE_POWER, 5500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 19) // + .output(STATUS, Status.LOCK)) // + .deactivate(); } @Test public void automatic_switching_time_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - + final var clock = createDummyClock(); new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.AUTOMATIC) // .setAutomaticRecommendationCtrlEnabled(true) // .setAutomaticRecommendationSurplusPower(3000) // @@ -254,64 +221,62 @@ public void automatic_switching_time_test() throws Exception { .setAutomaticLockCtrlEnabled(true) // .setAutomaticLockGridBuyPower(5000) // .setAutomaticLockSoc(20) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .setMinimumSwitchingTime(60) // .build()) .next(new TestCase("Test 1") // - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -4000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase("Test 1") // - .timeleap(clock, 18, ChronoUnit.SECONDS) // + .timeleap(clock, 18, SECONDS) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 2") // - .timeleap(clock, 18, ChronoUnit.SECONDS) // + .timeleap(clock, 18, SECONDS) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 3") // - .timeleap(clock, 18, ChronoUnit.SECONDS) // + .timeleap(clock, 18, SECONDS) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 4") // - .timeleap(clock, 18, ChronoUnit.SECONDS) // + .timeleap(clock, 18, SECONDS) // .output(AWAITING_HYSTERESIS, false)) .next(new TestCase("Test 5") // - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -4000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase("Test 6") // - .timeleap(clock, 30, ChronoUnit.SECONDS) // + .timeleap(clock, 30, SECONDS) // .output(STATUS, Status.FORCE_ON) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 7") // - .timeleap(clock, 10, ChronoUnit.SECONDS) // - .input(SUM_GRID_ACTIVE_POWER, -500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 10, SECONDS) // + .input(GRID_ACTIVE_POWER, -500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.FORCE_ON) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 8") // - .timeleap(clock, 30, ChronoUnit.SECONDS) // - .input(SUM_GRID_ACTIVE_POWER, 500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 30, SECONDS) // + .input(GRID_ACTIVE_POWER, 500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(AWAITING_HYSTERESIS, false) // - .output(STATUS, Status.RECOMMENDATION) // - ); + .output(STATUS, Status.RECOMMENDATION)) // + .deactivate(); } @Test public void automatic_switching2_time_test() throws Exception { - - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800), ZoneOffset.UTC); - - new ControllerTest(new ControllerIoHeatPumpSgReadyImpl()).addReference("componentManager", // - new DummyComponentManager(clock)) // + final var clock = createDummyClock(); + new ControllerTest(new ControllerIoHeatPumpSgReadyImpl())// + .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("sum", new DummySum()) // - .addComponent(new DummyInputOutput(IO_ID)) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrHeatPump0") // .setMode(Mode.AUTOMATIC) // .setAutomaticRecommendationCtrlEnabled(true) // .setAutomaticRecommendationSurplusPower(3000) // @@ -321,63 +286,64 @@ public void automatic_switching2_time_test() throws Exception { .setAutomaticLockCtrlEnabled(true) // .setAutomaticLockGridBuyPower(5000) // .setAutomaticLockSoc(20) // - .setOutputChannel1(outputChannel1) // - .setOutputChannel2(outputChannel2) // + .setOutputChannel1("io0/InputOutput0") // + .setOutputChannel2("io0/InputOutput1") // .setMinimumSwitchingTime(60) // .build()) .next(new TestCase("Test 1") // - .input(SUM_GRID_ACTIVE_POWER, -4000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -4000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase("Test 2") // - .timeleap(clock, 50, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, -3000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 50, SECONDS)// + .input(GRID_ACTIVE_POWER, -3000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.RECOMMENDATION) // .output(AWAITING_HYSTERESIS, true)) // .next(new TestCase("Test 3") // - .timeleap(clock, 15, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, -3000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 15, SECONDS)// + .input(GRID_ACTIVE_POWER, -3000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(AWAITING_HYSTERESIS, false)) // .next(new TestCase("Test 3 - Results") // .output(AWAITING_HYSTERESIS, true) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase("Test 4") // - .timeleap(clock, 65, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, -3000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 65, SECONDS)// + .input(GRID_ACTIVE_POWER, -3000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(AWAITING_HYSTERESIS, false) // .output(STATUS, Status.FORCE_ON)) // .next(new TestCase("Test 5") // - .timeleap(clock, 15, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, 500) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .timeleap(clock, 15, SECONDS)// + .input(GRID_ACTIVE_POWER, 500) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(AWAITING_HYSTERESIS, false) // .output(STATUS, Status.RECOMMENDATION)) // .next(new TestCase("Test 6") // - .timeleap(clock, 65, ChronoUnit.SECONDS)// - .input(SUM_GRID_ACTIVE_POWER, 15000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 15) // + .timeleap(clock, 65, SECONDS)// + .input(GRID_ACTIVE_POWER, 15000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 15) // .output(AWAITING_HYSTERESIS, false) // .output(STATUS, Status.LOCK)) // .next(new TestCase("Test 7") // .timeleap(clock, 15, ChronoUnit.MINUTES) // - .input(SUM_GRID_ACTIVE_POWER, -2700) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 95) // + .input(GRID_ACTIVE_POWER, -2700) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 95) // .output(STATUS, Status.REGULAR)) // .next(new TestCase("Test 8") // .timeleap(clock, 15, ChronoUnit.MINUTES)// - .input(SUM_GRID_ACTIVE_POWER, -15000) // - .input(SUM_ESS_DISCHARGE_POWER, 0) // - .input(SUM_ESS_SOC, 88) // - .output(STATUS, Status.RECOMMENDATION)); // + .input(GRID_ACTIVE_POWER, -15000) // + .input(ESS_DISCHARGE_POWER, 0) // + .input(ESS_SOC, 88) // + .output(STATUS, Status.RECOMMENDATION)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.pvinverter.fixpowerlimit/test/io/openems/edge/controller/pvinverter/fixpowerlimit/ControllerPvInverterFixPowerLimitImplTest.java b/io.openems.edge.controller.pvinverter.fixpowerlimit/test/io/openems/edge/controller/pvinverter/fixpowerlimit/ControllerPvInverterFixPowerLimitImplTest.java index 03b22d8e4ce..f854cb3e17d 100644 --- a/io.openems.edge.controller.pvinverter.fixpowerlimit/test/io/openems/edge/controller/pvinverter/fixpowerlimit/ControllerPvInverterFixPowerLimitImplTest.java +++ b/io.openems.edge.controller.pvinverter.fixpowerlimit/test/io/openems/edge/controller/pvinverter/fixpowerlimit/ControllerPvInverterFixPowerLimitImplTest.java @@ -7,19 +7,16 @@ public class ControllerPvInverterFixPowerLimitImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String PV_INVERTER_ID = "pvInverter0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerPvInverterFixPowerLimitImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setPvInverterId(PV_INVERTER_ID) // + .setId("ctrl0") // + .setPvInverterId("pvInverter0") // .setPowerLimit(10000) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.pvinverter.selltogridlimit/test/io/openems/edge/controller/pvinverter/selltogridlimit/ControllerPvInverterSellToGridLimitImplTest.java b/io.openems.edge.controller.pvinverter.selltogridlimit/test/io/openems/edge/controller/pvinverter/selltogridlimit/ControllerPvInverterSellToGridLimitImplTest.java index f1d89b53e25..ef3bc2313cb 100644 --- a/io.openems.edge.controller.pvinverter.selltogridlimit/test/io/openems/edge/controller/pvinverter/selltogridlimit/ControllerPvInverterSellToGridLimitImplTest.java +++ b/io.openems.edge.controller.pvinverter.selltogridlimit/test/io/openems/edge/controller/pvinverter/selltogridlimit/ControllerPvInverterSellToGridLimitImplTest.java @@ -1,153 +1,148 @@ package io.openems.edge.controller.pvinverter.selltogridlimit; +import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.edge.common.type.TypeUtils.getAsType; +import static io.openems.edge.controller.pvinverter.selltogridlimit.ControllerPvInverterSellToGridLimitImpl.DEFAULT_MAX_ADJUSTMENT_RATE; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3; +import static io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter.ChannelId.ACTIVE_POWER_LIMIT; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.common.types.OpenemsType; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; -import io.openems.edge.common.type.TypeUtils; import io.openems.edge.controller.test.ControllerTest; import io.openems.edge.meter.test.DummyElectricityMeter; import io.openems.edge.pvinverter.test.DummyManagedSymmetricPvInverter; public class ControllerPvInverterSellToGridLimitImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String METER_ID = "meter0"; - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - private static final ChannelAddress GRID_ACTIVE_POWER_L1 = new ChannelAddress(METER_ID, "ActivePowerL1"); - private static final ChannelAddress GRID_ACTIVE_POWER_L2 = new ChannelAddress(METER_ID, "ActivePowerL2"); - private static final ChannelAddress GRID_ACTIVE_POWER_L3 = new ChannelAddress(METER_ID, "ActivePowerL3"); - - private static final String PV_INVERTER = "pvInverter0"; - private static final ChannelAddress PV_INVERTER_ACTIVE_POWER = new ChannelAddress(PV_INVERTER, "ActivePower"); - private static final ChannelAddress PV_INVERTER_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(PV_INVERTER, - "ActivePowerLimit"); - - private static final double ADJUST_RATE = ControllerPvInverterSellToGridLimitImpl.DEFAULT_MAX_ADJUSTMENT_RATE; - @Test public void symmetricMeterTest() throws Exception { new ControllerTest(new ControllerPvInverterSellToGridLimitImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyElectricityMeter(METER_ID)) // - .addComponent(new DummyManagedSymmetricPvInverter(PV_INVERTER)).activate(MyConfig.create() // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .addComponent(new DummyElectricityMeter("meter0")) // + .addComponent(new DummyManagedSymmetricPvInverter("pvInverter0")) // + .activate(MyConfig.create() // + .setId("ctrl0") // + .setMeterId("meter0") // .setAsymmetricMode(false) // .setMaximumSellToGridPower(10_000) // - .setPvInverterId(PV_INVERTER) // + .setPvInverterId("pvInverter0") // .build()) .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -15000) // - .input(PV_INVERTER_ACTIVE_POWER, 15000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 10000)) // + .input("meter0", ACTIVE_POWER, -15000) // + .input("pvInverter0", ACTIVE_POWER, 15000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 10000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -15000) // - .input(PV_INVERTER_ACTIVE_POWER, 10000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 10000 - 10000 * ADJUST_RATE))) // 5000 -> 8000 + .input("meter0", ACTIVE_POWER, -15000) // + .input("pvInverter0", ACTIVE_POWER, 10000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 10000 - 10000 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 5000 -> 8000 .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -13000) // - .input(PV_INVERTER_ACTIVE_POWER, 8000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 8000 - 8000 * ADJUST_RATE))) // 5000 -> 6400 + .input("meter0", ACTIVE_POWER, -13000) // + .input("pvInverter0", ACTIVE_POWER, 8000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 8000 - 8000 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 5000 -> 6400 .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -11400) // - .input(PV_INVERTER_ACTIVE_POWER, 6400) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 6400 - 6400 * ADJUST_RATE))) // 5000 -> 5120 + .input("meter0", ACTIVE_POWER, -11400) // + .input("pvInverter0", ACTIVE_POWER, 6400) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 6400 - 6400 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 5000 -> 5120 .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -10120) // - .input(PV_INVERTER_ACTIVE_POWER, 5120) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 5000)) // + .input("meter0", ACTIVE_POWER, -10120) // + .input("pvInverter0", ACTIVE_POWER, 5120) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 5000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER, -9000) // - .input(PV_INVERTER_ACTIVE_POWER, 5000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 6000)) // + .input("meter0", ACTIVE_POWER, -9000) // + .input("pvInverter0", ACTIVE_POWER, 5000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 6000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER, 0) // - .input(PV_INVERTER_ACTIVE_POWER, 6000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 6000 + 6000 * ADJUST_RATE))); // 16000 -> 7200 + .input("meter0", ACTIVE_POWER, 0) // + .input("pvInverter0", ACTIVE_POWER, 6000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 6000 + 6000 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 16000 -> 7200 + .deactivate(); } @Test public void asymmetricMeterTest() throws Exception { new ControllerTest(new ControllerPvInverterSellToGridLimitImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyElectricityMeter(METER_ID)) // - .addComponent(new DummyManagedSymmetricPvInverter(PV_INVERTER)).activate(MyConfig.create() // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .addComponent(new DummyElectricityMeter("meter0")) // + .addComponent(new DummyManagedSymmetricPvInverter("pvInverter0")) // + .activate(MyConfig.create() // + .setId("ctrl0") // + .setMeterId("meter0") // .setAsymmetricMode(true) // .setMaximumSellToGridPower(4_000) // 12_000 in total - .setPvInverterId(PV_INVERTER) // + .setPvInverterId("pvInverter0") // .build()) .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -2000) // - .input(GRID_ACTIVE_POWER_L2, -4000) // - .input(GRID_ACTIVE_POWER_L3, -3000) // - .input(PV_INVERTER_ACTIVE_POWER, 12000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 12000)) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -4000) // + .input("meter0", ACTIVE_POWER_L3, -3000) // + .input("pvInverter0", ACTIVE_POWER, 12000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 12000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -2000) // - .input(GRID_ACTIVE_POWER_L2, -5000) // - .input(GRID_ACTIVE_POWER_L3, -2000) // - .input(PV_INVERTER_ACTIVE_POWER, 12000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 12000 - 12000 * ADJUST_RATE))) // 9000 -> 9600 + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -5000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input("pvInverter0", ACTIVE_POWER, 12000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 12000 - 12000 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 9000 -> 9600 .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -1200) // - .input(GRID_ACTIVE_POWER_L2, -4200) // - .input(GRID_ACTIVE_POWER_L3, -1200) // - .input(PV_INVERTER_ACTIVE_POWER, 9600) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 9000)) // + .input("meter0", ACTIVE_POWER_L1, -1200) // + .input("meter0", ACTIVE_POWER_L2, -4200) // + .input("meter0", ACTIVE_POWER_L3, -1200) // + .input("pvInverter0", ACTIVE_POWER, 9600) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 9000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -1000) // - .input(GRID_ACTIVE_POWER_L2, -4000) // - .input(GRID_ACTIVE_POWER_L3, -1000) // - .input(PV_INVERTER_ACTIVE_POWER, 9000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 9000)) // + .input("meter0", ACTIVE_POWER_L1, -1000) // + .input("meter0", ACTIVE_POWER_L2, -4000) // + .input("meter0", ACTIVE_POWER_L3, -1000) // + .input("pvInverter0", ACTIVE_POWER, 9000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 9000)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -1000) // - .input(GRID_ACTIVE_POWER_L2, -3700) // - .input(GRID_ACTIVE_POWER_L3, -1000) // - .input(PV_INVERTER_ACTIVE_POWER, 9000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 9900)) // + .input("meter0", ACTIVE_POWER_L1, -1000) // + .input("meter0", ACTIVE_POWER_L2, -3700) // + .input("meter0", ACTIVE_POWER_L3, -1000) // + .input("pvInverter0", ACTIVE_POWER, 9000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 9900)) // .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -2000) // - .input(GRID_ACTIVE_POWER_L2, -5000) // - .input(GRID_ACTIVE_POWER_L3, -2000) // - .input(PV_INVERTER_ACTIVE_POWER, 9900) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 9900 - 9900 * ADJUST_RATE))) // 6900 -> 7920 + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -5000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input("pvInverter0", ACTIVE_POWER, 9900) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 9900 - 9900 * DEFAULT_MAX_ADJUSTMENT_RATE))) // 6900 -> 7920 .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, -2000) // - .input(GRID_ACTIVE_POWER_L2, -5000) // - .input(GRID_ACTIVE_POWER_L3, -2000) // - .input(PV_INVERTER_ACTIVE_POWER, 7920) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, - TypeUtils.getAsType(OpenemsType.INTEGER, 7920 - 7920 * ADJUST_RATE))); // 4920 -> 6336 + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -5000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input("pvInverter0", ACTIVE_POWER, 7920) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, + getAsType(INTEGER, 7920 - 7920 * DEFAULT_MAX_ADJUSTMENT_RATE))); // 4920 -> 6336 new ControllerTest(new ControllerPvInverterSellToGridLimitImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyElectricityMeter(METER_ID)) // - .addComponent(new DummyManagedSymmetricPvInverter(PV_INVERTER)).activate(MyConfig.create() // - .setId(CTRL_ID) // - .setMeterId(METER_ID) // + .addComponent(new DummyElectricityMeter("meter0")) // + .addComponent(new DummyManagedSymmetricPvInverter("pvInverter0")) // + .activate(MyConfig.create() // + .setId("ctrl0") // + .setMeterId("meter0") // .setAsymmetricMode(true) // .setMaximumSellToGridPower(4_000) // 12_000 in total - .setPvInverterId(PV_INVERTER) // + .setPvInverterId("pvInverter0") // .build()) .next(new TestCase() // - .input(GRID_ACTIVE_POWER_L1, 1000) // - .input(GRID_ACTIVE_POWER_L2, 2000) // - .input(GRID_ACTIVE_POWER_L3, 3000) // - .input(PV_INVERTER_ACTIVE_POWER, 1000) // - .output(PV_INVERTER_SET_ACTIVE_POWER_EQUALS, 16000)) // - ; + .input("meter0", ACTIVE_POWER_L1, 1000) // + .input("meter0", ACTIVE_POWER_L2, 2000) // + .input("meter0", ACTIVE_POWER_L3, 3000) // + .input("pvInverter0", ACTIVE_POWER, 1000) // + .output("pvInverter0", ACTIVE_POWER_LIMIT, 16000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.balancingschedule/test/io/openems/edge/controller/symmetric/balancingschedule/ControllerEssBalancingScheduleImplTest.java b/io.openems.edge.controller.symmetric.balancingschedule/test/io/openems/edge/controller/symmetric/balancingschedule/ControllerEssBalancingScheduleImplTest.java index 7501e1d9d60..ed88ef4c8be 100644 --- a/io.openems.edge.controller.symmetric.balancingschedule/test/io/openems/edge/controller/symmetric/balancingschedule/ControllerEssBalancingScheduleImplTest.java +++ b/io.openems.edge.controller.symmetric.balancingschedule/test/io/openems/edge/controller/symmetric/balancingschedule/ControllerEssBalancingScheduleImplTest.java @@ -1,39 +1,31 @@ package io.openems.edge.controller.symmetric.balancingschedule; +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.edge.controller.symmetric.balancingschedule.ControllerEssBalancingSchedule.ChannelId.GRID_ACTIVE_POWER_SET_POINT; +import static io.openems.edge.controller.symmetric.balancingschedule.ControllerEssBalancingSchedule.ChannelId.NO_ACTIVE_SETPOINT; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS_WITH_PID; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.GRID_MODE; +import static java.time.temporal.ChronoUnit.SECONDS; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; -import io.openems.common.utils.JsonUtils; import io.openems.edge.common.sum.GridMode; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssBalancingScheduleImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - private static final String METER_ID = "meter0"; - - private static final ChannelAddress CTRL_NO_ACTIVE_SETPOINT = new ChannelAddress(CTRL_ID, "NoActiveSetpoint"); - private static final ChannelAddress CTRL_GRID_ACTIVE_POWER_SET_POINT = new ChannelAddress(CTRL_ID, - "GridActivePowerSetPoint"); - - private static final ChannelAddress ESS_GRID_MODE = new ChannelAddress(ESS_ID, "GridMode"); - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress SET_ACTIVE_POWER_EQUALS_WITH_PID = new ChannelAddress(ESS_ID, - "SetActivePowerEqualsWithPid"); - - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { final var start = 1577836800L; @@ -42,47 +34,47 @@ public void test() throws Exception { new ControllerTest(new ControllerEssBalancingScheduleImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("ess", new DummyManagedSymmetricEss(ESS_ID)) // - .addReference("meter", new DummyElectricityMeter(METER_ID)) // + .addReference("ess", new DummyManagedSymmetricEss("ess0")) // + .addReference("meter", new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // - .setSchedule(JsonUtils.buildJsonArray()// - .add(JsonUtils.buildJsonObject()// + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // + .setSchedule(buildJsonArray()// + .add(buildJsonObject()// .addProperty("startTimestamp", start + 500) // .addProperty("duration", 900) // .addProperty("activePowerSetPoint", 0) // .build()) // - .add(JsonUtils.buildJsonObject()// + .add(buildJsonObject()// .addProperty("startTimestamp", start + 500 + 800) // .addProperty("duration", 900) // .addProperty("activePowerSetPoint", 3000) // - .build() // - ).build().toString() // - ).build()) // + .build()) // + .build().toString()) // + .build()) // .next(new TestCase("No active setpoint") // - .input(ESS_GRID_MODE, GridMode.ON_GRID) // - .input(GRID_ACTIVE_POWER, 4000) // - .input(ESS_ACTIVE_POWER, 1000) // - .output(CTRL_NO_ACTIVE_SETPOINT, true)) // + .input("ess0", GRID_MODE, GridMode.ON_GRID) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 4000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 1000) // + .output(NO_ACTIVE_SETPOINT, true)) // .next(new TestCase("Balance to 0") // - .timeleap(clock, 500, ChronoUnit.SECONDS) // - .input(GRID_ACTIVE_POWER, 4000) // - .input(ESS_ACTIVE_POWER, 1000) // - .output(CTRL_NO_ACTIVE_SETPOINT, false) // - .output(SET_ACTIVE_POWER_EQUALS_WITH_PID, 5000)) // + .timeleap(clock, 500, SECONDS) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 4000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 1000) // + .output(NO_ACTIVE_SETPOINT, false) // + .output("ess0", SET_ACTIVE_POWER_EQUALS_WITH_PID, 5000)) // .next(new TestCase("Balance to -2000 via Channel") // - .input(CTRL_GRID_ACTIVE_POWER_SET_POINT, -2000) // - .input(GRID_ACTIVE_POWER, 4000) // - .input(ESS_ACTIVE_POWER, 1000) // - .output(SET_ACTIVE_POWER_EQUALS_WITH_PID, 7000)) // + .input(GRID_ACTIVE_POWER_SET_POINT, -2000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 4000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 1000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS_WITH_PID, 7000)) // .next(new TestCase("Balance to 3000") // - .timeleap(clock, 800, ChronoUnit.SECONDS) // - .input(GRID_ACTIVE_POWER, 4000) // - .input(ESS_ACTIVE_POWER, 1000) // - .output(CTRL_NO_ACTIVE_SETPOINT, false) // - .output(SET_ACTIVE_POWER_EQUALS_WITH_PID, 2000)) // - ; + .timeleap(clock, 800, SECONDS) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 4000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 1000) // + .output(NO_ACTIVE_SETPOINT, false) // + .output("ess0", SET_ACTIVE_POWER_EQUALS_WITH_PID, 2000)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.fixreactivepower/test/io/openems/edge/controller/symmetric/fixreactivepower/ControllerEssFixReactivePowerImplTest.java b/io.openems.edge.controller.symmetric.fixreactivepower/test/io/openems/edge/controller/symmetric/fixreactivepower/ControllerEssFixReactivePowerImplTest.java index 9ffd8bebe22..f576936930b 100644 --- a/io.openems.edge.controller.symmetric.fixreactivepower/test/io/openems/edge/controller/symmetric/fixreactivepower/ControllerEssFixReactivePowerImplTest.java +++ b/io.openems.edge.controller.symmetric.fixreactivepower/test/io/openems/edge/controller/symmetric/fixreactivepower/ControllerEssFixReactivePowerImplTest.java @@ -7,19 +7,16 @@ public class ControllerEssFixReactivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEssFixReactivePowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setPower(1000) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.limitactivepower/test/io/openems/edge/controller/symmetric/limitactivepower/ControllerEssLimitActivePowerImplTest.java b/io.openems.edge.controller.symmetric.limitactivepower/test/io/openems/edge/controller/symmetric/limitactivepower/ControllerEssLimitActivePowerImplTest.java index c9261c90ed3..ace177e6000 100644 --- a/io.openems.edge.controller.symmetric.limitactivepower/test/io/openems/edge/controller/symmetric/limitactivepower/ControllerEssLimitActivePowerImplTest.java +++ b/io.openems.edge.controller.symmetric.limitactivepower/test/io/openems/edge/controller/symmetric/limitactivepower/ControllerEssLimitActivePowerImplTest.java @@ -7,21 +7,18 @@ public class ControllerEssLimitActivePowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEssLimitActivePowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMaxChargePower(1000) // .setMaxDischargePower(1000) // .setValidatePowerConstraints(false) // - .build()); // - ; + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.peakshaving/test/io/openems/edge/controller/symmetric/peakshaving/ControllerEssPeakShavingImplTest.java b/io.openems.edge.controller.symmetric.peakshaving/test/io/openems/edge/controller/symmetric/peakshaving/ControllerEssPeakShavingImplTest.java index b1af67695a4..5249a7598aa 100644 --- a/io.openems.edge.controller.symmetric.peakshaving/test/io/openems/edge/controller/symmetric/peakshaving/ControllerEssPeakShavingImplTest.java +++ b/io.openems.edge.controller.symmetric.peakshaving/test/io/openems/edge/controller/symmetric/peakshaving/ControllerEssPeakShavingImplTest.java @@ -1,97 +1,91 @@ package io.openems.edge.controller.symmetric.peakshaving; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyPower; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssPeakShavingImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { new ControllerTest(new ControllerEssPeakShavingImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .setPower(new DummyPower(0.3, 0.3, 0.1))) // - .addComponent(new DummyElectricityMeter(METER_ID)) // + .addComponent(new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setPeakShavingPower(100_000) // .setRechargePower(50_000) // .build()) .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 6000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 6000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 12000)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 12000)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 3793) // - .input(METER_ACTIVE_POWER, 120000 - 3793) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 16483)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 3793) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 3793) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 16483)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 8981) // - .input(METER_ACTIVE_POWER, 120000 - 8981) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19649)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 8981) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 8981) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19649)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 13723) // - .input(METER_ACTIVE_POWER, 120000 - 13723) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21577)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 13723) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 13723) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21577)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 17469) // - .input(METER_ACTIVE_POWER, 120000 - 17469) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22436)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 17469) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 17469) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22436)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20066) // - .input(METER_ACTIVE_POWER, 120000 - 20066) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22531)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20066) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 20066) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22531)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21564) // - .input(METER_ACTIVE_POWER, 120000 - 21564) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 22171)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21564) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 21564) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 22171)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 22175) // - .input(METER_ACTIVE_POWER, 120000 - 22175) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21608)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22175) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 22175) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21608)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 22173) // - .input(METER_ACTIVE_POWER, 120000 - 22173) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 21017)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 22173) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 22173) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 21017)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21816) // - .input(METER_ACTIVE_POWER, 120000 - 21816) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 20508)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21816) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 21816) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 20508)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 21311) // - .input(METER_ACTIVE_POWER, 120000 - 21311) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 20129)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 21311) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 21311) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 20129)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20803) // - .input(METER_ACTIVE_POWER, 120000 - 20803) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19889)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20803) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 20803) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19889)) // .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 20377) // - .input(METER_ACTIVE_POWER, 120000 - 20377) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 19767)); // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 20377) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000 - 20377) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 19767)) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.randompower/test/io/openems/edge/controller/symmetric/randompower/ControllerEssRandomPowerImplTest.java b/io.openems.edge.controller.symmetric.randompower/test/io/openems/edge/controller/symmetric/randompower/ControllerEssRandomPowerImplTest.java index 94172daab79..ab9b1bfd8f3 100644 --- a/io.openems.edge.controller.symmetric.randompower/test/io/openems/edge/controller/symmetric/randompower/ControllerEssRandomPowerImplTest.java +++ b/io.openems.edge.controller.symmetric.randompower/test/io/openems/edge/controller/symmetric/randompower/ControllerEssRandomPowerImplTest.java @@ -7,19 +7,17 @@ public class ControllerEssRandomPowerImplTest { - private static final String CTRL_ID = "ctrl0"; - private static final String ESS_ID = "ess0"; - @Test public void test() throws Exception { new ControllerTest(new ControllerEssRandomPowerImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // + .setId("ctrl0") // + .setEssId("ess0") // .setMinPower(0) // .setMaxPower(1000) // - .build()); // + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.controller.symmetric.timeslotpeakshaving/test/io/openems/edge/controller/timeslotpeakshaving/ControllerEssTimeslotPeakshavingImplTest.java b/io.openems.edge.controller.symmetric.timeslotpeakshaving/test/io/openems/edge/controller/timeslotpeakshaving/ControllerEssTimeslotPeakshavingImplTest.java index f8f7066afcf..fb6cb7828ec 100644 --- a/io.openems.edge.controller.symmetric.timeslotpeakshaving/test/io/openems/edge/controller/timeslotpeakshaving/ControllerEssTimeslotPeakshavingImplTest.java +++ b/io.openems.edge.controller.symmetric.timeslotpeakshaving/test/io/openems/edge/controller/timeslotpeakshaving/ControllerEssTimeslotPeakshavingImplTest.java @@ -1,47 +1,40 @@ package io.openems.edge.controller.timeslotpeakshaving; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.SET_ACTIVE_POWER_EQUALS; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; +import static java.time.temporal.ChronoUnit.MINUTES; + import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.GridMode; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyPower; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class ControllerEssTimeslotPeakshavingImplTest { - private static final String CTRL_ID = "ctrl0"; - - private static final String ESS_ID = "ess0"; - private static final ChannelAddress ESS_SOC = new ChannelAddress(ESS_ID, "Soc"); - private static final ChannelAddress ESS_ACTIVE_POWER = new ChannelAddress(ESS_ID, "ActivePower"); - private static final ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new ChannelAddress(ESS_ID, - "SetActivePowerEquals"); - - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_ACTIVE_POWER = new ChannelAddress(METER_ID, "ActivePower"); - @Test public void test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-02-03T08:30:00.00Z"), ZoneOffset.UTC); new ControllerTest(new ControllerEssTimeslotPeakshavingImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyManagedSymmetricEss(ESS_ID) // + .addComponent(new DummyManagedSymmetricEss("ess0") // .setPower(new DummyPower(0.3, 0.3, 0.1)) // .withGridMode(GridMode.ON_GRID)) // - .addComponent(new DummyElectricityMeter(METER_ID)) // + .addComponent(new DummyElectricityMeter("meter0")) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .setMeterId(METER_ID) // + .setId("ctrl0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setPeakShavingPower(100_000) // .setRechargePower(50_000) // .setSlowChargePower(50_000) // @@ -60,34 +53,37 @@ public void test() throws Exception { .setSunday(true) // .build()) .next(new TestCase() // - .input(ESS_ACTIVE_POWER, 0) // - .input(ESS_SOC, 90) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, null)) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 0) // + .input("ess0", SOC, 90) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, null)) // .next(new TestCase() // - .timeleap(clock, 31, ChronoUnit.MINUTES)/* current time is 09:31, run in slow charge state */ - .input(ESS_SOC, 96) // - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 13500)) // + .timeleap(clock, 31, MINUTES)/* current time is 09:31, run in slow charge state */ + .input("ess0", SOC, 96) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 13500)) // .next(new TestCase() // - .timeleap(clock, 31, ChronoUnit.MINUTES)/* current time is 09:31, run in hysterisis state */ - .input(ESS_SOC, 100) // - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000)) // nothing set on, Ess's setActivePower + .timeleap(clock, 31, MINUTES)/* current time is 09:31, run in hysterisis state */ + .input("ess0", SOC, 100) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + // nothing set on, Ess's setActivePower + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000)) .next(new TestCase() // - .input(ESS_SOC, 94) // - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 27000)) // + .input("ess0", SOC, 94) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 27000)) // .next(new TestCase() // - .timeleap(clock, 75, ChronoUnit.MINUTES)/* current time is 10:47, run in high threshold state */ - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000) // - .output(ESS_SET_ACTIVE_POWER_EQUALS, 33000)) // + .timeleap(clock, 75, MINUTES)/* current time is 10:47, run in high threshold state */ + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000) // + .output("ess0", SET_ACTIVE_POWER_EQUALS, 33000)) // .next(new TestCase() // - .timeleap(clock, 75, ChronoUnit.MINUTES)/* current time is 12:02 run in normal state */ - .input(ESS_ACTIVE_POWER, 5000) // - .input(METER_ACTIVE_POWER, 120000)); // nothing set on, Ess's setActivePower + .timeleap(clock, 75, MINUTES)/* current time is 12:02 run in normal state */ + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 5000) // + // nothing set on, Ess's setActivePower + .input("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 120000)) // + .deactivate(); } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java index 258c66c960f..1904864db00 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/EvcsProps.java @@ -1,7 +1,9 @@ package io.openems.edge.app.evcs; +import static io.openems.edge.app.common.props.CommonProps.defaultDef; import static io.openems.edge.core.appmanager.formly.enums.InputType.NUMBER; +import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; @@ -11,7 +13,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; -import io.openems.edge.app.common.props.CommonProps; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.core.appmanager.AppDef; @@ -28,6 +29,7 @@ import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.formly.builder.FieldGroupBuilder; import io.openems.edge.core.appmanager.formly.enums.DisplayType; +import io.openems.edge.evcs.api.PhaseRotation; public final class EvcsProps { @@ -45,7 +47,7 @@ private EvcsProps() { public static AppDef numberOfChargePoints(// final int maxValue // ) { - return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def // + return AppDef.copyOfGeneric(defaultDef(), def -> def // .setTranslatedLabel("App.Evcs.numberOfChargingStations.label") // .setDefaultValue(1) // .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> // @@ -103,7 +105,7 @@ private static void field(// */ public static AppDef clusterMaxHardwarePower( Nameable acceptProperty) { - return AppDef.copyOfGeneric(CommonProps.defaultDef(), def -> def // + return AppDef.copyOfGeneric(defaultDef(), def -> def // .setTranslatedLabel("App.Evcs.Cluster.maxChargeFromGrid.label") // .setAllowedToSave(false) // .setIsAllowedToSee((app, property, l, parameter, user) -> { @@ -182,4 +184,20 @@ private static final boolean isClusterInstalled(ComponentManager componentManage return false; } + /** + * Creates a {@link AppDef} for a {@link PhaseRotation}. + * + * @return the {@link AppDef} + */ + public static final AppDef phaseRotation() { + return AppDef.copyOfGeneric(defaultDef(), def -> def // + .setTranslatedLabel("App.Evcs.phaseRotation.label") // + .setTranslatedDescription("App.Evcs.phaseRotation.description") // + .setDefaultValue(PhaseRotation.L1_L2_L3) // + .setField(JsonFormlyUtil::buildSelectFromNameable, (app, property, l, parameter, field) -> { + field.setOptions(Arrays.stream(PhaseRotation.values()) // + .map(PhaseRotation::name) // + .toList()); + })); + } } diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java index ed993465b63..59586a5769e 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/HardyBarthEvcs.java @@ -73,6 +73,7 @@ "EVCS_ID": "evcs0", "CTRL_EVCS_ID": "ctrlEvcs0", "IP": "192.168.25.30", + "PHASE_ROTATION":"L1_L2_L3", "NUMBER_OF_CHARGING_STATIONS": 1, "EVCS_ID_CP_2": "evcs0", "CTRL_EVCS_ID_CP_2": "ctrlEvcs0", @@ -144,6 +145,7 @@ public static enum Property implements PropertyParent { .collect(Exp.toArrayExpression()) // .every(i -> Exp.currentModelValue(EVCS_ID).notEqual(i)))); }))), // + PHASE_ROTATION(AppDef.copyOfGeneric(EvcsProps.phaseRotation())), // ; private final AppDef def; @@ -317,6 +319,7 @@ OpenemsNamedException> appPropertyConfigurationFactory() { final var alias = this.getString(p, l, SubPropertyFirstChargepoint.ALIAS); final var ip = this.getString(p, l, SubPropertyFirstChargepoint.IP); final var evcsId = this.getId(t, p, Property.EVCS_ID); + final var phaseRotation = this.getString(p, l, Property.PHASE_ROTATION); final var ctrlEvcsId = this.getId(t, p, Property.CTRL_EVCS_ID); schedulerIds.add(new SchedulerComponent(ctrlEvcsId, "Controller.Evcs", this.getAppId())); @@ -324,6 +327,7 @@ OpenemsNamedException> appPropertyConfigurationFactory() { final var components = Lists.newArrayList(// new EdgeConfig.Component(evcsId, alias, factorieId, JsonUtils.buildJsonObject() // .addProperty("ip", ip) // + .addPropertyIfNotNull("phaseRotation", phaseRotation) // .build()), // new EdgeConfig.Component(ctrlEvcsId, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // .addProperty("evcs.id", evcsId) // @@ -339,6 +343,7 @@ OpenemsNamedException> appPropertyConfigurationFactory() { components.add(new EdgeConfig.Component(evcsIdCp2, aliasCp2, factorieId, JsonUtils.buildJsonObject() // .addProperty("ip", ipCp2) // + .addPropertyIfNotNull("phaseRotation", phaseRotation) // .build())); components.add(new EdgeConfig.Component(ctrlEvcsIdCp2, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // diff --git a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java index e9a680cb831..2ab629b5ae7 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java +++ b/io.openems.edge.core/src/io/openems/edge/app/evcs/KebaEvcs.java @@ -55,7 +55,8 @@ "properties":{ "EVCS_ID": "evcs0", "CTRL_EVCS_ID": "ctrlEvcs0", - "IP":"192.168.25.11" + "IP":"192.168.25.11", + "PHASE_ROTATION":"L1_L2_L3" }, "appDescriptor": { "websiteUrl": {@link AppDescriptor#getWebsiteUrl()} @@ -80,6 +81,7 @@ public enum Property implements Type def; @@ -120,6 +122,7 @@ protected ThrowingTriFunction, L // values the user enters final var ip = this.getString(p, l, Property.IP); final var alias = this.getString(p, l, Property.ALIAS); + final var phaseRotation = this.getString(p, l, Property.PHASE_ROTATION); // values which are being auto generated by the appmanager final var evcsId = this.getId(t, p, Property.EVCS_ID); @@ -133,6 +136,7 @@ protected ThrowingTriFunction, L var components = Lists.newArrayList(// new EdgeConfig.Component(evcsId, alias, "Evcs.Keba.KeContact", JsonUtils.buildJsonObject() // .addPropertyIfNotNull("ip", ip) // + .addPropertyIfNotNull("phaseRotation", phaseRotation) // .build()), // new EdgeConfig.Component(ctrlEvcsId, controllerAlias, "Controller.Evcs", JsonUtils.buildJsonObject() // .addProperty("evcs.id", evcsId) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties index 16414b2d0d5..927b28bf399 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -123,6 +123,8 @@ App.Evcs.controller.alias = Ladestation Steuerung App.Evcs.ip.description = Die IP-Adresse der Ladestation. App.Evcs.chargingStation.label = Ladepunkt {0} App.Evcs.numberOfChargingStations.label = Anzahl Ladepunkte +App.Evcs.phaseRotation.label = Phasenrotation +App.Evcs.phaseRotation.description = Verkabelung der einzelnen Phasen der Ladestation zu den Phasen im Netz App.Evcs.Cluster.Name = Multiladepunkt Management App.Evcs.Cluster.Name.short = Multiladepunkt Management diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties index 2566a3fa36a..538f2862b93 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -123,6 +123,8 @@ App.Evcs.controller.alias = Charging station control App.Evcs.ip.description = The IP address of the charging station. App.Evcs.chargingStation.label = Charging point {0} App.Evcs.numberOfChargingStations.label = Number of charging points +App.Evcs.phaseRotation.label = Phase rotation +App.Evcs.phaseRotation.description = Wiring of the individual phases of the charging station to actual phases of the grid App.Evcs.Cluster.Name = Multi-charging point management App.Evcs.Cluster.Name.short = Multi-charging point management diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/EdgeConfigWorker.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/EdgeConfigWorker.java index 9679cfb5a6a..6c90e120dab 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/EdgeConfigWorker.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/EdgeConfigWorker.java @@ -595,7 +595,7 @@ private static TreeMap convertProperties(String componentId for (EdgeConfig.Factory.Property property : factory.getProperties()) { var key = property.getId(); - if (EdgeConfig.ignorePropertyKey(key) || EdgeConfig.ignoreComponentPropertyKey(componentId, key)) { + if (EdgeConfig.ignorePropertyKey(key)) { // Ignore this Property continue; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java index 41b9dd0f734..7e42538a6c0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.common.currency.CurrencyConfig; +import io.openems.common.types.CurrencyConfig; @ObjectClassDefinition(// name = "Core Meta", // diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java index 27843503c1e..0d581be7eab 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java @@ -22,6 +22,7 @@ import io.openems.edge.common.channel.LongReadChannel; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.currency.Currency; import io.openems.edge.common.meta.Meta; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveTable; @@ -90,6 +91,6 @@ public ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { } private void applyConfig(Config config) { - this._setCurrency(config.currency().toCurrency()); + this._setCurrency(Currency.fromCurrencyConfig(config.currency())); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java b/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java index ea45fc4a94a..9ab8803e015 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/sum/SumImpl.java @@ -309,13 +309,14 @@ private void calculateChannelValues() { } else if (component instanceof Evcs evcs) { /* * Electric Vehicle Charging Station + * TODO replace with ElectricityMeter.isManagedConsumption() */ if (evcs instanceof MetaEvcs) { // ignore this Evcs continue; } - managedConsumptionActivePower.addValue(evcs.getChargePowerChannel()); + managedConsumptionActivePower.addValue(evcs.getActivePowerChannel()); } else if (component instanceof TimeOfUseTariff tou) { /* diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java index 699921aa573..c67423251bc 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java +++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java @@ -18,7 +18,7 @@ import io.openems.edge.core.appmanager.Apps; import io.openems.edge.core.appmanager.DummyPseudoComponentManager; import io.openems.edge.core.appmanager.OpenemsAppInstance; -import io.openems.edge.io.test.DummyInputOutput; +import io.openems.edge.io.test.DummyCustomInputOutput; public class TestFeneconHome30DefaultRelays { @@ -96,10 +96,10 @@ private final OpenemsAppInstance createFullHomeWithDummyIo() throws Exception { final var instance = TestFeneconHome30.createFullHome30(this.appManagerTestBundle, DUMMY_ADMIN); this.appManagerTestBundle.componentManger.handleDeleteComponentConfigRequest(DUMMY_ADMIN, new DeleteComponentConfigRequest("io0")); - final var dummyRelay = new DummyInputOutput("io0", "RELAY", 1, 8); + final var dummyRelay = new DummyCustomInputOutput("io0", "RELAY", 1, 8); this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay.id()); ((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay); - final var dummyRelay1 = new DummyInputOutput("io1", "RELAY", 1, 8); + final var dummyRelay1 = new DummyCustomInputOutput("io1", "RELAY", 1, 8); this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay1.id()); ((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay1); return instance; diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java index 983785d6092..b8cf0b2bd19 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java +++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java @@ -18,7 +18,7 @@ import io.openems.edge.core.appmanager.Apps; import io.openems.edge.core.appmanager.DummyPseudoComponentManager; import io.openems.edge.core.appmanager.OpenemsAppInstance; -import io.openems.edge.io.test.DummyInputOutput; +import io.openems.edge.io.test.DummyCustomInputOutput; public class TestFeneconHomeDefaultRelays { @@ -92,10 +92,10 @@ private final OpenemsAppInstance createFullHomeWithDummyIo() throws Exception { final var instance = TestFeneconHome.createFullHome(this.appManagerTestBundle, DUMMY_ADMIN); this.appManagerTestBundle.componentManger.handleDeleteComponentConfigRequest(DUMMY_ADMIN, new DeleteComponentConfigRequest("io0")); - final var dummyRelay = new DummyInputOutput("io0", "RELAY", 1, 4); + final var dummyRelay = new DummyCustomInputOutput("io0", "RELAY", 1, 4); this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay.id()); ((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay); - final var dummyRelay1 = new DummyInputOutput("io1", "RELAY", 1, 4); + final var dummyRelay1 = new DummyCustomInputOutput("io1", "RELAY", 1, 4); this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay1.id()); ((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay1); return instance; diff --git a/io.openems.edge.core/test/io/openems/edge/core/meta/MetaImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/meta/MetaImplTest.java index 946dafc96a0..3c6a00657d5 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/meta/MetaImplTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/meta/MetaImplTest.java @@ -1,6 +1,6 @@ package io.openems.edge.core.meta; -import static io.openems.edge.common.currency.CurrencyConfig.EUR; +import static io.openems.common.types.CurrencyConfig.EUR; import org.junit.Test; diff --git a/io.openems.edge.core/test/io/openems/edge/core/meta/MyConfig.java b/io.openems.edge.core/test/io/openems/edge/core/meta/MyConfig.java index b99b018b74a..c269dc60237 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/meta/MyConfig.java +++ b/io.openems.edge.core/test/io/openems/edge/core/meta/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.core.meta; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.common.currency.CurrencyConfig; +import io.openems.common.types.CurrencyConfig; import io.openems.edge.common.meta.Meta; @SuppressWarnings("all") diff --git a/io.openems.edge.core/test/io/openems/edge/core/predictormanager/PredictorManagerImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/predictormanager/PredictorManagerImplTest.java index 934e934489e..3bc99e30cdc 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/predictormanager/PredictorManagerImplTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/predictormanager/PredictorManagerImplTest.java @@ -1,19 +1,17 @@ package io.openems.edge.core.predictormanager; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static io.openems.edge.predictor.api.prediction.Prediction.EMPTY_PREDICTION; import static java.time.temporal.ChronoUnit.DAYS; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import java.time.Instant; -import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.List; import org.junit.Test; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.test.TimeLeapClock; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.DummySum; import io.openems.edge.common.test.ComponentTest; @@ -60,7 +58,7 @@ public class PredictorManagerImplTest { @Test public void test() throws OpenemsException, Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); final var cm = new DummyComponentManager(clock); final var sum = new DummySum(); final var midnight = ZonedDateTime.now(clock).truncatedTo(DAYS); diff --git a/io.openems.edge.core/test/io/openems/edge/core/sum/ExtremeEverValuesTest.java b/io.openems.edge.core/test/io/openems/edge/core/sum/ExtremeEverValuesTest.java index 86641c06232..4befb39052f 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/sum/ExtremeEverValuesTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/sum/ExtremeEverValuesTest.java @@ -1,21 +1,20 @@ package io.openems.edge.core.sum; import static io.openems.edge.common.test.TestUtils.activateNextProcessImage; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static io.openems.edge.common.test.TestUtils.withValue; import static io.openems.edge.core.sum.ExtremeEverValues.Range.NEGATIVE; import static io.openems.edge.core.sum.ExtremeEverValues.Range.POSTIVE; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import java.io.IOException; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.sum.Sum; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -25,12 +24,12 @@ public class ExtremeEverValuesTest { @Test public void test() throws OpenemsException, Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T20:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); var cm = new DummyConfigurationAdmin(); var sum = new SumImpl(); new ComponentTest(sum) // .addReference("cm", cm) // - .addReference("componentManager", new DummyComponentManager()) // + .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // .setGridMinActivePower(0) // .setIgnoreStateComponents() // @@ -63,14 +62,14 @@ public void test() throws OpenemsException, Exception { assertEquals(0, getProperty(cm, "gridMinActivePower")); // Still the same - clock.leap(24, ChronoUnit.HOURS); + clock.leap(24, HOURS); withValue(sum, Sum.ChannelId.GRID_ACTIVE_POWER, -100); sut.update(sum, cm); activateNextProcessImage(sum); assertEquals(0, getProperty(cm, "gridMinActivePower")); // 24 hours passed -> update config - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); withValue(sum, Sum.ChannelId.GRID_ACTIVE_POWER, -101); sut.update(sum, cm); activateNextProcessImage(sum); @@ -78,7 +77,7 @@ public void test() throws OpenemsException, Exception { assertEquals(-101, getProperty(cm, "gridMinActivePower")); // Update Channel; not the config - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); withValue(sum, Sum.ChannelId.GRID_ACTIVE_POWER, -101); sut.update(sum, cm); activateNextProcessImage(sum); diff --git a/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java b/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java index 77a25ef888b..7d8f85f074b 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/sum/SumImplTest.java @@ -1,11 +1,14 @@ package io.openems.edge.core.sum; +import static io.openems.edge.common.sum.Sum.ChannelId.CONSUMPTION_MAX_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MAX_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_MIN_ACTIVE_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.PRODUCTION_MAX_ACTIVE_POWER; import static io.openems.edge.meter.api.MeterType.GRID; import org.junit.Test; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -15,13 +18,6 @@ public class SumImplTest { - private static final ChannelAddress GRID_MIN_ACTIVE_POWER = new ChannelAddress("_sum", "GridMinActivePower"); - private static final ChannelAddress GRID_MAX_ACTIVE_POWER = new ChannelAddress("_sum", "GridMaxActivePower"); - private static final ChannelAddress PRODUCTION_MAX_ACTIVE_POWER = new ChannelAddress("_sum", - "ProductionMaxActivePower"); - private static final ChannelAddress CONSUMPTION_MAX_ACTIVE_POWER = new ChannelAddress("_sum", - "ConsumptionMaxActivePower"); - @Test public void test() throws OpenemsException, Exception { var sut = new SumImpl(); diff --git a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/ess/Edge2EdgeEssImplTest.java b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/ess/Edge2EdgeEssImplTest.java index 90317ad9e80..6b200696166 100644 --- a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/ess/Edge2EdgeEssImplTest.java +++ b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/ess/Edge2EdgeEssImplTest.java @@ -10,19 +10,16 @@ public class Edge2EdgeEssImplTest { - private static final String COMPONENT_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new Edge2EdgeEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setRemoteAccessMode(AccessMode.READ_WRITE) // - .setRemoteComponentId(COMPONENT_ID) // + .setRemoteComponentId("ess0") // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/Edge2EdgeEssMeterImplTest.java b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/Edge2EdgeEssMeterImplTest.java index a52aed0a22b..4e0bd762bf9 100644 --- a/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/Edge2EdgeEssMeterImplTest.java +++ b/io.openems.edge.edge2edge/test/io/openems/edge/edge2edge/meter/Edge2EdgeEssMeterImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.edge2edge.meter; +import static io.openems.edge.meter.api.MeterType.PRODUCTION; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class Edge2EdgeEssMeterImplTest { - private static final String COMPONENT_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new Edge2EdgeMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // - .setRemoteComponentId(COMPONENT_ID) // - .setMeterType(MeterType.PRODUCTION) // + .setId("meter0") // + .setModbusId("modbus0") // + .setRemoteComponentId("meter0") // + .setMeterType(PRODUCTION) // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java index bacd9a10260..aa2a426c22c 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java @@ -37,8 +37,6 @@ public class EnergySchedulerImplTest { public static final Clock CLOCK = new TimeLeapClock(Instant.parse("2020-03-04T14:19:00.00Z"), ZoneOffset.UTC); - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { create(CLOCK); @@ -73,7 +71,7 @@ public static EnergySchedulerImpl create(Clock clock) throws Exception { .addReference("schedulables", List.of(ctrl)) // .addReference("sum", sum) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setEnabled(false) // .setLogVerbosity(TRACE) // .build()) // diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java index a2b82ad90a9..b9b321d8366 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java @@ -1,6 +1,7 @@ package io.openems.edge.energy.optimizer; import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static io.openems.edge.common.test.TestUtils.withValue; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; @@ -29,6 +30,8 @@ import static io.openems.edge.energy.optimizer.Utils.toEnergy; import static io.openems.edge.energy.optimizer.Utils.toPower; import static io.openems.edge.energy.optimizer.Utils.updateSchedule; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -36,11 +39,8 @@ import static org.junit.Assert.assertTrue; import java.time.Duration; -import java.time.Instant; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.TreeMap; import java.util.stream.IntStream; @@ -50,7 +50,6 @@ import com.google.common.collect.ImmutableSortedMap; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.test.AbstractDummyOpenemsComponent; @@ -240,7 +239,7 @@ public void testGetEssMinSocEnergy() { @Test public void testHandleScheduleRequest() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-03-04T14:19:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); final var energyScheduler = EnergySchedulerImplTest.create(clock); // Simulate historic data @@ -282,16 +281,16 @@ public void testHandleScheduleRequest() throws Exception { @Test public void testCalculateExecutionLimitSeconds() { - final var clock = new TimeLeapClock(Instant.parse("2022-01-01T00:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); assertEquals(Duration.ofMinutes(14).plusSeconds(30).toSeconds(), calculateExecutionLimitSeconds(clock)); - clock.leap(11, ChronoUnit.MINUTES); + clock.leap(11, MINUTES); assertEquals(Duration.ofMinutes(3).plusSeconds(30).toSeconds(), calculateExecutionLimitSeconds(clock)); - clock.leap(150, ChronoUnit.SECONDS); + clock.leap(150, SECONDS); assertEquals(60, calculateExecutionLimitSeconds(clock)); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); assertEquals(Duration.ofMinutes(15).plusSeconds(59).toSeconds(), calculateExecutionLimitSeconds(clock)); } diff --git a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/EssFeneconBydContainerImplTest.java b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/EssFeneconBydContainerImplTest.java index 841489bb5b7..5d1475d92ab 100644 --- a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/EssFeneconBydContainerImplTest.java +++ b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/EssFeneconBydContainerImplTest.java @@ -9,25 +9,20 @@ public class EssFeneconBydContainerImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS0_ID = "modbus0"; - private static final String MODBUS1_ID = "modbus1"; - private static final String MODBUS2_ID = "modbus2"; - @Test public void test() throws Exception { new ManagedSymmetricEssTest(new EssFeneconBydContainerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS0_ID)) // - .addReference("modbus1", new DummyModbusBridge(MODBUS1_ID)) // - .addReference("modbus2", new DummyModbusBridge(MODBUS2_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // + .addReference("modbus1", new DummyModbusBridge("modbus1")) // + .addReference("modbus2", new DummyModbusBridge("modbus2")) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setReadonly(true) // - .setModbusId0(MODBUS0_ID) // - .setModbusId1(MODBUS1_ID) // - .setModbusId2(MODBUS2_ID) // + .setModbusId0("modbus0") // + .setModbusId1("modbus1") // + .setModbusId2("modbus2") // .build()) // ; } diff --git a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/EssFeneconBydContainerWatchdogControllerImplTest.java b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/EssFeneconBydContainerWatchdogControllerImplTest.java deleted file mode 100644 index 19d242a4257..00000000000 --- a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/EssFeneconBydContainerWatchdogControllerImplTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.openems.edge.ess.byd.container.watchdog; - -import io.openems.edge.bridge.modbus.test.DummyModbusBridge; -import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.common.test.DummyComponentManager; -import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.controller.test.ControllerTest; -import io.openems.edge.ess.byd.container.EssFeneconBydContainerImpl; -import io.openems.edge.ess.test.DummyPower; - -public class EssFeneconBydContainerWatchdogControllerImplTest { - - private static final String CTRL_ID = "ctrl0"; - // private final static ChannelAddress CTRL_WATCHDOG = new - // ChannelAddress(CTRL_ID, "Watchdog"); - - private static final String MODBUS0_ID = "modbus0"; - private static final String MODBUS1_ID = "modbus1"; - private static final String MODBUS2_ID = "modbus2"; - - private static final String ESS_ID = "ess0"; - // private final static ChannelAddress ESS_SET_ACTIVE_POWER_EQUALS = new - // ChannelAddress(ESS_ID, - // "SetActivePowerEquals"); - // private final static ChannelAddress ESS_SET_REACTIVE_POWER_EQUALS = new - // ChannelAddress(ESS_ID, - // "SetReactivePowerEquals"); - - // TODO requires fix by Pooran Chandrashekaraiah - // @Test - protected void test() throws Exception { - var ess = new EssFeneconBydContainerImpl(); - new ComponentTest(ess) // - .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS0_ID)) // - .addReference("modbus1", new DummyModbusBridge(MODBUS1_ID)) // - .addReference("modbus2", new DummyModbusBridge(MODBUS2_ID)) // - .activate(MyEssConfig.create() // - .setId(ESS_ID) // - .setModbusId0(MODBUS0_ID) // - .setModbusId1(MODBUS1_ID) // - .setModbusId2(MODBUS2_ID) // - .build()); - - new ControllerTest(new EssFeneconBydContainerWatchdogControllerImpl()) // - .addReference("componentManager", new DummyComponentManager()) // - .addReference("cm", new DummyConfigurationAdmin()) // - .addComponent(ess) // - .activate(MyWatchdogConfig.create() // - .setId(CTRL_ID) // - .setEssId(ESS_ID) // - .build()) - // .next(new TestCase()// - // .output(ESS_SET_ACTIVE_POWER_EQUALS, 0)// - // .output(ESS_SET_REACTIVE_POWER_EQUALS, 0))// - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0)// - // .output(ESS_SET_ACTIVE_POWER_EQUALS, 0) // - // .output(ESS_SET_REACTIVE_POWER_EQUALS, 0))// - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0).// - // output(CTRL_IS_TIMEOUT, 1))// - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0)// - // .output(CTRL_IS_TIMEOUT, 1))// - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0)// - // .output(CTRL_IS_TIMEOUT, 1)) - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 1)// - // .output(CTRL_IS_TIMEOUT, 0))// - // .next(new TestCase() // - // .output(CTRL_IS_TIMEOUT, 0)) - // .next(new TestCase() // - // .input(CTRL_WATCHDOG, 0)// - // .output(CTRL_IS_TIMEOUT, 1))// - ; - } - -} diff --git a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyEssConfig.java b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyEssConfig.java deleted file mode 100644 index fea2f40e694..00000000000 --- a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyEssConfig.java +++ /dev/null @@ -1,101 +0,0 @@ -package io.openems.edge.ess.byd.container.watchdog; - -import io.openems.common.test.AbstractComponentConfig; -import io.openems.common.utils.ConfigUtils; -import io.openems.edge.ess.byd.container.Config; - -@SuppressWarnings("all") -public class MyEssConfig extends AbstractComponentConfig implements Config { - - protected static class Builder { - private String id; - private boolean readonly; - private String modbusId0; - private String modbusId1; - private String modbusId2; - - private Builder() { - } - - public Builder setId(String id) { - this.id = id; - return this; - } - - public Builder setModbusId0(String modbusId0) { - this.modbusId0 = modbusId0; - return this; - } - - public Builder setModbusId1(String modbusId1) { - this.modbusId1 = modbusId1; - return this; - } - - public Builder setModbusId2(String modbusId2) { - this.modbusId2 = modbusId2; - return this; - } - - public Builder setReadonly(boolean readonly) { - this.readonly = readonly; - return this; - } - - public MyEssConfig build() { - return new MyEssConfig(this); - } - } - - /** - * Create a Config builder. - * - * @return a {@link Builder} - */ - public static Builder create() { - return new Builder(); - } - - private final Builder builder; - - private MyEssConfig(Builder builder) { - super(Config.class, builder.id); - this.builder = builder; - } - - @Override - public boolean readonly() { - return this.builder.readonly; - } - - @Override - public String modbus_id0() { - return this.builder.modbusId0; - } - - @Override - public String modbus_id1() { - return this.builder.modbusId1; - } - - @Override - public String modbus_id2() { - return this.builder.modbusId2; - } - - @Override - public String Modbus_target() { - return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id0()); - } - - @Override - public String modbus1_target() { - return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id1()); - } - - @Override - public String modbus2_target() { - return ConfigUtils.generateReferenceTargetFilter(this.id(), this.modbus_id2()); - } - -} diff --git a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyWatchdogConfig.java b/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyWatchdogConfig.java deleted file mode 100644 index 90faa50d2fc..00000000000 --- a/io.openems.edge.ess.byd.container/test/io/openems/edge/ess/byd/container/watchdog/MyWatchdogConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.openems.edge.ess.byd.container.watchdog; - -import io.openems.common.test.AbstractComponentConfig; - -@SuppressWarnings("all") -public class MyWatchdogConfig extends AbstractComponentConfig implements Config { - - protected static class Builder { - private String id; - private String essId; - - private Builder() { - } - - public Builder setId(String id) { - this.id = id; - return this; - } - - public Builder setEssId(String essId) { - this.essId = essId; - return this; - } - - public MyWatchdogConfig build() { - return new MyWatchdogConfig(this); - } - } - - /** - * Create a Config builder. - * - * @return a {@link Builder} - */ - public static Builder create() { - return new Builder(); - } - - private final Builder builder; - - private MyWatchdogConfig(Builder builder) { - super(Config.class, builder.id); - this.builder = builder; - } - - @Override - public String ess_id() { - return this.builder.essId; - } - -} \ No newline at end of file diff --git a/io.openems.edge.ess.cluster/test/io/openems/edge/ess/cluster/EssClusterImplTest.java b/io.openems.edge.ess.cluster/test/io/openems/edge/ess/cluster/EssClusterImplTest.java index bc0f208f1af..5a2e9ddf4ab 100644 --- a/io.openems.edge.ess.cluster/test/io/openems/edge/ess/cluster/EssClusterImplTest.java +++ b/io.openems.edge.ess.cluster/test/io/openems/edge/ess/cluster/EssClusterImplTest.java @@ -1,8 +1,17 @@ package io.openems.edge.ess.cluster; +import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP; +import static io.openems.edge.ess.api.AsymmetricEss.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_CHARGE_POWER; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.ACTIVE_CHARGE_ENERGY; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.ACTIVE_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.GRID_MODE; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.REACTIVE_POWER; +import static io.openems.edge.ess.api.SymmetricEss.ChannelId.SOC; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.startstop.StartStop; import io.openems.edge.common.startstop.StartStopConfig; import io.openems.edge.common.sum.GridMode; @@ -15,76 +24,39 @@ public class EssClusterImplTest { - private static final String CLUSTER_ID = "ess0"; - private static final String ESS1_ID = "ess1"; - private static final String ESS2_ID = "ess2"; - private static final String ESS3_ID = "ess3"; - private static final ChannelAddress CLUSTER_GRID_MODE = new ChannelAddress(CLUSTER_ID, "GridMode"); - private static final ChannelAddress ESS1_GRID_MODE = new ChannelAddress(ESS1_ID, "GridMode"); - private static final ChannelAddress ESS2_GRID_MODE = new ChannelAddress(ESS2_ID, "GridMode"); - private static final ChannelAddress ESS3_GRID_MODE = new ChannelAddress(ESS3_ID, "GridMode"); - private static final ChannelAddress CLUSTER_SOC = new ChannelAddress(CLUSTER_ID, "Soc"); - private static final ChannelAddress ESS1_SOC = new ChannelAddress(ESS1_ID, "Soc"); - private static final ChannelAddress ESS2_SOC = new ChannelAddress(ESS2_ID, "Soc"); - private static final ChannelAddress CLUSTER_ACTIVE_POWER = new ChannelAddress(CLUSTER_ID, "ActivePower"); - private static final ChannelAddress ESS1_ACTIVE_POWER = new ChannelAddress(ESS1_ID, "ActivePower"); - private static final ChannelAddress ESS2_ACTIVE_POWER = new ChannelAddress(ESS2_ID, "ActivePower"); - private static final ChannelAddress CLUSTER_REACTIVE_POWER = new ChannelAddress(CLUSTER_ID, "ReactivePower"); - private static final ChannelAddress ESS1_REACTIVE_POWER = new ChannelAddress(ESS1_ID, "ReactivePower"); - private static final ChannelAddress ESS2_REACTIVE_POWER = new ChannelAddress(ESS2_ID, "ReactivePower"); - private static final ChannelAddress CLUSTER_ACTIVE_POWER_L1 = new ChannelAddress(CLUSTER_ID, "ActivePowerL1"); - private static final ChannelAddress ESS2_ACTIVE_POWER_L1 = new ChannelAddress(ESS2_ID, "ActivePowerL1"); - private static final ChannelAddress CLUSTER_ACTIVE_CHARGE_ENERGY = new ChannelAddress(CLUSTER_ID, - "ActiveChargeEnergy"); - private static final ChannelAddress ESS1_ACTIVE_CHARGE_ENERGY = new ChannelAddress(ESS1_ID, "ActiveChargeEnergy"); - private static final ChannelAddress ESS2_ACTIVE_CHARGE_ENERGY = new ChannelAddress(ESS2_ID, "ActiveChargeEnergy"); - private static final ChannelAddress CLUSTER_ALLOWED_CHARGE_POWER = new ChannelAddress(CLUSTER_ID, - "AllowedChargePower"); - private static final ChannelAddress ESS1_ALLOWED_CHARGE_POWER = new ChannelAddress(ESS1_ID, "AllowedChargePower"); - private static final ChannelAddress ESS2_ALLOWED_CHARGE_POWER = new ChannelAddress(ESS2_ID, "AllowedChargePower"); - private static final ChannelAddress CLUSTER_ALLOWED_DISCHARGE_POWER = new ChannelAddress(CLUSTER_ID, - "AllowedDischargePower"); - private static final ChannelAddress ESS1_ALLOWED_DISCHARGE_POWER = new ChannelAddress(ESS1_ID, - "AllowedDischargePower"); - private static final ChannelAddress ESS2_ALLOWED_DISCHARGE_POWER = new ChannelAddress(ESS2_ID, - "AllowedDischargePower"); - private static final ChannelAddress CLUSTER_START_STOP = new ChannelAddress(CLUSTER_ID, "StartStop"); - private static final ChannelAddress ESS1_START_STOP = new ChannelAddress(ESS1_ID, "StartStop"); - private static final ChannelAddress ESS2_START_STOP = new ChannelAddress(ESS2_ID, "StartStop"); - @Test public void testCluster() throws Exception { new ComponentTest(new EssClusterImpl()) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS1_ID)) // - .addReference("addEss", new DummyManagedAsymmetricEss(ESS2_ID)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess1")) // + .addReference("addEss", new DummyManagedAsymmetricEss("ess2")) // .activate(MyConfig.create() // - .setId(CLUSTER_ID) // - .setEssIds(ESS1_ID, ESS2_ID) // + .setId("ess0") // + .setEssIds("ess1", "ess2") // .setStartStop(StartStopConfig.START) // .build()) .next(new TestCase() // - .input(ESS1_GRID_MODE, GridMode.ON_GRID) // - .input(ESS2_GRID_MODE, GridMode.ON_GRID) // - .output(CLUSTER_GRID_MODE, GridMode.ON_GRID) // - .input(ESS1_ACTIVE_POWER, 1234) // - .input(ESS2_ACTIVE_POWER, 9876) // - .output(CLUSTER_ACTIVE_POWER, 11110) // - .input(ESS1_REACTIVE_POWER, 1111) // - .input(ESS2_REACTIVE_POWER, 2222) // - .output(CLUSTER_REACTIVE_POWER, 3333) // - .input(ESS1_ACTIVE_CHARGE_ENERGY, 1) // - .input(ESS2_ACTIVE_CHARGE_ENERGY, 2) // - .output(CLUSTER_ACTIVE_CHARGE_ENERGY, 3L) // - .input(ESS2_ACTIVE_POWER_L1, 1111) // - .output(CLUSTER_ACTIVE_POWER_L1, 1234 / 3 + 1111) // - .input(ESS1_ALLOWED_CHARGE_POWER, 11) // - .input(ESS2_ALLOWED_CHARGE_POWER, 22) // - .output(CLUSTER_ALLOWED_CHARGE_POWER, 33) // - .input(ESS1_ALLOWED_DISCHARGE_POWER, 10) // - .input(ESS2_ALLOWED_DISCHARGE_POWER, 20) // - .output(CLUSTER_ALLOWED_DISCHARGE_POWER, 30) // + .input("ess1", GRID_MODE, GridMode.ON_GRID) // + .input("ess2", GRID_MODE, GridMode.ON_GRID) // + .output(GRID_MODE, GridMode.ON_GRID) // + .input("ess1", ACTIVE_POWER, 1234) // + .input("ess2", ACTIVE_POWER, 9876) // + .output(ACTIVE_POWER, 11110) // + .input("ess1", REACTIVE_POWER, 1111) // + .input("ess2", REACTIVE_POWER, 2222) // + .output(REACTIVE_POWER, 3333) // + .input("ess1", ACTIVE_CHARGE_ENERGY, 1) // + .input("ess2", ACTIVE_CHARGE_ENERGY, 2) // + .output(ACTIVE_CHARGE_ENERGY, 3L) // + .input("ess2", ACTIVE_POWER_L1, 1111) // + .output(ACTIVE_POWER_L1, 1234 / 3 + 1111) // + .input("ess1", ALLOWED_CHARGE_POWER, 11) // + .input("ess2", ALLOWED_CHARGE_POWER, 22) // + .output(ALLOWED_CHARGE_POWER, 33) // + .input("ess1", ALLOWED_DISCHARGE_POWER, 10) // + .input("ess2", ALLOWED_DISCHARGE_POWER, 20) // + .output(ALLOWED_DISCHARGE_POWER, 30) // ); } @@ -93,31 +65,31 @@ public void testGridMode() throws Exception { new ComponentTest(new EssClusterImpl()) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS1_ID)) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS2_ID)) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS3_ID)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess1")) // + .addReference("addEss", new DummyManagedSymmetricEss("ess2")) // + .addReference("addEss", new DummyManagedSymmetricEss("ess3")) // .activate(MyConfig.create() // - .setId(CLUSTER_ID) // - .setEssIds(ESS1_ID, ESS2_ID, ESS3_ID) // + .setId("ess0") // + .setEssIds("ess1", "ess2", "ess3") // .setStartStop(StartStopConfig.START) // .build()) .next(new TestCase() // - .input(ESS1_GRID_MODE, GridMode.ON_GRID) // - .input(ESS2_GRID_MODE, GridMode.ON_GRID) // - .input(ESS3_GRID_MODE, GridMode.ON_GRID) // - .output(CLUSTER_GRID_MODE, GridMode.ON_GRID) // + .input("ess1", GRID_MODE, GridMode.ON_GRID) // + .input("ess2", GRID_MODE, GridMode.ON_GRID) // + .input("ess3", GRID_MODE, GridMode.ON_GRID) // + .output(GRID_MODE, GridMode.ON_GRID) // ) // .next(new TestCase() // - .input(ESS1_GRID_MODE, GridMode.OFF_GRID) // - .input(ESS2_GRID_MODE, GridMode.OFF_GRID) // - .input(ESS3_GRID_MODE, GridMode.OFF_GRID) // - .output(CLUSTER_GRID_MODE, GridMode.OFF_GRID) // + .input("ess1", GRID_MODE, GridMode.OFF_GRID) // + .input("ess2", GRID_MODE, GridMode.OFF_GRID) // + .input("ess3", GRID_MODE, GridMode.OFF_GRID) // + .output(GRID_MODE, GridMode.OFF_GRID) // ) // .next(new TestCase() // - .input(ESS1_GRID_MODE, GridMode.OFF_GRID) // - .input(ESS2_GRID_MODE, GridMode.OFF_GRID) // - .input(ESS3_GRID_MODE, GridMode.UNDEFINED) // - .output(CLUSTER_GRID_MODE, GridMode.UNDEFINED) // + .input("ess1", GRID_MODE, GridMode.OFF_GRID) // + .input("ess2", GRID_MODE, GridMode.OFF_GRID) // + .input("ess3", GRID_MODE, GridMode.UNDEFINED) // + .output(GRID_MODE, GridMode.UNDEFINED) // ) // ; } @@ -127,25 +99,25 @@ public void testSoc() throws Exception { new ComponentTest(new EssClusterImpl()) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS1_ID).withCapacity(50000)) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS2_ID).withCapacity(3000)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess1").withCapacity(50000)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess2").withCapacity(3000)) // .activate(MyConfig.create() // - .setId(CLUSTER_ID) // - .setEssIds(ESS1_ID, ESS2_ID) // + .setId("ess0") // + .setEssIds("ess1", "ess2") // .setStartStop(StartStopConfig.START) // .build()) .next(new TestCase() // - .input(ESS1_SOC, 20) // - .input(ESS2_SOC, 90) // - .output(CLUSTER_SOC, 24) // + .input("ess1", SOC, 20) // + .input("ess2", SOC, 90) // + .output(SOC, 24) // ) // .next(new TestCase() // - .input(ESS1_SOC, 21) // - .output(CLUSTER_SOC, 25) // + .input("ess1", SOC, 21) // + .output(SOC, 25) // ) // .next(new TestCase() // - .input(ESS1_SOC, 100) // - .output(CLUSTER_SOC, 99) // + .input("ess1", SOC, 100) // + .output(SOC, 99) // ) // ; } @@ -155,29 +127,29 @@ public void testStartStop() throws Exception { new ComponentTest(new EssClusterImpl()) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS1_ID)) // - .addReference("addEss", new DummyManagedSymmetricEss(ESS2_ID)) // + .addReference("addEss", new DummyManagedSymmetricEss("ess1")) // + .addReference("addEss", new DummyManagedSymmetricEss("ess2")) // .activate(MyConfig.create() // - .setId(CLUSTER_ID) // - .setEssIds(ESS1_ID, ESS2_ID) // + .setId("ess0") // + .setEssIds("ess1", "ess2") // .setStartStop(StartStopConfig.START) // .build()) .next(new TestCase() // - .input(ESS1_START_STOP, StartStop.UNDEFINED) // - .input(ESS2_START_STOP, StartStop.STOP) // - .output(CLUSTER_START_STOP, StartStop.UNDEFINED)) // + .input("ess1", START_STOP, StartStop.UNDEFINED) // + .input("ess2", START_STOP, StartStop.STOP) // + .output(START_STOP, StartStop.UNDEFINED)) // .next(new TestCase() // - .input(ESS1_START_STOP, StartStop.STOP) // - .input(ESS2_START_STOP, StartStop.STOP) // - .output(CLUSTER_START_STOP, StartStop.STOP)) // + .input("ess1", START_STOP, StartStop.STOP) // + .input("ess2", START_STOP, StartStop.STOP) // + .output(START_STOP, StartStop.STOP)) // .next(new TestCase() // - .input(ESS1_START_STOP, StartStop.START) // - .input(ESS2_START_STOP, StartStop.STOP) // - .output(CLUSTER_START_STOP, StartStop.UNDEFINED)) // + .input("ess1", START_STOP, StartStop.START) // + .input("ess2", START_STOP, StartStop.STOP) // + .output(START_STOP, StartStop.UNDEFINED)) // .next(new TestCase() // - .input(ESS1_START_STOP, StartStop.START) // - .input(ESS2_START_STOP, StartStop.START) // - .output(CLUSTER_START_STOP, StartStop.START)) // + .input("ess1", START_STOP, StartStop.START) // + .input("ess2", START_STOP, StartStop.START) // + .output(START_STOP, StartStop.START)) // ; } diff --git a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest.java b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest.java index 75baf32bbc0..0e5e0282eaf 100644 --- a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest.java +++ b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest.java @@ -1,5 +1,11 @@ package io.openems.edge.ess.core.power; +import static io.openems.edge.ess.power.api.Pwr.ACTIVE; +import static io.openems.edge.ess.power.api.Pwr.REACTIVE; +import static io.openems.edge.ess.power.api.Relationship.EQUALS; +import static io.openems.edge.ess.power.api.Relationship.LESS_OR_EQUALS; +import static io.openems.edge.ess.power.api.SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL; +import static io.openems.edge.ess.power.api.SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET; import static org.junit.Assert.assertEquals; import java.util.concurrent.atomic.AtomicInteger; @@ -12,9 +18,6 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.power.api.Phase; -import io.openems.edge.ess.power.api.Pwr; -import io.openems.edge.ess.power.api.Relationship; -import io.openems.edge.ess.power.api.SolverStrategy; import io.openems.edge.ess.test.DummyManagedAsymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyMetaEss; @@ -50,15 +53,15 @@ public void testSymmetricEss() throws Exception { .addReference("cm", cm) // .addReference("addEss", ess0) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // .build()); // expect("#10", ess0, 5000, 3000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 5000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 3000); + ess0.addPowerConstraint("", Phase.ALL, ACTIVE, EQUALS, 5000); + ess0.addPowerConstraint("", Phase.ALL, REACTIVE, EQUALS, 3000); componentTest.next(new TestCase()); } @@ -79,19 +82,19 @@ public void testAsymmetricEss() throws Exception { .addReference("cm", cm) // .addReference("addEss", ess0) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(false) // .setDebugMode(false) // .setEnablePid(false) // .build()); // expect("#1", ess0, 5000, 3333, 5000, 3333, 5000, 3334); - ess0.addPowerConstraint("", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 15000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 10000); - ess0.addPowerConstraint("", Phase.L1, Pwr.ACTIVE, Relationship.EQUALS, 5000); - ess0.addPowerConstraint("", Phase.L1, Pwr.REACTIVE, Relationship.EQUALS, 3333); - ess0.addPowerConstraint("", Phase.L2, Pwr.ACTIVE, Relationship.EQUALS, 5000); - ess0.addPowerConstraint("", Phase.L2, Pwr.REACTIVE, Relationship.EQUALS, 3333); + ess0.addPowerConstraint("", Phase.ALL, ACTIVE, EQUALS, 15000); + ess0.addPowerConstraint("", Phase.ALL, REACTIVE, EQUALS, 10000); + ess0.addPowerConstraint("", Phase.L1, ACTIVE, EQUALS, 5000); + ess0.addPowerConstraint("", Phase.L1, REACTIVE, EQUALS, 3333); + ess0.addPowerConstraint("", Phase.L2, ACTIVE, EQUALS, 5000); + ess0.addPowerConstraint("", Phase.L2, REACTIVE, EQUALS, 3333); componentTest.next(new TestCase()); } @@ -112,15 +115,15 @@ public void testAsymmetricEssAllEqual() throws Exception { .addReference("cm", cm) // .addReference("addEss", ess0) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(false) // .setDebugMode(false) // .setEnablePid(false) // .build()); // expect("#1", ess0, 5000, 3000, 5000, 3000, 5000, 3000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 15000); - ess0.addPowerConstraint("", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 9000); + ess0.addPowerConstraint("", Phase.ALL, ACTIVE, EQUALS, 15000); + ess0.addPowerConstraint("", Phase.ALL, REACTIVE, EQUALS, 9000); componentTest.next(new TestCase()); } @@ -151,7 +154,7 @@ public void testCluster() throws Exception { .addReference("addEss", ess1) // .addReference("addEss", ess2) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -160,72 +163,72 @@ public void testCluster() throws Exception { // #1 expect("#1", ess1, -5000, -3000); expect("#1", ess2, -0, 0); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#1", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#1", Phase.ALL, REACTIVE, EQUALS, -3000); ess1.withSoc(80); // this is for test #2 componentTest.next(new TestCase("#1")); // #2 expect("#2", ess1, -4697, -2818); expect("#2", ess2, -302, -181); - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#2", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#2", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#2")); // #3 expect("#3", ess1, -4429, -2657); expect("#3", ess2, -570, -342); - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#3", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#3", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#3")); // #4 expect("#4", ess1, -4190, -2514); expect("#4", ess2, -809, -485); - ess0.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#4", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#4")); // #5 expect("#5", ess1, -3976, -2385); expect("#5", ess2, -1023, -614); - ess0.addPowerConstraint("#5", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#5", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#5", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#5", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#5")); // #6 expect("#6", ess1, -3782, -2269); expect("#6", ess2, -1217, -730); - ess0.addPowerConstraint("#6", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#6", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#6", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#6", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#6")); // #7 expect("#7", ess1, -3606, -2164); expect("#7", ess2, -1393, -835); - ess0.addPowerConstraint("#7", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#7", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#7", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#7", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#7")); // #8 expect("#8", ess1, -3446, -2067); expect("#8", ess2, -1553, -932); - ess0.addPowerConstraint("#8", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#8", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#8", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#8", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#8")); // #9 expect("#9", ess1, -3300, -1980); expect("#9", ess2, -1699, -1019); - ess0.addPowerConstraint("#9", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#9", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#9", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#9", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#9")); // #10 expect("#10", ess1, -3165, -1899); expect("#10", ess2, -1834, -1100); - ess0.addPowerConstraint("#10", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#10", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#10", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#10", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#10")); ess1.withSymmetricApplyPowerCallback(null); @@ -244,8 +247,8 @@ public void testCluster() throws Exception { // #20 expect("#20", ess1, -0, 0); expect("#20", ess2, -5000, -3000); - ess0.addPowerConstraint("#20", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); - ess0.addPowerConstraint("#20", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -3000); + ess0.addPowerConstraint("#20", Phase.ALL, ACTIVE, EQUALS, -5000); + ess0.addPowerConstraint("#20", Phase.ALL, REACTIVE, EQUALS, -3000); componentTest.next(new TestCase("#20")); } @@ -304,7 +307,7 @@ public void testStrSctr() throws Exception { .addReference("addEss", ess5) // .addReference("addEss", ess6) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -317,8 +320,8 @@ public void testStrSctr() throws Exception { expect("#1", ess4, 0, 0); expect("#1", ess5, 10062, 0); // largest SoC expect("#1", ess6, 9986, 0); // second largest SoC - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 30000); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("#1", Phase.ALL, ACTIVE, EQUALS, 30000); + ess0.addPowerConstraint("#1", Phase.ALL, REACTIVE, EQUALS, 0); componentTest.next(new TestCase("#1")); // #2 @@ -328,8 +331,8 @@ public void testStrSctr() throws Exception { expect("#2", ess4, 0, 0); expect("#2", ess5, 8435, 5061); // largest SoC expect("#2", ess6, 8310, 4986); // second largest SoC - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 25000); - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 15000); + ess0.addPowerConstraint("#2", Phase.ALL, ACTIVE, EQUALS, 25000); + ess0.addPowerConstraint("#2", Phase.ALL, REACTIVE, EQUALS, 15000); componentTest.next(new TestCase("#2")); // #3 @@ -339,8 +342,8 @@ public void testStrSctr() throws Exception { expect("#3", ess4, 0, 0); expect("#3", ess5, 1723, 689); // largest SoC expect("#3", ess6, 1644, 658); // second largest SoC - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 5000); - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 2000); + ess0.addPowerConstraint("#3", Phase.ALL, ACTIVE, EQUALS, 5000); + ess0.addPowerConstraint("#3", Phase.ALL, REACTIVE, EQUALS, 2000); componentTest.next(new TestCase("#3")); // #4 not strictly defined force charge @@ -350,18 +353,18 @@ public void testStrSctr() throws Exception { expect("#4", ess4, -2000, -1000); expect("#4", ess5, -2000, -1000); // largest SoC expect("#4", ess6, -2000, -1000); // second largest SoC - ess1.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess2.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess3.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess4.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess5.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess6.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.LESS_OR_EQUALS, -2000); - ess1.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess2.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess3.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess4.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess5.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); - ess6.addPowerConstraint("#4", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, -1000); + ess1.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess2.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess3.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess4.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess5.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess6.addPowerConstraint("#4", Phase.ALL, ACTIVE, LESS_OR_EQUALS, -2000); + ess1.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess2.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess3.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess4.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess5.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); + ess6.addPowerConstraint("#4", Phase.ALL, REACTIVE, EQUALS, -1000); componentTest.next(new TestCase("#4")); } @@ -390,7 +393,7 @@ public void testCommercial40Cluster() throws Exception { .addReference("addEss", ess1) // .addReference("addEss", ess2) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // + .setStrategy(OPTIMIZE_BY_MOVING_TOWARDS_TARGET) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -399,65 +402,65 @@ public void testCommercial40Cluster() throws Exception { // #1 ess1.withAllowedChargePower(-500).withAllowedDischargePower(500); ess2.withAllowedChargePower(-500).withAllowedDischargePower(500); - assertEquals(1000, ess0.getPower().getMaxPower(ess0, Phase.ALL, Pwr.ACTIVE)); - assertEquals(-1000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(1000, ess0.getPower().getMaxPower(ess0, Phase.ALL, ACTIVE)); + assertEquals(-1000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#1", ess1, -500, 0); expect("#1", ess2, -500, 0); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -1000); + ess0.addPowerConstraint("#1", Phase.ALL, ACTIVE, EQUALS, -1000); componentTest.next(new TestCase("#1")); // #2 ess1.withAllowedChargePower(-1000); ess2.withAllowedChargePower(-1000); - assertEquals(-2000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-2000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#2", ess1, -1000, 0); expect("#2", ess2, -1000, 0); - ess0.addPowerConstraint("#2", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -2000); + ess0.addPowerConstraint("#2", Phase.ALL, ACTIVE, EQUALS, -2000); componentTest.next(new TestCase("#2")); // #3 ess1.withAllowedChargePower(-2000); ess2.withAllowedChargePower(-2000); - assertEquals(-4000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-4000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#3", ess1, -2000, 0); expect("#3", ess2, -2000, 0); - ess0.addPowerConstraint("#3", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -4000); + ess0.addPowerConstraint("#3", Phase.ALL, ACTIVE, EQUALS, -4000); componentTest.next(new TestCase("#3")); // #4 ess1.withAllowedChargePower(-3000); ess2.withAllowedChargePower(-3000); - assertEquals(-6000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-6000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#4", ess1, -2700, 0); // move towards ess1 because it is empty expect("#4", ess2, -2300, 0); - ess0.addPowerConstraint("#4", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); + ess0.addPowerConstraint("#4", Phase.ALL, ACTIVE, EQUALS, -5000); componentTest.next(new TestCase("#4")); // #5 ess1.withAllowedChargePower(-3500); ess2.withAllowedChargePower(-3500); - assertEquals(-7000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-7000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#5", ess1, -2900, 0); expect("#5", ess2, -2100, 0); - ess0.addPowerConstraint("#5", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); + ess0.addPowerConstraint("#5", Phase.ALL, ACTIVE, EQUALS, -5000); componentTest.next(new TestCase("#5")); // #6 ess1.withAllowedChargePower(-4000); ess2.withAllowedChargePower(-4000); - assertEquals(-8000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-8000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#6", ess1, -3100, 0); // move towards ess1 because it is empty expect("#6", ess2, -1900, 0); - ess0.addPowerConstraint("#6", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); + ess0.addPowerConstraint("#6", Phase.ALL, ACTIVE, EQUALS, -5000); componentTest.next(new TestCase("#6")); // #7 ess1.withAllowedChargePower(-6000); ess2.withAllowedChargePower(-6000); - assertEquals(-12000, ess0.getPower().getMinPower(ess0, Phase.ALL, Pwr.ACTIVE)); + assertEquals(-12000, ess0.getPower().getMinPower(ess0, Phase.ALL, ACTIVE)); expect("#7", ess1, -3300, 0); // move towards ess1 because it is empty expect("#7", ess2, -1700, 0); - ess0.addPowerConstraint("#7", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -5000); + ess0.addPowerConstraint("#7", Phase.ALL, ACTIVE, EQUALS, -5000); componentTest.next(new TestCase("#7")); } @@ -508,7 +511,7 @@ public void testMultilayerCluster() throws Exception { .addReference("addEss", ess21) // .addReference("addEss", ess22) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -519,8 +522,8 @@ public void testMultilayerCluster() throws Exception { expect("#1", ess12, 1500, 1500); expect("#1", ess21, 1500, 1500); expect("#1", ess22, 1500, 1500); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 6000); - ess0.addPowerConstraint("#1", Phase.ALL, Pwr.REACTIVE, Relationship.EQUALS, 6000); + ess0.addPowerConstraint("#1", Phase.ALL, ACTIVE, EQUALS, 6000); + ess0.addPowerConstraint("#1", Phase.ALL, REACTIVE, EQUALS, 6000); componentTest.next(new TestCase("#1")); } @@ -571,7 +574,7 @@ public void testNearEqualDistribution() throws Exception { .addReference("addEss", ess3) // .addReference("addEss", ess4) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -583,7 +586,7 @@ public void testNearEqualDistribution() throws Exception { expect("#1.3", ess3, 2500, 0); expect("#1.4", ess4, 2500, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 10000); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, 10000); ess0.setActivePowerEquals(10000); componentTest.next(new TestCase("#1")); @@ -593,7 +596,7 @@ public void testNearEqualDistribution() throws Exception { expect("#2.3", ess3, -2500, 0); expect("#2.4", ess4, -2500, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -10000); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, -10000); ess0.setActivePowerEquals(10000); componentTest.next(new TestCase("#1")); @@ -606,7 +609,7 @@ public void testNearEqualDistribution() throws Exception { expect("#3.3", ess3, 2701, 0); expect("#3.4", ess4, 1897, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 10000); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, 10000); componentTest.next(new TestCase("#3")); // #4 charging with lower allowed ccharge power @@ -618,7 +621,7 @@ public void testNearEqualDistribution() throws Exception { expect("#4.3", ess3, -9900, 0); expect("#4.4", ess4, -1881, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -10000); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, -10000); componentTest.next(new TestCase("#4")); // #5 keeping zero @@ -627,7 +630,7 @@ public void testNearEqualDistribution() throws Exception { expect("#5.3", ess3, 0, 0); expect("#5.4", ess4, 0, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, 0); componentTest.next(new TestCase("#5")); ess4.withAllowedChargePower(1000); @@ -638,7 +641,7 @@ public void testNearEqualDistribution() throws Exception { expect("#5.3", ess3, 0, 0); expect("#5.4", ess4, 0, 0); - ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("SetActivePowerEquals", Phase.ALL, ACTIVE, EQUALS, 0); componentTest.next(new TestCase("#5")); } diff --git a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest2.java b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest2.java index 7060c6e7393..dd36004444b 100644 --- a/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest2.java +++ b/io.openems.edge.ess.core/test/io/openems/edge/ess/core/power/PowerComponentTest2.java @@ -1,5 +1,8 @@ package io.openems.edge.ess.core.power; +import static io.openems.edge.ess.power.api.Pwr.ACTIVE; +import static io.openems.edge.ess.power.api.Relationship.EQUALS; +import static io.openems.edge.ess.power.api.SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL; import static org.junit.Assert.assertEquals; import java.util.concurrent.atomic.AtomicInteger; @@ -12,9 +15,6 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.power.api.Phase; -import io.openems.edge.ess.power.api.Pwr; -import io.openems.edge.ess.power.api.Relationship; -import io.openems.edge.ess.power.api.SolverStrategy; import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.ess.test.DummyMetaEss; @@ -55,7 +55,7 @@ public void testOnlyOneEssDistribution() throws Exception { .addReference("addEss", ess1) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -120,7 +120,7 @@ public void testNearEqualDistribution() throws Exception { .addReference("addEss", ess3) // .addReference("addEss", ess4) // .activate(MyConfig.create() // - .setStrategy(SolverStrategy.OPTIMIZE_BY_KEEPING_ALL_EQUAL) // + .setStrategy(OPTIMIZE_BY_KEEPING_ALL_EQUAL) // .setSymmetricMode(true) // .setDebugMode(false) // .setEnablePid(false) // @@ -132,7 +132,7 @@ public void testNearEqualDistribution() throws Exception { expect("#1.3", ess3, 2500, 0); expect("#1.4", ess4, 2500, 0); - ess0.addPowerConstraint("SetActivePowerEquals1", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 10000); + ess0.addPowerConstraint("SetActivePowerEquals1", Phase.ALL, ACTIVE, EQUALS, 10000); ess0.setActivePowerEquals(10000); componentTest.next(new TestCase("#1")); @@ -142,7 +142,7 @@ public void testNearEqualDistribution() throws Exception { expect("#2.3", ess3, -2500, 0); expect("#2.4", ess4, -2500, 0); - ess0.addPowerConstraint("SetActivePowerEquals2", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -10000); + ess0.addPowerConstraint("SetActivePowerEquals2", Phase.ALL, ACTIVE, EQUALS, -10000); ess0.setActivePowerEquals(10000); componentTest.next(new TestCase("#1")); @@ -155,7 +155,7 @@ public void testNearEqualDistribution() throws Exception { expect("#3.3", ess3, 2701, 0); expect("#3.4", ess4, 1897, 0); - ess0.addPowerConstraint("SetActivePowerEquals3", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 10000); + ess0.addPowerConstraint("SetActivePowerEquals3", Phase.ALL, ACTIVE, EQUALS, 10000); componentTest.next(new TestCase("#3")); // #4 charging with lower allowed charge power @@ -167,7 +167,7 @@ public void testNearEqualDistribution() throws Exception { expect("#4.3", ess3, -2703, 0); expect("#4.4", ess4, -1896, 0); - ess0.addPowerConstraint("SetActivePowerEquals4", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, -10000); + ess0.addPowerConstraint("SetActivePowerEquals4", Phase.ALL, ACTIVE, EQUALS, -10000); componentTest.next(new TestCase("#4")); // #5 keeping zero @@ -176,7 +176,7 @@ public void testNearEqualDistribution() throws Exception { expect("#5.3", ess3, 0, 0); expect("#5.4", ess4, 0, 0); - ess0.addPowerConstraint("SetActivePowerEquals5", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("SetActivePowerEquals5", Phase.ALL, ACTIVE, EQUALS, 0); componentTest.next(new TestCase("#5")); ess4.withAllowedChargePower(1000); @@ -187,7 +187,7 @@ public void testNearEqualDistribution() throws Exception { expect("#5.3", ess3, 0, 0); expect("#5.4", ess4, 0, 0); - ess0.addPowerConstraint("ctrl0", Phase.ALL, Pwr.ACTIVE, Relationship.EQUALS, 0); + ess0.addPowerConstraint("ctrl0", Phase.ALL, ACTIVE, EQUALS, 0); componentTest.next(new TestCase("#5")); } diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/EssFeneconCommercial40ImplTest.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/EssFeneconCommercial40ImplTest.java index fdd05793704..f1c7d45a6ff 100644 --- a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/EssFeneconCommercial40ImplTest.java +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/EssFeneconCommercial40ImplTest.java @@ -8,17 +8,14 @@ public class EssFeneconCommercial40ImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ManagedSymmetricEssTest(new EssFeneconCommercial40Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setSurplusFeedInSocLimit(90) // .setSurplusFeedInAllowedChargePowerLimit(-8000) // .setSurplusFeedInIncreasePowerFactor(1.1) // diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java index 317c043debe..91597df6669 100644 --- a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv1ImplTest.java @@ -10,19 +10,15 @@ public class EssFeneconCommercial40Pv1ImplTest { - private static final String CHARGER_ID = "charger0"; - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { var ess = new EssFeneconCommercial40Impl(); new ManagedSymmetricEssTest(ess) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.ess.fenecon.commercial40.MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setSurplusFeedInSocLimit(90) // .setSurplusFeedInAllowedChargePowerLimit(-8000) // .setSurplusFeedInIncreasePowerFactor(1.1) // @@ -34,11 +30,11 @@ public void test() throws Exception { new ComponentTest(new EssFeneconCommercial40Pv1Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfigPv1.create() // - .setId(CHARGER_ID) // - .setModbusId(MODBUS_ID) // - .setEssId(ESS_ID) // + .setId("charger0") // + .setModbusId("modbus0") // + .setEssId("ess0") // .setMaxActualPower(0) // .build()) // ; diff --git a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java index 652b0363b74..80003e5447f 100644 --- a/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java +++ b/io.openems.edge.ess.fenecon.commercial40/test/io/openems/edge/ess/fenecon/commercial40/charger/EssFeneconCommercial40Pv2ImplTest.java @@ -10,19 +10,15 @@ public class EssFeneconCommercial40Pv2ImplTest { - private static final String CHARGER_ID = "charger1"; - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { var ess = new EssFeneconCommercial40Impl(); new ManagedSymmetricEssTest(ess) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.ess.fenecon.commercial40.MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setSurplusFeedInSocLimit(90) // .setSurplusFeedInAllowedChargePowerLimit(-8000) // .setSurplusFeedInIncreasePowerFactor(1.1) // @@ -34,11 +30,11 @@ public void test() throws Exception { new ComponentTest(new EssFeneconCommercial40Pv2Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfigPv2.create() // - .setId(CHARGER_ID) // - .setModbusId(MODBUS_ID) // - .setEssId(ESS_ID) // + .setId("charger1") // + .setModbusId("modbus0") // + .setEssId("ess0") // .setMaxActualPower(0) // .build()) // ; diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/common/AllowedChargeDischargeHandlerTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/common/AllowedChargeDischargeHandlerTest.java index 8d8c1e1a08d..890f328d8ba 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/common/AllowedChargeDischargeHandlerTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/common/AllowedChargeDischargeHandlerTest.java @@ -1,10 +1,11 @@ package io.openems.edge.ess.generic.common; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; @@ -30,60 +31,60 @@ public void testStart() throws Exception { sut.calculateAllowedChargeDischargePower(clockProvider, false, null, null, null); assertEquals(0, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, null, null, null); assertEquals(0, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, -1, 500); assertEquals(225, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(-475, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(250, ChronoUnit.MILLIS); + clock.leap(250, MILLIS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, -1, 500); - clock.leap(250, ChronoUnit.MILLIS); + clock.leap(250, MILLIS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, -1, 500); assertEquals(-475, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(250, ChronoUnit.MILLIS); + clock.leap(250, MILLIS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 0, 500); - clock.leap(250, ChronoUnit.MILLIS); + clock.leap(250, MILLIS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 0, 500); assertEquals(450, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 0, 500); assertEquals(675, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); for (var i = 0; i < 15; i++) { - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 1, 500); } - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 1, 500); assertEquals(4275, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(380, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 1, 500); assertEquals(4500, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(403.75, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 2, 500); assertEquals(4500, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(451.25, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 2, 0, 500); assertEquals(1000, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(0, sut.lastBatteryAllowedDischargePower, 0.001); - clock.leap(1, ChronoUnit.SECONDS); + clock.leap(1, SECONDS); sut.calculateAllowedChargeDischargePower(clockProvider, true, 9, 9, 500); assertEquals(1225, sut.lastBatteryAllowedChargePower, 0.001); assertEquals(213.75, sut.lastBatteryAllowedDischargePower, 0.001); diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/EssGenericOffGridImplTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/EssGenericOffGridImplTest.java index 5fa9e0840b5..8761b50b6ed 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/EssGenericOffGridImplTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/offgrid/EssGenericOffGridImplTest.java @@ -1,11 +1,7 @@ package io.openems.edge.ess.generic.offgrid; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.test.DummyOffGridBatteryInverter; import io.openems.edge.common.startstop.StartStopConfig; @@ -16,26 +12,20 @@ public class EssGenericOffGridImplTest { - private static final String ESS_ID = "ess0"; - private static final String BATTERY_ID = "battery0"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String OFF_GRID_SWITCH_ID = "offGridSwitch0"; - @Test public void testStart() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); new ComponentTest(new EssGenericOffGridImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("batteryInverter", new DummyOffGridBatteryInverter(BATTERY_INVERTER_ID)) // - .addReference("battery", new DummyBattery(BATTERY_ID)) // - .addReference("offGridSwitch", new DummyOffGridSwitch(OFF_GRID_SWITCH_ID)) // + .addReference("componentManager", new DummyComponentManager()) // + .addReference("batteryInverter", new DummyOffGridBatteryInverter("batteryInverter0")) // + .addReference("battery", new DummyBattery("battery0")) // + .addReference("offGridSwitch", new DummyOffGridSwitch("offGridSwitch0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // - .setOffGridSwitchId(OFF_GRID_SWITCH_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // + .setOffGridSwitchId("offGridSwitch0") // .build()) // ; } diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java index 10a90e7e888..431c8e1d3c9 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssGenericManagedSymmetricImplTest.java @@ -1,5 +1,12 @@ package io.openems.edge.ess.generic.symmetric; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.common.startstop.StartStoppable.ChannelId.START_STOP; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER; +import static io.openems.edge.ess.generic.common.GenericManagedEss.EFFICIENCY_FACTOR; +import static io.openems.edge.ess.generic.symmetric.EssGenericManagedSymmetric.ChannelId.STATE_MACHINE; import static org.junit.Assert.assertEquals; import java.time.Instant; @@ -9,7 +16,6 @@ import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.test.DummyManagedSymmetricBatteryInverter; import io.openems.edge.common.startstop.StartStop; @@ -18,57 +24,38 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.ess.generic.common.GenericManagedEss; import io.openems.edge.ess.generic.symmetric.statemachine.StateMachine.State; import io.openems.edge.ess.test.DummyPower; import io.openems.edge.ess.test.ManagedSymmetricEssTest; public class EssGenericManagedSymmetricImplTest { - private static final String ESS_ID = "ess0"; - private static final String BATTERY_ID = "battery0"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - - private static final ChannelAddress ESS_STATE_MACHINE = new ChannelAddress(ESS_ID, "StateMachine"); - private static final ChannelAddress ESS_ALLOWED_DISCHARGE_POWER = new ChannelAddress(ESS_ID, - "AllowedDischargePower"); - - private static final ChannelAddress BATTERY_START_STOP = new ChannelAddress(BATTERY_ID, "StartStop"); - private static final ChannelAddress BATTERY_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent"); - private static final ChannelAddress BATTERY_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, - "DischargeMaxCurrent"); - - private static final ChannelAddress BATTERY_INVERTER_START_STOP = new ChannelAddress(BATTERY_INVERTER_ID, - "StartStop"); - private static final ChannelAddress BATTERY_INVERTER_ACTIVE_POWER = new ChannelAddress(BATTERY_INVERTER_ID, - "ActivePower"); - @Test public void testStart() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); new ComponentTest(new EssGenericManagedSymmetricImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID)) // - .addReference("battery", new DummyBattery(BATTERY_ID)) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter("batteryInverter0")) // + .addReference("battery", new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.START_BATTERY)) // + .output(STATE_MACHINE, State.START_BATTERY)) // .next(new TestCase("Start the Battery") // - .input(BATTERY_START_STOP, StartStop.START)) // + .input("battery0", START_STOP, StartStop.START)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.START_BATTERY_INVERTER)) // + .output(STATE_MACHINE, State.START_BATTERY_INVERTER)) // .next(new TestCase("Start the Battery-Inverter") // - .input(BATTERY_INVERTER_START_STOP, StartStop.START)) // + .input("batteryInverter0", START_STOP, StartStop.START)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.STARTED)) // + .output(STATE_MACHINE, State.STARTED)) // ; } @@ -78,30 +65,30 @@ public void testForceCharge() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID)) // - .addReference("battery", new DummyBattery(BATTERY_ID) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter("batteryInverter0")) // + .addReference("battery", new DummyBattery("battery0") // .withVoltage(500) // .withChargeMaxCurrent(50) // .withDischargeMaxCurrent(-5) // ) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase("Start the Battery") // - .input(BATTERY_START_STOP, StartStop.START) // - .output(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(BATTERY_INVERTER_ACTIVE_POWER, 0)) // + .input("battery0", START_STOP, StartStop.START) // + .output(ALLOWED_DISCHARGE_POWER, 0) // + .output(ACTIVE_POWER, 0)) // .next(new TestCase()) // .next(new TestCase("Start the Battery-Inverter") // - .input(BATTERY_INVERTER_START_STOP, StartStop.START)) // + .input("batteryInverter0", START_STOP, StartStop.START)) // .next(new TestCase()) // .next(new TestCase() // - .input(BATTERY_CHARGE_MAX_CURRENT, 50) // - .input(BATTERY_DISCHARGE_MAX_CURRENT, -5) // - .output(ESS_ALLOWED_DISCHARGE_POWER, (int) (-2500 * GenericManagedEss.EFFICIENCY_FACTOR))) // + .input("battery0", CHARGE_MAX_CURRENT, 50) // + .input("battery0", DISCHARGE_MAX_CURRENT, -5) // + .output(ALLOWED_DISCHARGE_POWER, (int) (-2500 * EFFICIENCY_FACTOR))) // ; } @@ -113,20 +100,20 @@ public void testDebugLog() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter("batteryInverter0") // .withStartStop(StartStop.START) // .withMaxApparentPower(92_000)) // - .addReference("battery", new DummyBattery(BATTERY_ID) // + .addReference("battery", new DummyBattery("battery0") // .withStartStop(StartStop.START) // .withSoc(60) // .withVoltage(700) // .withChargeMaxCurrent(80) // .withDischargeMaxCurrent(70)) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase() // .onBeforeProcessImage(() -> clock.leap(10, ChronoUnit.SECONDS)), 10); @@ -139,30 +126,29 @@ public void testTimeout() throws Exception { new ComponentTest(new EssGenericManagedSymmetricImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID)) // - .addReference("battery", new DummyBattery(BATTERY_ID)) // + .addReference("batteryInverter", new DummyManagedSymmetricBatteryInverter("batteryInverter0")) // + .addReference("battery", new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.START_BATTERY)) // + .output(STATE_MACHINE, State.START_BATTERY)) // .next(new TestCase("Start the Battery") // - .input(BATTERY_START_STOP, StartStop.START)) // + .input("battery0", START_STOP, StartStop.START)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.START_BATTERY_INVERTER)) // + .output(STATE_MACHINE, State.START_BATTERY_INVERTER)) // .next(new TestCase()// - .input(BATTERY_INVERTER_START_STOP, StartStop.STOP)// - .timeleap(clock, 350, ChronoUnit.SECONDS)// - )// + .input("batteryInverter0", START_STOP, StartStop.STOP)// + .timeleap(clock, 350, ChronoUnit.SECONDS)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.ERROR)) // + .output(STATE_MACHINE, State.ERROR)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.ERROR)) // + .output(STATE_MACHINE, State.ERROR)) // ; } } diff --git a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssProtectionTest.java b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssProtectionTest.java index 62959e6da20..d355eeee95c 100644 --- a/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssProtectionTest.java +++ b/io.openems.edge.ess.generic/test/io/openems/edge/ess/generic/symmetric/EssProtectionTest.java @@ -1,15 +1,23 @@ package io.openems.edge.ess.generic.symmetric; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MAX_CURRENT; +import static io.openems.edge.battery.api.Battery.ChannelId.DISCHARGE_MIN_VOLTAGE; +import static io.openems.edge.battery.api.Battery.ChannelId.SOC; +import static io.openems.edge.battery.api.Battery.ChannelId.VOLTAGE; +import static io.openems.edge.ess.generic.symmetric.EssProtection.ChannelId.EP_CHARGE_MAX_CURRENT; +import static io.openems.edge.ess.generic.symmetric.EssProtection.ChannelId.EP_DISCHARGE_MAX_CURRENT; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import java.time.Instant; import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; import org.junit.Test; import io.openems.common.test.TimeLeapClock; -import io.openems.common.types.ChannelAddress; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.batteryinverter.test.DummyManagedSymmetricBatteryInverter; import io.openems.edge.common.startstop.StartStop; @@ -22,30 +30,16 @@ public class EssProtectionTest { - private static final String ESS_ID = "ess0"; - private static final String BATTERY_ID = "battery0"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final ChannelAddress BATTERY_SOC = new ChannelAddress(BATTERY_ID, "Soc"); - private static final ChannelAddress BATTERY_VOLTAGE = new ChannelAddress(BATTERY_ID, "Voltage"); - private static final ChannelAddress BATTERY_CHARGE_MAX_VOLTAGE = new ChannelAddress(BATTERY_ID, "ChargeMaxVoltage"); - private static final ChannelAddress BATTERY_DISCHARGE_MIN_VOLTAGE = new ChannelAddress(BATTERY_ID, - "DischargeMinVoltage"); - private static final ChannelAddress BATTERY_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent"); - private static final ChannelAddress BATTERY_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, - "DischargeMaxCurrent"); - private static final ChannelAddress ESS_CHARGE_MAX_CURRENT = new ChannelAddress(ESS_ID, "EpChargeMaxCurrent"); - private static final ChannelAddress ESS_DISCHARGE_MAX_CURRENT = new ChannelAddress(ESS_ID, "EpDischargeMaxCurrent"); - @Test public void testEssProtection() throws Exception { final var ess = new EssGenericManagedSymmetricImpl(); final var clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); - final var batteryInverter = new DummyManagedSymmetricBatteryInverter(BATTERY_INVERTER_ID)// + final var batteryInverter = new DummyManagedSymmetricBatteryInverter("batteryInverter0")// .withStartStop(StartStop.START) // .withMaxApparentPower(92000)// .withDcMaxVoltage(1315)// .withDcMinVoltage(650); - final var battery = new DummyBattery(BATTERY_ID)// + final var battery = new DummyBattery("battery0")// .withStartStop(StartStop.START) // .withSoc(80)// .withChargeMaxCurrent(169)// @@ -62,64 +56,64 @@ public void testEssProtection() throws Exception { .addReference("batteryInverter", batteryInverter) // .addReference("battery", battery) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("ess0") // .setStartStopConfig(StartStopConfig.START) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setBatteryId(BATTERY_ID) // + .setBatteryInverterId("batteryInverter0") // + .setBatteryId("battery0") // .build()) // .next(new TestCase() // - .onBeforeProcessImage(() -> clock.leap(1, ChronoUnit.MINUTES)), 10)// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 80)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594))// + .onBeforeProcessImage(() -> clock.leap(1, MINUTES)), 10)// + .next(new TestCase()// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 80)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594))// .next(new TestCase() // - .onBeforeProcessImage(() -> clock.leap(1, ChronoUnit.MINUTES)), 10)// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 80)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594))// + .onBeforeProcessImage(() -> clock.leap(1, MINUTES)), 10)// + .next(new TestCase()// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 80)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594))// .next(new TestCase() // - .onBeforeProcessImage(() -> clock.leap(1, ChronoUnit.MINUTES)), 10)// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 80)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594))// + .onBeforeProcessImage(() -> clock.leap(1, MINUTES)), 10)// + .next(new TestCase()// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 80)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594))// ;// assertEquals("Started|SoC:80 %|L:0 W|Allowed:-92000;92000", ess.debugLog()); sutManaged// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.MINUTES))// + .timeleap(clock, 1, MINUTES))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 60)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 595)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 60)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 595)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.SECONDS))// + .timeleap(clock, 1, SECONDS))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 60)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 595)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 60)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 595)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.SECONDS))// + .timeleap(clock, 1, SECONDS))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_SOC, 60)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 595)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", SOC, 60)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 595)// )// ;// assertEquals("Started|SoC:60 %|L:0 W|Allowed:-92000;92000", ess.debugLog()); @@ -127,143 +121,143 @@ public void testEssProtection() throws Exception { // Force charge sutManaged// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 167)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 167)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 112)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 112)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 75)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 75)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 49)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 49)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 32)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 32)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 20)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 20)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 12)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 12)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 6)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 6)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 3)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 3)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 0)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, 0)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, -1)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, -1)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 645)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 0)// - .output(ESS_DISCHARGE_MAX_CURRENT, -2)// + .input("battery0", VOLTAGE, 645)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 0)// + .output(EP_DISCHARGE_MAX_CURRENT, -2)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .output(ESS_DISCHARGE_MAX_CURRENT, -2)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .output(EP_DISCHARGE_MAX_CURRENT, -2)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, -2)// - .output(ESS_DISCHARGE_MAX_CURRENT, -1)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, -2)// + .output(EP_DISCHARGE_MAX_CURRENT, -1)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, -2)// - .output(ESS_DISCHARGE_MAX_CURRENT, -1)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, -2)// + .output(EP_DISCHARGE_MAX_CURRENT, -1)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, -2)// - .output(ESS_DISCHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, -2)// + .output(EP_DISCHARGE_MAX_CURRENT, 0)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 650)// - .input(BATTERY_SOC, 0)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, -2)// - .output(ESS_DISCHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 650)// + .input("battery0", SOC, 0)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, -2)// + .output(EP_DISCHARGE_MAX_CURRENT, 0)// )// ;// assertEquals("Started|SoC:0 %|L:-1235 W|Allowed:-92000;-1235", ess.debugLog()); @@ -271,197 +265,197 @@ public void testEssProtection() throws Exception { // normal condition sutManaged// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .output(ESS_CHARGE_MAX_CURRENT, 654)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .output(EP_CHARGE_MAX_CURRENT, 654)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.MINUTES))// + .timeleap(clock, 1, MINUTES))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .output(ESS_CHARGE_MAX_CURRENT, 599)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .output(EP_CHARGE_MAX_CURRENT, 599)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.MINUTES))// + .timeleap(clock, 1, MINUTES))// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 700)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 169)// - .output(ESS_CHARGE_MAX_CURRENT, 561)// + .input("battery0", VOLTAGE, 700)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 169)// + .output(EP_CHARGE_MAX_CURRENT, 561)// )// .next(new TestCase()// - .timeleap(clock, 1, ChronoUnit.MINUTES))// + .timeleap(clock, 1, MINUTES))// ;// // Force discharge sutManaged// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 383)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 383)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 261)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 261)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 178)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 178)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 122)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 122)// )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 83)// - )// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 83)// + )// .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 57)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 38)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 26)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 18)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 12)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 8)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 5)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 3)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 798)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 1)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 798)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 0)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 798)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, -2)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, -1)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, -1)// - )// - .next(new TestCase()// - .input(BATTERY_VOLTAGE, 796)// - .input(BATTERY_SOC, 100)// - .input(BATTERY_CHARGE_MAX_VOLTAGE, 800)// - .input(BATTERY_DISCHARGE_MIN_VOLTAGE, 594)// - .input(BATTERY_DISCHARGE_MAX_CURRENT, 169)// - .input(BATTERY_CHARGE_MAX_CURRENT, 0)// - .output(ESS_CHARGE_MAX_CURRENT, 0)// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 57)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 38)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 26)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 18)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 12)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 8)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 5)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 3)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 798)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 1)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 798)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 0)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 798)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, -2)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, -1)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, -1)// + )// + .next(new TestCase()// + .input("battery0", VOLTAGE, 796)// + .input("battery0", SOC, 100)// + .input("battery0", CHARGE_MAX_VOLTAGE, 800)// + .input("battery0", DISCHARGE_MIN_VOLTAGE, 594)// + .input("battery0", DISCHARGE_MAX_CURRENT, 169)// + .input("battery0", CHARGE_MAX_CURRENT, 0)// + .output(EP_CHARGE_MAX_CURRENT, 0)// )// ;// assertEquals("Started|SoC:100 %|L:796 W|Allowed:796;92000", ess.debugLog()); diff --git a/io.openems.edge.ess.sma/test/io/openems/edge/sma/sunnyisland/EssSmaSunnyIslandImplTest.java b/io.openems.edge.ess.sma/test/io/openems/edge/sma/sunnyisland/EssSmaSunnyIslandImplTest.java index fcee18e6c8e..b420b8a95c5 100644 --- a/io.openems.edge.ess.sma/test/io/openems/edge/sma/sunnyisland/EssSmaSunnyIslandImplTest.java +++ b/io.openems.edge.ess.sma/test/io/openems/edge/sma/sunnyisland/EssSmaSunnyIslandImplTest.java @@ -9,17 +9,14 @@ public class EssSmaSunnyIslandImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ManagedSymmetricEssTest(new EssSmaSunnyIslandImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(Phase.L1) // .build()) // ; diff --git a/io.openems.edge.evcs.alpitronic.hypercharger/bnd.bnd b/io.openems.edge.evcs.alpitronic.hypercharger/bnd.bnd index 865db1aa41d..d1cfcf3a7ad 100644 --- a/io.openems.edge.evcs.alpitronic.hypercharger/bnd.bnd +++ b/io.openems.edge.evcs.alpitronic.hypercharger/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ io.openems.edge.timedata.api,\ -testpath: \ diff --git a/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java b/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java index 0dec8b68dad..89347a8e335 100644 --- a/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java +++ b/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java @@ -42,11 +42,13 @@ import io.openems.edge.common.taskmanager.Priority; import io.openems.edge.common.type.TypeUtils; import io.openems.edge.evcs.api.ChargeStateHandler; +import io.openems.edge.evcs.api.DeprecatedEvcs; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; @@ -61,8 +63,9 @@ EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // }) -public class EvcsAlpitronicHyperchargerImpl extends AbstractOpenemsModbusComponent implements Evcs, ManagedEvcs, - OpenemsComponent, ModbusComponent, EventHandler, EvcsAlpitronicHypercharger, TimedataProvider { +public class EvcsAlpitronicHyperchargerImpl extends AbstractOpenemsModbusComponent + implements Evcs, ManagedEvcs, DeprecatedEvcs, ElectricityMeter, OpenemsComponent, ModbusComponent, EventHandler, + EvcsAlpitronicHypercharger, TimedataProvider { private final Logger log = LoggerFactory.getLogger(EvcsAlpitronicHyperchargerImpl.class); /** Modbus offset for multiple connectors. */ @@ -91,7 +94,8 @@ protected void setModbus(BridgeModbus modbus) { *

* Accumulates the energy by calling this.calculateTotalEnergy.update(power); */ - private CalculateEnergyFromPower calculateTotalEnergy; + private final CalculateEnergyFromPower calculateTotalEnergy = new CalculateEnergyFromPower(this, + ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY); /** Handles charge states. */ private final ChargeStateHandler chargeStateHandler = new ChargeStateHandler(this); @@ -103,9 +107,16 @@ public EvcsAlpitronicHyperchargerImpl() { super(// OpenemsComponent.ChannelId.values(), // ModbusComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // + DeprecatedEvcs.ChannelId.values(), // EvcsAlpitronicHypercharger.ChannelId.values()); + DeprecatedEvcs.copyToDeprecatedEvcsChannels(this); + + // Automatically calculate L1/l2/L3 values from sum + ElectricityMeter.calculatePhasesFromActivePower(this); + // TODO consider CURRENT and VOLTAGE also } @Activate @@ -136,7 +147,6 @@ private void modified(ComponentContext context, Config config) throws OpenemsNam private void applyConfig(ComponentContext context, Config config) { this.config = config; - this.calculateTotalEnergy = new CalculateEnergyFromPower(this, Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY); this._setFixedMinimumHardwarePower(config.minHwPower()); this._setFixedMaximumHardwarePower(config.maxHwPower()); this._setPowerPrecision(1); @@ -160,12 +170,10 @@ public void handleEvent(Event event) { return; } switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: - this.calculateTotalEnergy.update(this.getChargePower().get()); - break; - case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE: - this.writeHandler.run(); - break; + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // + -> this.calculateTotalEnergy.update(this.getActivePower().get()); + case EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // + -> this.writeHandler.run(); } } @@ -186,8 +194,10 @@ protected ModbusProtocol defineModbusProtocol() { new FC4ReadInputRegistersTask(this.offset.apply(0), Priority.LOW, m(EvcsAlpitronicHypercharger.ChannelId.RAW_STATUS, new UnsignedWordElement(this.offset.apply(0))), + // TODO consider ElectricityMeter VOLTAGE m(EvcsAlpitronicHypercharger.ChannelId.CHARGING_VOLTAGE, new UnsignedDoublewordElement(this.offset.apply(1)), SCALE_FACTOR_MINUS_2), + // TODO consider ElectricityMeter CURRENT m(EvcsAlpitronicHypercharger.ChannelId.CHARGING_CURRENT, new UnsignedWordElement(this.offset.apply(3)), SCALE_FACTOR_MINUS_2), /* @@ -268,7 +278,7 @@ private void addCalculatePowerListeners() { // Calculate power from voltage and current final Consumer> calculatePower = ignore -> { - this._setChargePower(TypeUtils.getAsType(OpenemsType.INTEGER, TypeUtils.multiply(// + this._setActivePower(TypeUtils.getAsType(OpenemsType.INTEGER, TypeUtils.multiply(// this.getChargingVoltageChannel().getNextValue().get(), // this.getChargingCurrentChannel().getNextValue().get() // ))); @@ -283,35 +293,22 @@ private void addStatusListener() { /** * Maps the raw state into a {@link Status}. */ - switch (rawState) { - case AVAILABLE: - this._setStatus(Status.NOT_READY_FOR_CHARGING); - break; - case PREPARING_TAG_ID_READY: - this._setStatus(Status.READY_FOR_CHARGING); - break; - case CHARGING: - case PREPARING_EV_READY: - this._setStatus(Status.CHARGING); - break; - case RESERVED: - case SUSPENDED_EV: - case SUSPENDED_EV_SE: - this._setStatus(Status.CHARGING_REJECTED); - break; - case FINISHING: - this._setStatus(Status.CHARGING_FINISHED); - break; - case FAULTED: - case UNAVAILABLE: - case UNAVAILABLE_CONNECTION_OBJECT: - this._setStatus(Status.ERROR); - break; - case UNAVAILABLE_FW_UPDATE: - case UNDEFINED: - default: - this._setStatus(Status.UNDEFINED); - } + this._setStatus(switch (rawState) { + case AVAILABLE // + -> Status.NOT_READY_FOR_CHARGING; + case PREPARING_TAG_ID_READY // + -> Status.READY_FOR_CHARGING; + case CHARGING, PREPARING_EV_READY // + -> Status.CHARGING; + case RESERVED, SUSPENDED_EV, SUSPENDED_EV_SE // + -> Status.CHARGING_REJECTED; + case FINISHING // + -> Status.CHARGING_FINISHED; + case FAULTED, UNAVAILABLE, UNAVAILABLE_CONNECTION_OBJECT // + -> Status.ERROR; + case UNAVAILABLE_FW_UPDATE, UNDEFINED // + -> Status.UNDEFINED; + }); }); } diff --git a/io.openems.edge.evcs.alpitronic.hypercharger/test/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImplTest.java b/io.openems.edge.evcs.alpitronic.hypercharger/test/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImplTest.java index a2fb6ebfdc2..9e83d535c84 100644 --- a/io.openems.edge.evcs.alpitronic.hypercharger/test/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImplTest.java +++ b/io.openems.edge.evcs.alpitronic.hypercharger/test/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImplTest.java @@ -9,17 +9,14 @@ public class EvcsAlpitronicHyperchargerImplTest { - private static final String EVCS_ID = "evcs0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsAlpitronicHyperchargerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setModbusId(MODBUS_ID) // - .setId(EVCS_ID) // + .setModbusId("modbus0") // + .setId("evcs0") // .setModbusUnitId(1) // .setConnector(Connector.SLOT_0) // .setMaxHwPower(70_000) // diff --git a/io.openems.edge.evcs.api/bnd.bnd b/io.openems.edge.evcs.api/bnd.bnd index bd42bcb6773..eb22c030664 100644 --- a/io.openems.edge.evcs.api/bnd.bnd +++ b/io.openems.edge.evcs.api/bnd.bnd @@ -7,7 +7,7 @@ Bundle-Version: 1.0.0.${tstamp} ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ - io.openems.edge.meter.api + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/AbstractManagedEvcsComponent.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/AbstractManagedEvcsComponent.java index 56afd9dae55..ab88e63294c 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/AbstractManagedEvcsComponent.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/AbstractManagedEvcsComponent.java @@ -1,6 +1,7 @@ package io.openems.edge.evcs.api; import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; @@ -8,6 +9,7 @@ import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.meter.api.ElectricityMeter; /** * Abstract Managed EVCS Component. @@ -38,16 +40,17 @@ * */ public abstract class AbstractManagedEvcsComponent extends AbstractOpenemsComponent - implements Evcs, ManagedEvcs, EventHandler { + implements Evcs, ManagedEvcs, ElectricityMeter, EventHandler { private final Logger log = LoggerFactory.getLogger(AbstractManagedEvcsComponent.class); - private final WriteHandler writeHandler = new WriteHandler(this); + protected final WriteHandler writeHandler; private final ChargeStateHandler chargeStateHandler = new ChargeStateHandler(this); protected AbstractManagedEvcsComponent(io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) { super(firstInitialChannelIds, furtherInitialChannelIds); + this.writeHandler = this.createWriteHandler(); } @Override @@ -57,6 +60,13 @@ protected void activate(ComponentContext context, String id, String alias, boole Evcs.addCalculatePowerLimitListeners(this); } + @Override + @Deactivate + protected void deactivate() { + super.deactivate(); + this.writeHandler.cancelChargePower(); + } + @Override public void handleEvent(Event event) { if (!this.isEnabled()) { @@ -84,6 +94,10 @@ protected void logWarn(Logger log, String message) { super.logWarn(log, message); } + protected WriteHandler createWriteHandler() { + return new WriteHandler(this); + } + @Override protected void logDebug(Logger log, String message) { if (this.getConfiguredDebugMode()) { diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/DeprecatedEvcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/DeprecatedEvcs.java new file mode 100644 index 00000000000..e5e98c02c74 --- /dev/null +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/DeprecatedEvcs.java @@ -0,0 +1,58 @@ +package io.openems.edge.evcs.api; + +import static io.openems.common.channel.PersistencePriority.HIGH; +import static io.openems.common.channel.Unit.WATT; +import static io.openems.common.types.OpenemsType.INTEGER; + +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.IntegerReadChannel; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.meter.api.ElectricityMeter; + +/** + * This interface marks old implementations of {@link Evcs} that did not yet + * inherit {@link ElectricityMeter}. + * + *

+ * It should not be used for new implementations, but serves as a migration path + * for old implementations. + */ +public interface DeprecatedEvcs extends ElectricityMeter, OpenemsComponent { + + /** + * Copies values to Deprecated Channels during migration to + * {@link ElectricityMeter}. + * + *

    + *
  • ACTIVE_POWER -> CHARGE_POWER + *
  • ACTIVE_PRODUCTION_ENERGY -> ACTIVE_CONSUMPTION_ENERGY + *
+ * + * @param meter instance of myself + */ + public static void copyToDeprecatedEvcsChannels(DeprecatedEvcs meter) { + var chargePowerChannel = meter.channel(DeprecatedEvcs.ChannelId.CHARGE_POWER); + meter.getActivePowerChannel().onSetNextValue(v -> chargePowerChannel.setNextValue(v.get())); + meter.getActiveProductionEnergyChannel().onSetNextValue(v -> meter._setActiveConsumptionEnergy(v.get())); + } + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + /** + * Copy of {@link ElectricityMeter.ChannelId#ACTIVE_POWER}. + */ + CHARGE_POWER(Doc.of(INTEGER) // + .unit(WATT) // + .persistencePriority(HIGH)); + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } +} diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java index a1405624b2f..b8863a5c15b 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java @@ -11,14 +11,15 @@ import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.EnumReadChannel; import io.openems.edge.common.channel.IntegerReadChannel; -import io.openems.edge.common.channel.LongReadChannel; import io.openems.edge.common.channel.StateChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.common.modbusslave.ModbusType; +import io.openems.edge.meter.api.ElectricityMeter; +import io.openems.edge.meter.api.MeterType; -public interface Evcs extends OpenemsComponent { +public interface Evcs extends ElectricityMeter, OpenemsComponent { public static final Integer DEFAULT_MAXIMUM_HARDWARE_POWER = 22_080; // W public static final Integer DEFAULT_MINIMUM_HARDWARE_POWER = 4_140; // W @@ -42,21 +43,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * */ STATUS(Doc.of(Status.values()) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH)), // - - /** - * Charge Power. - * - *
    - *
  • Interface: Evcs - *
  • Readable - *
  • Type: Integer - *
  • Unit: W - *
- */ - CHARGE_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -72,7 +58,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * */ CHARGING_TYPE(Doc.of(ChargingType.values()) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -91,7 +76,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ PHASES(Doc.of(Phases.values()) // .debounce(5) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -116,7 +100,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ FIXED_MINIMUM_HARDWARE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -140,7 +123,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ FIXED_MAXIMUM_HARDWARE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -164,7 +146,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ MINIMUM_HARDWARE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -188,7 +169,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ MAXIMUM_HARDWARE_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -203,7 +183,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ MAXIMUM_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -218,7 +197,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ MINIMUM_POWER(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -233,21 +211,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { */ ENERGY_SESSION(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH)), // - - /** - * Active Consumption Energy. - * - *
    - *
  • Interface: Evcs - *
  • Type: Integer - *
  • Unit: Wh - *
- */ - ACTIVE_CONSUMPTION_ENERGY(Doc.of(OpenemsType.LONG) // - .unit(Unit.CUMULATED_WATT_HOURS) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH)), // /** @@ -260,7 +223,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * */ CHARGINGSTATION_COMMUNICATION_FAILED(Doc.of(Level.FAULT) // - .accessMode(AccessMode.READ_ONLY) // .persistencePriority(PersistencePriority.HIGH) // .text("Chargingstation Communication Failed " // + "| Keine Verbindung zur Ladestation " // @@ -278,6 +240,11 @@ public Doc doc() { } } + @Override + public default MeterType getMeterType() { + return MeterType.CONSUMPTION_METERED; + } + /** * Gets the Channel for {@link ChannelId#STATUS}. * @@ -305,44 +272,6 @@ public default void _setStatus(Status value) { this.getStatusChannel().setNextValue(value); } - /** - * Gets the Channel for {@link ChannelId#CHARGE_POWER}. - * - * @return the Channel - */ - public default IntegerReadChannel getChargePowerChannel() { - return this.channel(ChannelId.CHARGE_POWER); - } - - /** - * Gets the Charge Power in [W]. See {@link ChannelId#CHARGE_POWER}. - * - * @return the Channel {@link Value} - */ - public default Value getChargePower() { - return this.getChargePowerChannel().value(); - } - - /** - * Internal method to set the 'nextValue' on {@link ChannelId#CHARGE_POWER} - * Channel. - * - * @param value the next value - */ - public default void _setChargePower(Integer value) { - this.getChargePowerChannel().setNextValue(value); - } - - /** - * Internal method to set the 'nextValue' on {@link ChannelId#CHARGE_POWER} - * Channel. - * - * @param value the next value - */ - public default void _setChargePower(int value) { - this.getChargePowerChannel().setNextValue(value); - } - /** * Gets the Channel for {@link ChannelId#CHARGING_TYPE}. * @@ -666,45 +595,6 @@ public default void _setEnergySession(int value) { this.getEnergySessionChannel().setNextValue(value); } - /** - * Gets the Channel for {@link ChannelId#ACTIVE_CONSUMPTION_ENERGY}. - * - * @return the Channel - */ - public default LongReadChannel getActiveConsumptionEnergyChannel() { - return this.channel(ChannelId.ACTIVE_CONSUMPTION_ENERGY); - } - - /** - * Gets the Active Consumption Energy in [Wh_Σ]. This relates to negative - * ACTIVE_POWER. See {@link ChannelId#ACTIVE_CONSUMPTION_ENERGY}. - * - * @return the Channel {@link Value} - */ - public default Value getActiveConsumptionEnergy() { - return this.getActiveConsumptionEnergyChannel().value(); - } - - /** - * Internal method to set the 'nextValue' on - * {@link ChannelId#ACTIVE_CONSUMPTION_ENERGY} Channel. - * - * @param value the next value - */ - public default void _setActiveConsumptionEnergy(Long value) { - this.getActiveConsumptionEnergyChannel().setNextValue(value); - } - - /** - * Internal method to set the 'nextValue' on - * {@link ChannelId#ACTIVE_CONSUMPTION_ENERGY} Channel. - * - * @param value the next value - */ - public default void _setActiveConsumptionEnergy(long value) { - this.getActiveConsumptionEnergyChannel().setNextValue(value); - } - /** * Gets the Channel for {@link ChannelId#CHARGINGSTATION_COMMUNICATION_FAILED}. * @@ -765,6 +655,37 @@ public static void addCalculatePowerLimitListeners(Evcs evcs) { evcs.getPhasesChannel().onSetNextValue(calculateHardwarePowerLimits); } + /** + * Evaluates the number of Phases from the individual powers per phase. + * + *

+ * The EVCS will pull power from the grid for its own consumption and report + * that on one of the phases. This value is different from EVCS to EVCS but can + * be high. Because of this, this will only register a phase starting with 100W + * because then we definitively know that this load is caused by a car. + * + * @param activePowerL1 active power on L1 + * @param activePowerL2 active power on L2 + * @param activePowerL3 active power on L3 + * @return integer value indicating the number of phases; null if undefined + */ + public static Integer evaluatePhaseCount(Integer activePowerL1, Integer activePowerL2, Integer activePowerL3) { + int phases = 0; + if (activePowerL1 != null && activePowerL1 > 100) { + phases++; + } + if (activePowerL2 != null && activePowerL2 > 100) { + phases++; + } + if (activePowerL3 != null && activePowerL3 > 100) { + phases++; + } + return switch (phases) { + case 1, 2, 3 -> phases; + default -> null; + }; + } + /** * Used for Modbus/TCP Api Controller. Provides a Modbus table for the Channels * of this Component. @@ -775,7 +696,7 @@ public static void addCalculatePowerLimitListeners(Evcs evcs) { public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) { return ModbusSlaveNatureTable.of(Evcs.class, accessMode, 100) // .channel(0, ChannelId.STATUS, ModbusType.UINT16) // - .channel(1, ChannelId.CHARGE_POWER, ModbusType.UINT16) // + .uint16Reserved(1) // .channel(2, ChannelId.CHARGING_TYPE, ModbusType.UINT16) // .channel(3, ChannelId.PHASES, ModbusType.UINT16) // .channel(4, ChannelId.MAXIMUM_HARDWARE_POWER, ModbusType.UINT16) // @@ -786,7 +707,6 @@ public static ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode access .channel(9, ChannelId.FIXED_MINIMUM_HARDWARE_POWER, ModbusType.UINT16) // .channel(10, ChannelId.FIXED_MAXIMUM_HARDWARE_POWER, ModbusType.UINT16) // .channel(11, ChannelId.MINIMUM_POWER, ModbusType.UINT16) // - .channel(12, ChannelId.ACTIVE_CONSUMPTION_ENERGY, ModbusType.UINT16) // .build(); } } diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/MeasuringEvcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/MeasuringEvcs.java index 18f7154f46a..50e680aeb0e 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/MeasuringEvcs.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/MeasuringEvcs.java @@ -6,6 +6,7 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; +// TODO consider replacing this by ElectricityMeter public interface MeasuringEvcs extends Evcs { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { @@ -240,27 +241,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .persistencePriority(PersistencePriority.HIGH) // .text("Energy.Reactive.Import.Interval")), - /** - * Frequency. - * - *

- * Instantaneous reading of powerline frequency. NOTE: OCPP 1.6 does not have a - * UnitOfMeasure for frequency, the UnitOfMeasure for any SampledValue with - * measurand: Frequency is Hertz. - * - *

    - *
  • Interface: MeasuringEvcs - *
  • Readable - *
  • Type: String - *
  • Unit: Hz - *
- */ - FREQUENCY(Doc.of(OpenemsType.STRING) // - .unit(Unit.HERTZ) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH) // - .text("Frequency")), - /** * Active power to grid (export) * @@ -371,23 +351,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .persistencePriority(PersistencePriority.HIGH) // .text("Fan speed")), - /** - * Voltage. - * - *

- * Instantaneous AC RMS supply voltage. - * - *

    - *
  • Interface: MeasuringEvcs - *
  • Readable - *
  • Type: String - *
- */ - VOLTAGE(Doc.of(OpenemsType.STRING) // - .accessMode(AccessMode.READ_ONLY) // - .persistencePriority(PersistencePriority.HIGH) // - .text("Voltage")), - /** * Temperature. * diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/PhaseRotation.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/PhaseRotation.java new file mode 100644 index 00000000000..e89c312e781 --- /dev/null +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/PhaseRotation.java @@ -0,0 +1,77 @@ +package io.openems.edge.evcs.api; + +public enum PhaseRotation { + /** + * EVCS uses standard wiring. + * + *
    + *
  • EVCS L1 is connected to Grid L1 + *
  • EVCS L2 is connected to Grid L2 + *
  • EVCS L3 is connected to Grid L3 + *
+ */ + L1_L2_L3, + + /** + * EVCS uses rotated wiring. + * + *
    + *
  • EVCS L1 is connected to Grid L2 + *
  • EVCS L2 is connected to Grid L3 + *
  • EVCS L3 is connected to Grid L1 + *
+ */ + L2_L3_L1, + + /** + * EVCS uses rotated wiring. + * + *
    + *
  • EVCS L1 is connected to Grid L3 + *
  • EVCS L2 is connected to Grid L1 + *
  • EVCS L3 is connected to Grid L2 + *
+ */ + L3_L1_L2; + + public record RotatedPhases(// + Integer voltageL1, int currentL1, Integer activePowerL1, // + Integer voltageL2, int currentL2, Integer activePowerL2, // + Integer voltageL3, int currentL3, Integer activePowerL3) { + + /** + * Rotate phases for voltage, current and active power. + * + * @param phaseRotation the {@link PhaseRotation} + * @param voltageL1 the voltage on L1 + * @param currentL1 the current on L1 + * @param activePowerL1 the active power on L1 + * @param voltageL2 the voltage on L2 + * @param currentL2 the current on L2 + * @param activePowerL2 the active power on L2 + * @param voltageL3 the voltage on L3 + * @param currentL3 the current on L3 + * @param activePowerL3 the active power on L3 + * @return {@link RotatedPhases} + */ + public static RotatedPhases from(PhaseRotation phaseRotation, // + Integer voltageL1, int currentL1, Integer activePowerL1, // + Integer voltageL2, int currentL2, Integer activePowerL2, // + Integer voltageL3, int currentL3, Integer activePowerL3) { + return switch (phaseRotation) { + case L1_L2_L3 -> new RotatedPhases(// + voltageL1, currentL1, activePowerL1, // + voltageL2, currentL2, activePowerL2, // + voltageL3, currentL3, activePowerL3); + case L2_L3_L1 -> new RotatedPhases(// + voltageL3, currentL3, activePowerL3, // + voltageL1, currentL1, activePowerL1, // + voltageL2, currentL2, activePowerL2); + case L3_L1_L2 -> new RotatedPhases(// + voltageL2, currentL2, activePowerL2, // + voltageL3, currentL3, activePowerL3, // + voltageL1, currentL1, activePowerL1); + }; + } + } +} diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/WriteHandler.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/WriteHandler.java index 64fcb073b24..a9562639153 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/WriteHandler.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/WriteHandler.java @@ -75,7 +75,7 @@ private void setPower() { this.parent._setStatus(Status.ENERGY_LIMIT_REACHED); // Apply Charge Power - if (this.lastTarget != 0 || this.parent.getChargePower().orElse(0) != 0) { + if (this.lastTarget != 0 || this.parent.getActivePower().orElse(0) != 0) { this.parent.getChargeStateHandler().applyNewChargeState(ChargeState.DECREASING); this.applyChargePower(0); } @@ -128,7 +128,7 @@ private void setPower() { * * @param power Power that should be applied */ - private void applyChargePower(int power) { + protected void applyChargePower(int power) { try { boolean sent = false; @@ -249,4 +249,11 @@ private void logDebug(String message) { OpenemsComponent.logInfo(this.parent, this.log, message); } } + + /** + * Used for async applyChargePower calls. + * + */ + public void cancelChargePower() { + } } diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/test/DummyManagedEvcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/test/DummyManagedEvcs.java index fd28514e222..f64c89c394e 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/test/DummyManagedEvcs.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/test/DummyManagedEvcs.java @@ -12,9 +12,10 @@ import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; public class DummyManagedEvcs extends AbstractManagedEvcsComponent - implements Evcs, ManagedEvcs, OpenemsComponent, EventHandler { + implements Evcs, ManagedEvcs, ElectricityMeter, OpenemsComponent, EventHandler { private final EvcsPower evcsPower; private int minimumHardwarePower = Evcs.DEFAULT_MINIMUM_HARDWARE_POWER; @@ -23,6 +24,7 @@ public class DummyManagedEvcs extends AbstractManagedEvcsComponent public DummyManagedEvcs(String id, EvcsPower evcsPower) { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // Evcs.ChannelId.values() // ); @@ -85,14 +87,14 @@ public boolean getConfiguredDebugMode() { @Override public boolean applyChargePowerLimit(int power) throws OpenemsException { - this._setChargePower(power); + this._setActivePower(power); this._setStatus(Status.CHARGING); return true; } @Override public boolean pauseChargeProcess() throws OpenemsException { - this._setChargePower(0); + this._setActivePower(0); return true; } diff --git a/io.openems.edge.evcs.api/test/io/openems/edge/evcs/api/AbstractManagedEvcsTest.java b/io.openems.edge.evcs.api/test/io/openems/edge/evcs/api/AbstractManagedEvcsTest.java index 908cdeb7bfd..a7c39ac36a0 100644 --- a/io.openems.edge.evcs.api/test/io/openems/edge/evcs/api/AbstractManagedEvcsTest.java +++ b/io.openems.edge.evcs.api/test/io/openems/edge/evcs/api/AbstractManagedEvcsTest.java @@ -1,11 +1,17 @@ package io.openems.edge.evcs.api; +import static io.openems.edge.evcs.api.Evcs.ChannelId.ENERGY_SESSION; +import static io.openems.edge.evcs.api.Evcs.ChannelId.STATUS; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.CHARGE_STATE; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT_WITH_FILTER; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_ENERGY_LIMIT; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; import static org.junit.Assert.assertEquals; import org.junit.Test; import io.openems.common.function.ThrowingRunnable; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.channel.IntegerReadChannel; import io.openems.edge.common.filter.DisabledRampFilter; import io.openems.edge.common.filter.RampFilter; @@ -16,329 +22,272 @@ public class AbstractManagedEvcsTest { + /** + * Sleep between every TestCase to make sure that the Channel Values are added + * to the pastValues Map. This is required because the Channel Value timestamp + * does not consider the mocked Clock. + * + *

+ * Timeleap is not used, to avoid using a clock in the ChargeSatusHandler and + * therefore a ClockProvider function in every EVCS (.timeleap(clock, 31, + * ChronoUnit.SECONDS)) + */ + private static final ThrowingRunnable SLEEP = () -> Thread.sleep(1010); + private static final DummyEvcsPower EVCS_POWER = new DummyEvcsPower(new DisabledRampFilter()); private static final DummyEvcsPower EVCS_POWER_WITH_FILTER = new DummyEvcsPower(new RampFilter()); private static final DummyManagedEvcs EVCS0 = new DummyManagedEvcs("evcs0", EVCS_POWER); private static final DummyManagedEvcs EVCS1 = new DummyManagedEvcs("evcs1", EVCS_POWER); - private static final DummyManagedEvcs evcs2 = new DummyManagedEvcs("evcs2", EVCS_POWER_WITH_FILTER); + private static final DummyManagedEvcs EVCS2 = new DummyManagedEvcs("evcs2", EVCS_POWER_WITH_FILTER); private static final int MINIMUM = Evcs.DEFAULT_MINIMUM_HARDWARE_POWER; private static final int MAXIMUM = Evcs.DEFAULT_MAXIMUM_HARDWARE_POWER; - // Channel Addresses EVCS0 - private static ChannelAddress evcs0Status = new ChannelAddress("evcs0", "Status"); - private static ChannelAddress evcs0ChargeState = new ChannelAddress("evcs0", "ChargeState"); - private static ChannelAddress evcs0ChargePower = new ChannelAddress("evcs0", "ChargePower"); - private static ChannelAddress evcs0SetChargePowerLimit = new ChannelAddress("evcs0", "SetChargePowerLimit"); - - // Channel Addresses EVCS1 - private static ChannelAddress evcs1Status = new ChannelAddress("evcs1", "Status"); - private static ChannelAddress evcs1ChargeState = new ChannelAddress("evcs1", "ChargeState"); - private static ChannelAddress evcs1ChargePower = new ChannelAddress("evcs1", "ChargePower"); - private static ChannelAddress evcs1SetChargePowerLimit = new ChannelAddress("evcs1", "SetChargePowerLimit"); - private static ChannelAddress evcs1SetEnergyLimit = new ChannelAddress("evcs1", "SetEnergyLimit"); - private static ChannelAddress evcs1EnergySession = new ChannelAddress("evcs1", "EnergySession"); - - // Channel Addresses EVCS 2 - private static ChannelAddress evcs2Status = new ChannelAddress("evcs2", "Status"); - private static ChannelAddress evcs2ChargeState = new ChannelAddress("evcs2", "ChargeState"); - private static ChannelAddress evcs2ChargePower = new ChannelAddress("evcs2", "ChargePower"); - private static ChannelAddress evcs2SetChargePowerLimitWithFilter = new ChannelAddress("evcs2", - "SetChargePowerLimitWithFilter"); - /* * ATTENTION: The test could fail if you run it in Debug mode and e.g. the test * is expecting an output within the "getMinimumTimeTillCharingLimitTaken" time. */ - @Test public void abstractManagedEvcsTest() throws Exception { - // Sleep between every TestCase to make sure that the Channel Values are added - // to the pastValues Map. This is required because the Channel Value timestamp - // does not consider the mocked Clock. - final ThrowingRunnable sleep = () -> Thread.sleep(1010); - - ComponentTest test = new ComponentTest(EVCS0).addComponent(EVCS0); - - // Initial charge - test.next(new TestCase("Initial charge") // - - .input(evcs0SetChargePowerLimit, 15000) // - .input(evcs0ChargePower, 0) // - .input(evcs0Status, Status.READY_FOR_CHARGING) // - - .output(evcs0ChargePower, 15000) // - .output(evcs0ChargeState, ChargeState.INCREASING)); // - - /* - * Cannot check the nextValue of SetChargePowerLimit as output, because the test - * validator checks the write value (.output(evcs0SetChargePowerLimit, 15000)) - */ - // Check set charge limit + var test = new ComponentTest(EVCS0) // + .addComponent(EVCS0) // + + .next(new TestCase("Initial charge") // + .input(SET_CHARGE_POWER_LIMIT, 15000) // + .input(ACTIVE_POWER, 0) // + .input(STATUS, Status.READY_FOR_CHARGING) // + .output(ACTIVE_POWER, 15000) // + .output(CHARGE_STATE, ChargeState.INCREASING)); // + + // Cannot check the nextValue of SetChargePowerLimit as output, because the test + // validator checks the write value + // (.output(ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT, 15000)) assertEquals("Check next value of setChargePowerLimit", 15000, // - ((IntegerReadChannel) EVCS0.channel(ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT)).getNextValue() + (EVCS0.channel(SET_CHARGE_POWER_LIMIT)).getNextValue() .orElse(0).intValue()); - // Wait till charge limit is taken - test.next(new TestCase("Check ChargeState after 'getMinimumTimeTillCharingLimitTaken'") // - /* - * Timeleap is not used, to avoid using a clock in the ChargeSatusHandler and - * therefore a ClockProvider function in every EVCS (.timeleap(clock, 31, - * ChronoUnit.SECONDS)) - */ - .onAfterProcessImage(sleep) // - .input(evcs0ChargePower, 15000) // - .output(evcs0Status, Status.CHARGING) // - .output(evcs0ChargeState, ChargeState.CHARGING)); // - - // Decrease power - test.next(new TestCase("Decrease Power") // - .input(evcs0ChargePower, 15000) // - .input(evcs0SetChargePowerLimit, 8000) // - .output(evcs0ChargePower, 8000) // - .output(evcs0Status, Status.CHARGING) // - .output(evcs0ChargeState, ChargeState.DECREASING)); // - - // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not - // expired - test.next(new TestCase("Stay in decreasing charge state") // - .input(evcs0ChargePower, 8000) // - .input(evcs0SetChargePowerLimit, 20000) // - .output(evcs0ChargePower, 8000) // - .output(evcs0Status, Status.CHARGING) // - .output(evcs0ChargeState, ChargeState.DECREASING)); // - - // MinimumTimeTillCharingLimitTaken passed - test.next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // - .onAfterProcessImage(sleep) // - .input(evcs0ChargePower, 8000) // - .input(evcs0SetChargePowerLimit, 20000) // - .output(evcs0ChargePower, 20000) // - .output(evcs0Status, Status.CHARGING) // - .output(evcs0ChargeState, ChargeState.INCREASING)); // - - // Charge power is increasing but decrease has higher priority than pause - test.next(new TestCase("Decrease has highest priority") // - .input(evcs0ChargePower, 20000) // - .input(evcs0SetChargePowerLimit, 0) // - .output(evcs0ChargePower, 0) // - .output(evcs0Status, Status.CHARGING_REJECTED) // - .output(evcs0ChargeState, ChargeState.DECREASING)); // + test // + .next(new TestCase("Check ChargeState after 'getMinimumTimeTillCharingLimitTaken'") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 15000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.CHARGING)) + + .next(new TestCase("Decrease Power") // + .input(ACTIVE_POWER, 15000) // + .input(SET_CHARGE_POWER_LIMIT, 8000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not + // expired + .next(new TestCase("Stay in decreasing charge state") // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT, 20000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT, 20000) // + .output(ACTIVE_POWER, 20000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + // Charge power is increasing but decrease has higher priority than pause + .next(new TestCase("Decrease has highest priority") // + .input(ACTIVE_POWER, 20000) // + .input(SET_CHARGE_POWER_LIMIT, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.DECREASING)); // } @Test public void abstractManagedEvcsStateChangesTest() throws Exception { - // Sleep between every TestCase to make sure that the Channel Values are added - // to the pastValues Map. This is required because the Channel Value timestamp - // does not consider the mocked Clock. - final ThrowingRunnable sleep = () -> Thread.sleep(1010); - - ComponentTest test = new ComponentTest(EVCS1).addComponent(EVCS1); - - // Initial charge - test.next(new TestCase("Initial charge") // - - .input(evcs1SetChargePowerLimit, 15000) // - .input(evcs1ChargePower, 0) // - .input(evcs1Status, Status.READY_FOR_CHARGING) // - .input(evcs1EnergySession, 9999) // - .input(evcs1SetEnergyLimit, 10000) // - .output(evcs1ChargePower, 15000) // - .output(evcs1ChargeState, ChargeState.INCREASING)); // - - // EnergyLimit Reached - test.next(new TestCase("Energy limit reached") // - .input(evcs1ChargePower, 15000) // - .input(evcs1EnergySession, 10000) // - .input(evcs1Status, Status.CHARGING) // - .output(evcs1ChargeState, ChargeState.DECREASING) // - .output(evcs1Status, Status.ENERGY_LIMIT_REACHED) // - .output(evcs1ChargePower, 0)); // - - // EnergyLimit increased - still in pause - test.next(new TestCase("Energy limit increased - still in pause") // - .input(evcs1ChargePower, 0) // - .input(evcs1EnergySession, 10000) // - .input(evcs1SetEnergyLimit, 20000) // - .input(evcs1SetChargePowerLimit, 15000) // - .output(evcs1ChargePower, 0) // - .output(evcs1ChargeState, ChargeState.DECREASING)); // - - // EnergyLimit increased - after pause - test.next(new TestCase("Energy limit increased - after pause") // - .onAfterProcessImage(sleep) // - .input(evcs1ChargePower, 0) // - .input(evcs1EnergySession, 10000) // - .input(evcs1SetEnergyLimit, 20000) // - .input(evcs1SetChargePowerLimit, 15000) // - .output(evcs1ChargePower, 15000) // - .output(evcs1ChargeState, ChargeState.INCREASING)); // - - // Decrease power - test.next(new TestCase("Decrease Power") // - .input(evcs1ChargePower, 15000) // - .input(evcs1SetChargePowerLimit, 8000) // - .output(evcs1ChargePower, 8000) // - .output(evcs1Status, Status.CHARGING) // - .output(evcs1ChargeState, ChargeState.DECREASING)); // - - // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not - // expired - test.next(new TestCase("Stay in decreasing charge state") // - .input(evcs1ChargePower, 8000) // - .input(evcs1SetChargePowerLimit, 20000) // - .output(evcs1ChargePower, 8000) // - .output(evcs1Status, Status.CHARGING) // - .output(evcs1ChargeState, ChargeState.DECREASING)); // - - // MinimumTimeTillCharingLimitTaken passed - test.next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // - .onAfterProcessImage(sleep) // - .input(evcs1ChargePower, 8000) // - .input(evcs1SetChargePowerLimit, 20000) // - .output(evcs1ChargePower, 20000) // - .output(evcs1Status, Status.CHARGING) // - .output(evcs1ChargeState, ChargeState.INCREASING)); // - - // Charge power is increasing but decrease has higher priority than pause - test.next(new TestCase("Decrease has highest priority") // - .input(evcs1ChargePower, 20000) // - .input(evcs1SetChargePowerLimit, 0) // - .output(evcs1ChargePower, 0) // - .output(evcs1Status, Status.CHARGING_REJECTED) // - .output(evcs1ChargeState, ChargeState.DECREASING)); // + new ComponentTest(EVCS1) // + .addComponent(EVCS1) // + + .next(new TestCase("Initial charge") // + .input(SET_CHARGE_POWER_LIMIT, 15000) // + .input(ACTIVE_POWER, 0) // + .input(STATUS, Status.READY_FOR_CHARGING) // + .input(ENERGY_SESSION, 9999) // + .input(SET_ENERGY_LIMIT, 10000) // + .output(ACTIVE_POWER, 15000) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Energy limit reached") // + .input(ACTIVE_POWER, 15000) // + .input(ENERGY_SESSION, 10000) // + .input(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING) // + .output(STATUS, Status.ENERGY_LIMIT_REACHED) // + .output(ACTIVE_POWER, 0)) + + .next(new TestCase("Energy limit increased - still in pause") // + .input(ACTIVE_POWER, 0) // + .input(ENERGY_SESSION, 10000) // + .input(SET_ENERGY_LIMIT, 20000) // + .input(SET_CHARGE_POWER_LIMIT, 15000) // + .output(ACTIVE_POWER, 0) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("Energy limit increased - after pause") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 0) // + .input(ENERGY_SESSION, 10000) // + .input(SET_ENERGY_LIMIT, 20000) // + .input(SET_CHARGE_POWER_LIMIT, 15000) // + .output(ACTIVE_POWER, 15000) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Decrease Power") // + .input(ACTIVE_POWER, 15000) // + .input(SET_CHARGE_POWER_LIMIT, 8000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("Stay in decreasing charge state; 'MinimumTimeTillCharingLimitTaken' is not expired") // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT, 20000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT, 20000) // + .output(ACTIVE_POWER, 20000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Charge power is increasing, but decrease has highest priority") // + .input(ACTIVE_POWER, 20000) // + .input(SET_CHARGE_POWER_LIMIT, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.DECREASING)); // } @Test public void abstractManagedEvcsWithFilterTest() throws Exception { - // Sleep between every TestCase to make sure that the Channel Values are added - // to the pastValues Map. This is required because the Channel Value timestamp - // does not consider the mocked Clock. - final ThrowingRunnable sleep = () -> Thread.sleep(1010); - - ComponentTest test = new ComponentTest(evcs2).addComponent(evcs2); + ComponentTest test = new ComponentTest(EVCS2) // + .addComponent(EVCS2); // Initial charge - int initialResult = (int) (MINIMUM + MAXIMUM * evcs2.getEvcsPower().getIncreaseRate()); // 5244 - test.next(new TestCase("Initial charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 0) // - .input(evcs2Status, Status.READY_FOR_CHARGING) // - .output(evcs2ChargePower, initialResult) // - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - /* - * Cannot check the nextValue of SetChargePowerLimit as output, because the test - * validator checks the write value (.output(evcs0SetChargePowerLimit, - * initialResult)) - */ - // Check set charge limit + int initialResult = (int) (MINIMUM + MAXIMUM * EVCS2.getEvcsPower().getIncreaseRate()); // 5244 + + test // + .next(new TestCase("Initial charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 0) // + .input(STATUS, Status.READY_FOR_CHARGING) // + .output(ACTIVE_POWER, initialResult) // + .output(CHARGE_STATE, ChargeState.INCREASING)); // + + // Cannot check the nextValue of SetChargePowerLimit as output, because the test + // validator checks the write value + // (.output(ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT, + // initialResult)) assertEquals("Check next value of setChargePowerLimit", initialResult, // - ((IntegerReadChannel) evcs2.channel(ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT)).getNextValue() + (EVCS2.channel(SET_CHARGE_POWER_LIMIT)).getNextValue() .orElse(0).intValue()); - // Further charge - int increasingValue = (int) (MAXIMUM * evcs2.getEvcsPower().getIncreaseRate()); - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 5244) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, (int) (initialResult + increasingValue)) // 6348 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Further charge - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 6348) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, initialResult + increasingValue * 2) // 7452 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Further charge - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 6348) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, initialResult + increasingValue * 3) // 8556 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Further charge - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 15000) // - .input(evcs2ChargePower, 6348) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, initialResult + increasingValue * 4) // 9660 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Further charge - reached target - test.next(new TestCase("Further charge") // - - .input(evcs2SetChargePowerLimitWithFilter, 10000) // - .input(evcs2ChargePower, 6348) // - .input(evcs2Status, Status.CHARGING) // - .output(evcs2ChargePower, 10_000) // 10000 W - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Wait till charge limit is taken - test.next(new TestCase("Check ChargeState after 'getMinimumTimeTillCharingLimitTaken'") // - .onAfterProcessImage(sleep) // - .input(evcs2ChargePower, 10000) // - .output(evcs2Status, Status.CHARGING) // - .output(evcs2ChargeState, ChargeState.CHARGING)); // - - // Decrease power - test.next(new TestCase("Decrease Power") // - .input(evcs2ChargePower, 10000) // - .input(evcs2SetChargePowerLimitWithFilter, 8000) // - .output(evcs2ChargePower, 8000) // - .output(evcs2Status, Status.CHARGING) // - .output(evcs2ChargeState, ChargeState.DECREASING)); // - - // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not - // expired - test.next(new TestCase("Stay in decreasing charge state") // - .input(evcs2ChargePower, 8000) // - .input(evcs2SetChargePowerLimitWithFilter, 20000) // - .output(evcs2ChargePower, 8000) // - .output(evcs2Status, Status.CHARGING) // - .output(evcs2ChargeState, ChargeState.DECREASING)); // - - // MinimumTimeTillCharingLimitTaken passed - test.next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // - .onAfterProcessImage(sleep) // - .input(evcs2ChargePower, 8000) // - .input(evcs2SetChargePowerLimitWithFilter, 20000) // - .output(evcs2ChargePower, 8000 + increasingValue) // 9104 - .output(evcs2Status, Status.CHARGING) // - .output(evcs2ChargeState, ChargeState.INCREASING)); // - - // Charge power is increasing but decrease has higher priority than pause - test.next(new TestCase("Decrease has highest priority") // - .input(evcs2ChargePower, 20000) // - .input(evcs2SetChargePowerLimitWithFilter, 0) // - .output(evcs2ChargePower, 0) // - .output(evcs2Status, Status.CHARGING_REJECTED) // - .output(evcs2ChargeState, ChargeState.DECREASING)); // - - // Charging stopped - still in pause state - test.next(new TestCase("Charging stopped - still in pause state") // - .input(evcs2ChargePower, 0) // - .input(evcs2SetChargePowerLimitWithFilter, 0) // - .output(evcs2ChargePower, 0) // - .output(evcs2Status, Status.CHARGING_REJECTED) // - .output(evcs2ChargeState, ChargeState.DECREASING)); // - - // Charging stopped - test.next(new TestCase("Charging stopped") // - .onAfterProcessImage(sleep) // - .input(evcs2ChargePower, 0) // - .input(evcs2SetChargePowerLimitWithFilter, 0) // - .output(evcs2ChargePower, 0) // - .output(evcs2Status, Status.CHARGING_REJECTED) // - .output(evcs2ChargeState, ChargeState.NOT_CHARGING)); // + int increasingValue = (int) (MAXIMUM * EVCS2.getEvcsPower().getIncreaseRate()); + test // + .next(new TestCase("Further charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 5244) // + .input(STATUS, Status.CHARGING) // + // 6348 W + .output(ACTIVE_POWER, (int) (initialResult + increasingValue)) + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Further charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 6348) // + .input(STATUS, Status.CHARGING) // + .output(ACTIVE_POWER, initialResult + increasingValue * 2) // 7452 W + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Further charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 6348) // + .input(STATUS, Status.CHARGING) // + .output(ACTIVE_POWER, initialResult + increasingValue * 3) // 8556 W + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Further charge") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 15000) // + .input(ACTIVE_POWER, 6348) // + .input(STATUS, Status.CHARGING) // + .output(ACTIVE_POWER, initialResult + increasingValue * 4) // 9660 W + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase("Further charge - reached target") // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 10000) // + .input(ACTIVE_POWER, 6348) // + .input(STATUS, Status.CHARGING) // + .output(ACTIVE_POWER, 10_000) // 10000 W + .output(CHARGE_STATE, ChargeState.INCREASING)) + + .next(new TestCase( + "Wait till charge limit is taken. Check ChargeState after 'getMinimumTimeTillCharingLimitTaken'") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 10000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.CHARGING)) + + .next(new TestCase("Decrease Power") // + .input(ACTIVE_POWER, 10000) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 8000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + // Enough power to increase, but 'MinimumTimeTillCharingLimitTaken' is not + // expired + .next(new TestCase("Stay in decreasing charge state") // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 20000) // + .output(ACTIVE_POWER, 8000) // + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("MinimumTimeTillCharginglimitTaken passed") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 8000) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 20000) // + .output(ACTIVE_POWER, 8000 + increasingValue) // 9104 + .output(STATUS, Status.CHARGING) // + .output(CHARGE_STATE, ChargeState.INCREASING)) + + // Charge power is increasing but decrease has higher priority than pause + .next(new TestCase("Decrease has highest priority") // + .input(ACTIVE_POWER, 20000) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("Charging stopped - still in pause state") // + .input(ACTIVE_POWER, 0) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.DECREASING)) + + .next(new TestCase("Charging stopped") // + .onAfterProcessImage(SLEEP) // + .input(ACTIVE_POWER, 0) // + .input(SET_CHARGE_POWER_LIMIT_WITH_FILTER, 0) // + .output(ACTIVE_POWER, 0) // + .output(STATUS, Status.CHARGING_REJECTED) // + .output(CHARGE_STATE, ChargeState.NOT_CHARGING)); // } } diff --git a/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImpl.java b/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImpl.java index 348062f8ecd..ab6eca07d59 100644 --- a/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImpl.java +++ b/io.openems.edge.evcs.cluster/src/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImpl.java @@ -1,5 +1,9 @@ package io.openems.edge.evcs.cluster; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; +import static io.openems.edge.evcs.api.Phases.TWO_PHASE; +import static java.lang.Math.round; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -39,7 +43,6 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.MetaEvcs; -import io.openems.edge.evcs.api.Phases; import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @@ -52,7 +55,7 @@ EdgeEventConstants.TOPIC_CYCLE_AFTER_CONTROLLERS, // }) public class EvcsClusterPeakShavingImpl extends AbstractOpenemsComponent - implements MetaEvcs, OpenemsComponent, Evcs, EventHandler, EvcsClusterPeakShaving, + implements MetaEvcs, OpenemsComponent, Evcs, ElectricityMeter, EventHandler, EvcsClusterPeakShaving, /* * Cluster is not a Controller, but we need to be placed at the correct position * in the Cycle by the Scheduler to be able to read the actually available ESS @@ -107,6 +110,7 @@ public class EvcsClusterPeakShavingImpl extends AbstractOpenemsComponent public EvcsClusterPeakShavingImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // EvcsClusterPeakShaving.ChannelId.values(), // Controller.ChannelId.values() // @@ -222,8 +226,8 @@ public void handleEvent(Event event) { */ private void calculateChannelValues() { this.currentEvcsClusterState = EvcsClusterStatus.REGULAR; - final var chargePower = new CalculateIntegerSum(); - final var blockedChargePower = new CalculateIntegerSum(); + final var activePower = new CalculateIntegerSum(); + final var blockedActivePower = new CalculateIntegerSum(); final var minHardwarePower = new CalculateIntegerSum(); final var maxHardwarePowerOfAll = new CalculateIntegerSum(); final var minFixedHardwarePower = new CalculateIntegerSum(); @@ -231,20 +235,16 @@ private void calculateChannelValues() { final var minPower = new CalculateIntegerSum(); final var evcsClusterStatus = new CalculateEvcsClusterStatus(); - for (Evcs evcs : this.getSortedEvcss()) { - chargePower.addValue(evcs.getChargePowerChannel()); - blockedChargePower.addValue(evcs.getChargePowerChannel(), value -> { - + for (var evcs : this.getSortedEvcss()) { + activePower.addValue(evcs.getActivePowerChannel()); + blockedActivePower.addValue(evcs.getActivePowerChannel(), value -> { // Calculate the blocked power using all 3 phases for now if (value != null) { - switch (evcs.getPhases()) { - case ONE_PHASE: - return value * Phases.THREE_PHASE.getValue(); - case TWO_PHASE: - return Math.round(value / Phases.TWO_PHASE.getValue() * Phases.THREE_PHASE.getValue()); - case THREE_PHASE: - return value; - } + return switch (evcs.getPhases()) { + case ONE_PHASE -> value * THREE_PHASE.getValue(); + case TWO_PHASE -> round(value / TWO_PHASE.getValue() * THREE_PHASE.getValue()); + case THREE_PHASE -> value; + }; } return null; }); @@ -258,8 +258,8 @@ private void calculateChannelValues() { } } - this._setChargePower(chargePower.calculate()); - this._setEvcsBlockedChargePower(blockedChargePower.calculate()); + this._setActivePower(activePower.calculate()); + this._setEvcsBlockedChargePower(blockedActivePower.calculate()); this._setFixedMinimumHardwarePower(minFixedHardwarePower.calculate()); this._setFixedMaximumHardwarePower(maxFixedHardwarePower.calculate()); this.channel(Evcs.ChannelId.MINIMUM_HARDWARE_POWER).setNextValue(minHardwarePower.calculate()); @@ -329,7 +329,7 @@ protected void limitEvcss() { * Defines the active charging stations that are charging. */ List activeEvcss = new ArrayList<>(); - for (Evcs evcs : this.getSortedEvcss()) { + for (var evcs : this.getSortedEvcss()) { if (evcs instanceof ManagedEvcs) { var managedEvcs = (ManagedEvcs) evcs; int requestedPower = managedEvcs.getSetChargePowerRequestChannel().getNextWriteValue().orElse(0); @@ -361,8 +361,7 @@ protected void limitEvcss() { case READY_FOR_CHARGING: // Check if there is enough power for an initial charge - // if (totalPowerLimit - this.getChargePower().orElse(0) >= guaranteedPower) { - if (totalPowerLimit - initialChargePower - this.getChargePower().orElse(0) >= guaranteedPower) { + if (totalPowerLimit - initialChargePower - this.getActivePower().orElse(0) >= guaranteedPower) { this.logInfoInDebugmode("Set initial power " + guaranteedPower + " to " + evcs.id()); managedEvcs.setChargePowerLimit(guaranteedPower); @@ -404,7 +403,7 @@ protected void limitEvcss() { /* * Distributes the available Power to the active EVCSs */ - for (ManagedEvcs evcs : activeEvcss) { + for (var evcs : activeEvcss) { // int guaranteedPower = evcs.getMinimumPowerChannel().getNextValue().orElse(0); int guaranteedPower = evcs.getMinimumPowerChannel().getNextValue().orElse(0); @@ -483,8 +482,7 @@ public int getMaximumPowerToDistribute() { // Calculate maximum grid power var gridPower = this.getGridPower(); - var maxAvailableGridPower = (this.config.hardwarePowerLimitPerPhase() * Phases.THREE_PHASE.getValue()) - - gridPower; + var maxAvailableGridPower = (this.config.hardwarePowerLimitPerPhase() * THREE_PHASE.getValue()) - gridPower; this.channel(EvcsClusterPeakShaving.ChannelId.MAXIMUM_AVAILABLE_GRID_POWER).setNextValue(maxAvailableGridPower); // Current charge power blocked by all EVCS's @@ -496,7 +494,7 @@ public int getMaximumPowerToDistribute() { "Calculation of the maximum charge Power: EVCS Charge [" + evcsCharge + "] + Max. available storage power [" + maxAvailableStoragePower + "] + ( Configured Hardware Limit * 3 [" - + this.config.hardwarePowerLimitPerPhase() * Phases.THREE_PHASE.getValue() + + this.config.hardwarePowerLimitPerPhase() * THREE_PHASE.getValue() + "] - Maximum of all three phases * 3 [" + gridPower + "]"); return allowedChargePower > 0 ? allowedChargePower : 0; @@ -531,8 +529,7 @@ private int getGridPower() { public int getAvailableGridPower() { // Calculate maximum grid power int gridPower = this.getGridPower(); - int maxAvailableGridPower = (this.config.hardwarePowerLimitPerPhase() * Phases.THREE_PHASE.getValue()) - - gridPower; + int maxAvailableGridPower = (this.config.hardwarePowerLimitPerPhase() * THREE_PHASE.getValue()) - gridPower; return maxAvailableGridPower; } diff --git a/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImplTest.java b/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImplTest.java index 2f6aee35dbf..42c4a4310e9 100644 --- a/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImplTest.java +++ b/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/EvcsClusterPeakShavingImplTest.java @@ -1,8 +1,23 @@ package io.openems.edge.evcs.cluster; +import static io.openems.edge.common.sum.Sum.ChannelId.ESS_ACTIVE_POWER; +import static io.openems.edge.ess.api.ManagedSymmetricEss.ChannelId.ALLOWED_DISCHARGE_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MAXIMUM_HARDWARE_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MAXIMUM_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.MINIMUM_HARDWARE_POWER; +import static io.openems.edge.evcs.api.Evcs.ChannelId.STATUS; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.CHARGE_STATE; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_LIMIT; +import static io.openems.edge.evcs.api.ManagedEvcs.ChannelId.SET_CHARGE_POWER_REQUEST; +import static io.openems.edge.evcs.cluster.EvcsClusterPeakShaving.ChannelId.EVCS_CLUSTER_STATUS; +import static io.openems.edge.evcs.cluster.EvcsClusterPeakShaving.ChannelId.MAXIMUM_POWER_TO_DISTRIBUTE; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.filter.DisabledRampFilter; import io.openems.edge.common.filter.RampFilter; import io.openems.edge.common.sum.DummySum; @@ -29,341 +44,256 @@ public class EvcsClusterPeakShavingImplTest { private static final DummyManagedEvcs EVCS2 = new DummyManagedEvcs("evcs2", EVCS_POWER); private static final DummyManagedEvcs EVCS3 = new DummyManagedEvcs("evcs3", EVCS_POWER); private static final DummyManagedEvcs EVCS4 = new DummyManagedEvcs("evcs4", EVCS_POWER); - - private static final DummyEvcsPower EVCS_POWER_WITH_FILTER = new DummyEvcsPower(new RampFilter()); - private static final DummyManagedEvcs EVCS5 = new DummyManagedEvcs("evcs5", EVCS_POWER_WITH_FILTER); + private static final DummyManagedEvcs EVCS5 = new DummyManagedEvcs("evcs5", new DummyEvcsPower(new RampFilter())); private static final int HARDWARE_POWER_LIMIT_PER_PHASE = 7000; - private static final ChannelAddress SUM_ESS_ACTIVE_POWER = new ChannelAddress("_sum", "EssActivePower"); - private static final ChannelAddress METER_GRID_ACTIVE_POWER = new ChannelAddress("meter0", "ActivePower"); - private static final ChannelAddress METER_GRID_ACTIVE_POWER_L1 = new ChannelAddress("meter0", "ActivePowerL1"); - private static final ChannelAddress METER_GRID_ACTIVE_POWER_L2 = new ChannelAddress("meter0", "ActivePowerL2"); - private static final ChannelAddress METER_GRID_ACTIVE_POWER_L3 = new ChannelAddress("meter0", "ActivePowerL3"); - private static final ChannelAddress ESS_ALLOWED_DISCHARGE_POWER = new ChannelAddress("ess0", - "AllowedDischargePower"); - - private static final ChannelAddress EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE = new ChannelAddress("evcsCluster0", - "MaximumPowerToDistribute"); - private static final ChannelAddress EVCS_CLUSTER_STATUS = new ChannelAddress("evcsCluster0", "EvcsClusterStatus"); - - private static final ChannelAddress EVCS0_STATUS = new ChannelAddress("evcs0", "Status"); - private static final ChannelAddress EVCS0_CHARGE_POWER = new ChannelAddress("evcs0", "ChargePower"); - private static final ChannelAddress EVCS0_MAXIMUM_POWER = new ChannelAddress("evcs0", "MaximumPower"); - private static final ChannelAddress EVCS0_SET_POWER_REQUEST = new ChannelAddress("evcs0", "SetChargePowerRequest"); - private static final ChannelAddress EVCS0_SET_CHARGE_POWER_LIMIT = new ChannelAddress("evcs0", - "SetChargePowerLimit"); - private static final ChannelAddress EVCS0_MAXIMUM_HARDWARE_POWER = new ChannelAddress("evcs0", - "MaximumHardwarePower"); - private static final ChannelAddress EVCS0_MINIMUM_HARDWARE_POWER = new ChannelAddress("evcs0", - "MinimumHardwarePower"); - private static final ChannelAddress EVCS0_CHARE_STATE = new ChannelAddress("evcs0", "ChargeState"); - - private static final ChannelAddress EVCS1_STATUS = new ChannelAddress("evcs1", "Status"); - private static final ChannelAddress EVCS1_CHARGE_POWER = new ChannelAddress("evcs1", "ChargePower"); - private static final ChannelAddress EVCS1_MAXIMUM_POWER = new ChannelAddress("evcs1", "MaximumPower"); - private static final ChannelAddress EVCS1_SET_POWER_REQUEST = new ChannelAddress("evcs1", "SetChargePowerRequest"); - private static final ChannelAddress EVCS1_SET_CHARGE_POWER_LIMIT = new ChannelAddress("evcs1", - "SetChargePowerLimit"); - private static final ChannelAddress EVCS1_MAXIMUM_HARDWARE_POWER = new ChannelAddress("evcs1", - "MaximumHardwarePower"); - private static final ChannelAddress EVCS1_MINIMUM_HARDWARE_POWER = new ChannelAddress("evcs1", - "MinimumHardwarePower"); - private static final ChannelAddress EVCS1_CHARGE_STATE = new ChannelAddress("evcs1", "ChargeState"); - - private static final ChannelAddress EVCS2_STATUS = new ChannelAddress("evcs2", "Status"); - private static final ChannelAddress EVCS2_SET_POWER_REQUEST = new ChannelAddress("evcs2", "SetChargePowerRequest"); - private static final ChannelAddress EVCS2_CHARGE_STATE = new ChannelAddress("evcs2", "ChargeState"); - - private static final ChannelAddress EVCS3_STATUS = new ChannelAddress("evcs3", "Status"); - private static final ChannelAddress EVCS3_SET_POWER_REQUEST = new ChannelAddress("evcs3", "SetChargePowerRequest"); - private static final ChannelAddress EVCS3_CHARGE_STATE = new ChannelAddress("evcs3", "ChargeState"); - - private static final ChannelAddress EVCS4_STATUS = new ChannelAddress("evcs4", "Status"); - private static final ChannelAddress EVCS4_SET_POWER_REQUEST = new ChannelAddress("evcs4", "SetChargePowerRequest"); - - private static final ChannelAddress EVCS5_STATUS = new ChannelAddress("evcs5", "Status"); - private static final ChannelAddress EVCS5_CHARGE_POWER = new ChannelAddress("evcs5", "ChargePower"); - private static final ChannelAddress EVCS5_SET_POWER_REQUEST = new ChannelAddress("evcs5", "SetChargePowerRequest"); - private static final ChannelAddress EVCS5_SET_CHARGE_POWER_LIMIT = new ChannelAddress("evcs5", - "SetChargePowerLimit"); - private static final ChannelAddress EVCS5_MAXIMUM_HARDWARE_POWER = new ChannelAddress("evcs5", - "MaximumHardwarePower"); - private static final ChannelAddress EVCS5_CHARGE_STATE = new ChannelAddress("evcs5", "ChargeState"); - @Test public void clusterMaximum_essActivePowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 21000)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 21000)) // .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, -5000) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 26000)) // + .input(ESS_ACTIVE_POWER, -5000) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 26000)) // .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 6000) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 15000)) // + .input(ESS_ACTIVE_POWER, 6000) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 15000)) // ; } @Test public void clusterMaximum_symmetricGridPowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(METER_GRID_ACTIVE_POWER, -6000) // - .input(METER_GRID_ACTIVE_POWER_L1, -2000) // - .input(METER_GRID_ACTIVE_POWER_L2, -2000) // - .input(METER_GRID_ACTIVE_POWER_L3, -2000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 27000)) // - .next(new TestCase() // - .input(METER_GRID_ACTIVE_POWER, 4500) // - .input(METER_GRID_ACTIVE_POWER_L1, 1500) // - .input(METER_GRID_ACTIVE_POWER_L2, 1500) // - .input(METER_GRID_ACTIVE_POWER_L3, 1500) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 16500)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("meter0", ACTIVE_POWER, -6000) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -2000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 27000)) // + .next(new TestCase() // + .input("meter0", ACTIVE_POWER, 4500) // + .input("meter0", ACTIVE_POWER_L1, 1500) // + .input("meter0", ACTIVE_POWER_L2, 1500) // + .input("meter0", ACTIVE_POWER_L3, 1500) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 16500)) // ; } @Test public void clusterMaximum_assymmetricGridPowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(METER_GRID_ACTIVE_POWER, -4000) // - .input(METER_GRID_ACTIVE_POWER_L1, -2000) // - .input(METER_GRID_ACTIVE_POWER_L2, -1000) // - .input(METER_GRID_ACTIVE_POWER_L3, -1000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 24000)) // - .next(new TestCase() // - .input(METER_GRID_ACTIVE_POWER, 4500) // - .input(METER_GRID_ACTIVE_POWER_L1, 3000) // - .input(METER_GRID_ACTIVE_POWER_L2, 1500) // - .input(METER_GRID_ACTIVE_POWER_L3, 500) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 12000)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("meter0", ACTIVE_POWER, -4000) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -1000) // + .input("meter0", ACTIVE_POWER_L3, -1000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 24000)) // + .next(new TestCase() // + .input("meter0", ACTIVE_POWER, 4500) // + .input("meter0", ACTIVE_POWER_L1, 3000) // + .input("meter0", ACTIVE_POWER_L2, 1500) // + .input("meter0", ACTIVE_POWER_L3, 500) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 12000)) // ; } @Test public void clusterMaximum_symmetricGridPower_essActivePowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(METER_GRID_ACTIVE_POWER, -6000) // - .input(METER_GRID_ACTIVE_POWER_L1, -2000) // - .input(METER_GRID_ACTIVE_POWER_L2, -2000) // - .input(METER_GRID_ACTIVE_POWER_L3, -2000) // - .input(SUM_ESS_ACTIVE_POWER, -6000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 33000)) // - .next(new TestCase() // - .input(METER_GRID_ACTIVE_POWER, 4500) // - .input(METER_GRID_ACTIVE_POWER_L1, 1500) // - .input(METER_GRID_ACTIVE_POWER_L2, 1500) // - .input(METER_GRID_ACTIVE_POWER_L3, 1500) // - .input(SUM_ESS_ACTIVE_POWER, 3000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 13500)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("meter0", ACTIVE_POWER, -6000) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -2000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input(ESS_ACTIVE_POWER, -6000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 33000)) // + .next(new TestCase() // + .input("meter0", ACTIVE_POWER, 4500) // + .input("meter0", ACTIVE_POWER_L1, 1500) // + .input("meter0", ACTIVE_POWER_L2, 1500) // + .input("meter0", ACTIVE_POWER_L3, 1500) // + .input(ESS_ACTIVE_POWER, 3000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 13500)) // ; } @Test public void clusterMaximum_essAllowedDischargePowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - var sut = new EvcsClusterPeakShavingImpl(); - var test = new ComponentTest(sut) // + new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // - .build()); // - test// - .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(METER_GRID_ACTIVE_POWER, -6000) // - .input(METER_GRID_ACTIVE_POWER_L1, -2000) // - .input(METER_GRID_ACTIVE_POWER_L2, -2000) // - .input(METER_GRID_ACTIVE_POWER_L3, -2000) // - .input(SUM_ESS_ACTIVE_POWER, -6000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 10000) // + .setEvcsIds("evcs0", "evcs1") // + .build()) // + .next(new TestCase() // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("meter0", ACTIVE_POWER, -6000) // + .input("meter0", ACTIVE_POWER_L1, -2000) // + .input("meter0", ACTIVE_POWER_L2, -2000) // + .input("meter0", ACTIVE_POWER_L3, -2000) // + .input(ESS_ACTIVE_POWER, -6000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 10000) // .onBeforeControllersCallbacks(() -> sut.run()) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 63000)) // - .next(new TestCase() // - .input(METER_GRID_ACTIVE_POWER, 4500) // - .input(METER_GRID_ACTIVE_POWER_L1, 1500) // - .input(METER_GRID_ACTIVE_POWER_L2, 1500) // - .input(METER_GRID_ACTIVE_POWER_L3, 1500) // - .input(SUM_ESS_ACTIVE_POWER, 3000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 20000) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 63000)) // + .next(new TestCase() // + .input("meter0", ACTIVE_POWER, 4500) // + .input("meter0", ACTIVE_POWER_L1, 1500) // + .input("meter0", ACTIVE_POWER_L2, 1500) // + .input("meter0", ACTIVE_POWER_L3, 1500) // + .input(ESS_ACTIVE_POWER, 3000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 20000) // .onBeforeControllersCallbacks(() -> sut.run()) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 43500)) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 43500)) // ; } @Test public void clusterDistribution_nothingToChargeTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING)) // - .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 0) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 0)) // - .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 21000) // - .input(METER_GRID_ACTIVE_POWER_L1, 7000) // - .input(METER_GRID_ACTIVE_POWER_L2, 7000) // - .input(METER_GRID_ACTIVE_POWER_L3, 7000) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 0) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 0)) // - .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 15000) // - .input(METER_GRID_ACTIVE_POWER_L1, 5000) // - .input(METER_GRID_ACTIVE_POWER_L2, 5000) // - .input(METER_GRID_ACTIVE_POWER_L3, 5000) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS0_MAXIMUM_POWER, 22000) // - .input(EVCS1_MAXIMUM_POWER, 22000) // - .input(EVCS0_MINIMUM_HARDWARE_POWER, 4500) // - .input(EVCS1_MINIMUM_HARDWARE_POWER, 4500) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 6000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 6000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 0)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING)) // + .next(new TestCase() // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // + .next(new TestCase() // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 21000) // + .input("meter0", ACTIVE_POWER_L1, 7000) // + .input("meter0", ACTIVE_POWER_L2, 7000) // + .input("meter0", ACTIVE_POWER_L3, 7000) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 0) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 0)) // + .next(new TestCase() // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 15000) // + .input("meter0", ACTIVE_POWER_L1, 5000) // + .input("meter0", ACTIVE_POWER_L2, 5000) // + .input("meter0", ACTIVE_POWER_L3, 5000) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", MAXIMUM_POWER, 22000) // + .input("evcs1", MAXIMUM_POWER, 22000) // + .input("evcs0", MINIMUM_HARDWARE_POWER, 4500) // + .input("evcs1", MINIMUM_HARDWARE_POWER, 4500) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 6000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 6000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 0)) // ; } @Test public void clusterDistribution_chargeTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1", "evcs2", "evcs3", "evcs4" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // @@ -376,237 +306,218 @@ public void clusterDistribution_chargeTest() throws Exception { .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1", "evcs2", "evcs3", "evcs4") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 30000) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS2_SET_POWER_REQUEST, 15000) // - .input(EVCS3_SET_POWER_REQUEST, 15000) // - .input(EVCS4_SET_POWER_REQUEST, 15000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING) // - .input(EVCS2_STATUS, Status.CHARGING) // - .input(EVCS3_STATUS, Status.CHARGING) // - .input(EVCS4_STATUS, Status.CHARGING) // - .input(EVCS0_MAXIMUM_POWER, null) // - .input(EVCS1_MAXIMUM_POWER, null) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_CHARGE_POWER, 0) // - .input(EVCS1_CHARGE_POWER, 0)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 30000) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs2", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs3", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs4", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING) // + .input("evcs2", STATUS, Status.CHARGING) // + .input("evcs3", STATUS, Status.CHARGING) // + .input("evcs4", STATUS, Status.CHARGING) // + .input("evcs0", MAXIMUM_POWER, null) // + .input("evcs1", MAXIMUM_POWER, null) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs1", ACTIVE_POWER, 0)) // ; } @Test public void clusterDistribution_chargeTest2() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // // TODO: The charge power of an EVCS has to be checked if it really charges // this amount) - .input(EVCS0_CHARGE_POWER, 11000) // - .input(EVCS1_CHARGE_POWER, 22000) // - .input(EVCS0_MAXIMUM_POWER, 22000) // - .input(EVCS1_MAXIMUM_POWER, 22000) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 54000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 15000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 15000)) // + .input("evcs0", ACTIVE_POWER, 11000) // + .input("evcs1", ACTIVE_POWER, 22000) // + .input("evcs0", MAXIMUM_POWER, 22000) // + .input("evcs1", MAXIMUM_POWER, 22000) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 54000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 15000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 15000)) // ; } @Test public void clusterDistribution_chargeTest_maximumHardwarePowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS0_CHARGE_POWER, 11000) // - .input(EVCS1_CHARGE_POWER, 0) // - .input(EVCS0_MAXIMUM_POWER, null) // - .input(EVCS1_MAXIMUM_POWER, null) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 11000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 11000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 32000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 11000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 11000)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", ACTIVE_POWER, 11000) // + .input("evcs1", ACTIVE_POWER, 0) // + .input("evcs0", MAXIMUM_POWER, null) // + .input("evcs1", MAXIMUM_POWER, null) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 11000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 11000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 32000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 11000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 11000)) // ; } @Test public void clusterDistribution_chargeTest_maximumPowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS0_CHARGE_POWER, 0) // - .input(EVCS1_CHARGE_POWER, 0) // - .input(EVCS0_MAXIMUM_POWER, 5000) // - .input(EVCS1_MAXIMUM_POWER, 9000) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_STATUS, Status.CHARGING) // - .input(EVCS1_STATUS, Status.CHARGING)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 15000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 15000)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs1", ACTIVE_POWER, 0) // + .input("evcs0", MAXIMUM_POWER, 5000) // + .input("evcs1", MAXIMUM_POWER, 9000) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", STATUS, Status.CHARGING) // + .input("evcs1", STATUS, Status.CHARGING)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 15000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 15000)) // ; } @Test public void clusterDistribution_chargeTest_minimumPowerTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addReference("addEvcs", EVCS0) // .addReference("addEvcs", EVCS1) // - .addReference("addEvcs", EVCS2) // - .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS0_SET_POWER_REQUEST, 15000) // - .input(EVCS1_SET_POWER_REQUEST, 15000) // - .input(EVCS0_CHARGE_POWER, 0) // - .input(EVCS1_CHARGE_POWER, 0) // - .input(EVCS0_MAXIMUM_POWER, 5000) // - .input(EVCS1_MAXIMUM_POWER, 9000) // - .input(EVCS0_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS1_MAXIMUM_HARDWARE_POWER, 22000) // - .input(EVCS0_MINIMUM_HARDWARE_POWER, 4500) // - .input(EVCS1_MINIMUM_HARDWARE_POWER, 4500) // - .input(EVCS0_STATUS, Status.READY_FOR_CHARGING) // - .input(EVCS1_STATUS, Status.READY_FOR_CHARGING)) // - .next(new TestCase() // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 4500) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 4500)) // - .next(new TestCase() // - .input(EVCS0_MINIMUM_HARDWARE_POWER, 9000) // - .input(EVCS1_MINIMUM_HARDWARE_POWER, 6900) // - .output(EVCS0_SET_CHARGE_POWER_LIMIT, 9000) // - .output(EVCS1_SET_CHARGE_POWER_LIMIT, 6900)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs0", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs1", SET_CHARGE_POWER_REQUEST, 15000) // + .input("evcs0", ACTIVE_POWER, 0) // + .input("evcs1", ACTIVE_POWER, 0) // + .input("evcs0", MAXIMUM_POWER, 5000) // + .input("evcs1", MAXIMUM_POWER, 9000) // + .input("evcs0", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs1", MAXIMUM_HARDWARE_POWER, 22000) // + .input("evcs0", MINIMUM_HARDWARE_POWER, 4500) // + .input("evcs1", MINIMUM_HARDWARE_POWER, 4500) // + .input("evcs0", STATUS, Status.READY_FOR_CHARGING) // + .input("evcs1", STATUS, Status.READY_FOR_CHARGING)) // + .next(new TestCase() // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 4500) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 4500)) // + .next(new TestCase() // + .input("evcs0", MINIMUM_HARDWARE_POWER, 9000) // + .input("evcs1", MINIMUM_HARDWARE_POWER, 6900) // + .output("evcs0", SET_CHARGE_POWER_LIMIT, 9000) // + .output("evcs1", SET_CHARGE_POWER_LIMIT, 6900)) // ; } @Test public void clusterDistribution_filterTest() throws Exception { - String[] evcsIds = { "evcs5" }; - - int initialPowerFromCluster = 4500; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // @@ -615,24 +526,25 @@ public void clusterDistribution_filterTest() throws Exception { .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs5") // .build()) // .next(new TestCase() // - .input(SUM_ESS_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER, 0) // - .input(METER_GRID_ACTIVE_POWER_L1, 0) // - .input(METER_GRID_ACTIVE_POWER_L2, 0) // - .input(METER_GRID_ACTIVE_POWER_L3, 0) // - .input(ESS_ALLOWED_DISCHARGE_POWER, 0) // - .input(EVCS5_SET_POWER_REQUEST, 22000) // - .input(EVCS5_CHARGE_POWER, 0) // - .input(EVCS5_CHARGE_STATE, ChargeState.NOT_CHARGING) // - .input(EVCS5_MAXIMUM_HARDWARE_POWER, 22080) // - .input(EVCS5_STATUS, Status.READY_FOR_CHARGING) // - .input(EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // + .input(ESS_ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER, 0) // + .input("meter0", ACTIVE_POWER_L1, 0) // + .input("meter0", ACTIVE_POWER_L2, 0) // + .input("meter0", ACTIVE_POWER_L3, 0) // + .input("ess0", ALLOWED_DISCHARGE_POWER, 0) // + .input("evcs5", SET_CHARGE_POWER_REQUEST, 22000) // + .input("evcs5", ACTIVE_POWER, 0) // + .input("evcs5", CHARGE_STATE, ChargeState.NOT_CHARGING) // + .input("evcs5", MAXIMUM_HARDWARE_POWER, 22080) // + .input("evcs5", STATUS, Status.READY_FOR_CHARGING) // + .input("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // .next(new TestCase() // // Cannot test charge states of evcs because the WriteHandler is not triggered // in a Cluster test. @@ -640,15 +552,13 @@ public void clusterDistribution_filterTest() throws Exception { // .output(evcs5ChargeState, ChargeState.INCREASING)) // // .output(evcs5ChargeState, ChargeState.INCREASING) // // .output(evcsClusterStatus, EvcsClusterStatus.INCREASING)) // - .output(EVCS_CLUSTER_MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // - .output(EVCS5_SET_CHARGE_POWER_LIMIT, initialPowerFromCluster) // + .output("evcsCluster0", MAXIMUM_POWER_TO_DISTRIBUTE, 21000) // + .output("evcs5", SET_CHARGE_POWER_LIMIT, 4500) // ); } @Test public void clusterStatusTest() throws Exception { - String[] evcsIds = { "evcs0", "evcs1", "evcs2", "evcs3" }; - new ComponentTest(new EvcsClusterPeakShavingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // @@ -657,50 +567,50 @@ public void clusterStatusTest() throws Exception { .addReference("addEvcs", EVCS1) // .addReference("addEvcs", EVCS2) // .addReference("addEvcs", EVCS3) // - .addReference("addEvcs", EVCS4) // .addReference("meter", METER) // .addReference("ess", ESS) // .activate(MyConfig.create() // - .setEssId(ESS.id()) // - .setMeterId(METER.id()) // + .setId("evcsCluster0") // + .setEssId("ess0") // + .setMeterId("meter0") // .setHardwarePowerLimit(HARDWARE_POWER_LIMIT_PER_PHASE) // - .setEvcsIds(evcsIds) // + .setEvcsIds("evcs0", "evcs1", "evcs2", "evcs3") // .build()) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.UNDEFINED) // - .input(EVCS1_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS2_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS3_CHARGE_STATE, ChargeState.UNDEFINED)) // + .input("evcs0", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs1", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs2", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs3", CHARGE_STATE, ChargeState.UNDEFINED)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.UNDEFINED)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.UNDEFINED)) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.INCREASING) // - .input(EVCS1_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS2_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS3_CHARGE_STATE, ChargeState.UNDEFINED)) // + .input("evcs0", CHARGE_STATE, ChargeState.INCREASING) // + .input("evcs1", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs2", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs3", CHARGE_STATE, ChargeState.UNDEFINED)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.INCREASING)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.INCREASING)) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(EVCS1_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS2_CHARGE_STATE, ChargeState.UNDEFINED) // - .input(EVCS3_CHARGE_STATE, ChargeState.UNDEFINED)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs1", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs2", CHARGE_STATE, ChargeState.UNDEFINED) // + .input("evcs3", CHARGE_STATE, ChargeState.UNDEFINED)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(EVCS1_CHARGE_STATE, ChargeState.DECREASING) // - .input(EVCS2_CHARGE_STATE, ChargeState.INCREASING) // - .input(EVCS3_CHARGE_STATE, ChargeState.UNDEFINED)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs1", CHARGE_STATE, ChargeState.DECREASING) // + .input("evcs2", CHARGE_STATE, ChargeState.INCREASING) // + .input("evcs3", CHARGE_STATE, ChargeState.UNDEFINED)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.DECREASING)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.DECREASING)) // .next(new TestCase() // - .input(EVCS0_CHARE_STATE, ChargeState.CHARGING) // - .input(EVCS1_CHARGE_STATE, ChargeState.CHARGING) // - .input(EVCS2_CHARGE_STATE, ChargeState.CHARGING) // - .input(EVCS3_CHARGE_STATE, ChargeState.CHARGING)) // + .input("evcs0", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs1", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs2", CHARGE_STATE, ChargeState.CHARGING) // + .input("evcs3", CHARGE_STATE, ChargeState.CHARGING)) // .next(new TestCase() // - .output(EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // + .output("evcsCluster0", EVCS_CLUSTER_STATUS, EvcsClusterStatus.REGULAR)) // ; } } diff --git a/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/MyConfig.java b/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/MyConfig.java index 8abf3801d3b..3185f98a2ae 100644 --- a/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/MyConfig.java +++ b/io.openems.edge.evcs.cluster/test/io/openems/edge/evcs/cluster/MyConfig.java @@ -8,12 +8,12 @@ public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { - private String id = "evcsCluster0"; - private boolean debugMode = false; - private int hardwarePowerLimitPerPhase = 7000; - private String[] evcsIds = { "evcs0", "evcs1" }; - private String essId = "ess0"; - private String meterId = "meter0"; + private String id; + private boolean debugMode; + private int hardwarePowerLimitPerPhase; + private String[] evcsIds; + private String essId; + private String meterId; private Builder() { } @@ -33,7 +33,7 @@ public Builder setHardwarePowerLimit(int hardwarePowerLimitPerPhase) { return this; } - public Builder setEvcsIds(String[] evcsIds) { + public Builder setEvcsIds(String... evcsIds) { this.evcsIds = evcsIds; return this; } diff --git a/io.openems.edge.evcs.dezony/bnd.bnd b/io.openems.edge.evcs.dezony/bnd.bnd index a0816a90967..53163b7f5a4 100644 --- a/io.openems.edge.evcs.dezony/bnd.bnd +++ b/io.openems.edge.evcs.dezony/bnd.bnd @@ -7,7 +7,8 @@ Bundle-Version: 1.0.0.${tstamp} ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyReadWorker.java b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyReadWorker.java index e148b80e681..176c289f0a5 100644 --- a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyReadWorker.java +++ b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/DezonyReadWorker.java @@ -1,5 +1,16 @@ package io.openems.edge.evcs.dezony; +import static io.openems.common.types.OpenemsType.DOUBLE; +import static io.openems.common.types.OpenemsType.STRING; +import static io.openems.common.utils.JsonUtils.getAsDouble; +import static io.openems.common.utils.JsonUtils.getAsFloat; +import static io.openems.common.utils.JsonUtils.getAsInt; +import static io.openems.common.utils.JsonUtils.getAsJsonObject; +import static io.openems.common.utils.JsonUtils.getAsLong; +import static io.openems.common.utils.JsonUtils.getAsShort; +import static io.openems.common.utils.JsonUtils.getAsString; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; + import java.util.Map; import java.util.function.Function; @@ -13,7 +24,6 @@ import io.openems.edge.common.channel.ChannelId; import io.openems.edge.common.type.TypeUtils; import io.openems.edge.evcs.api.Evcs; -import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.api.Status; public class DezonyReadWorker extends AbstractCycleWorker { @@ -54,29 +64,28 @@ protected void forever() throws OpenemsNamedException { * @param json Given raw data in JSON */ private void setEvcsChannelIds(JsonElement json) { - final var activeConsumptionEnergyArray = this.getArrayFromJson(json, "currDataPoint"); + final var energyArray = getArrayFromJson(json, "currDataPoint"); - this.parent._setActiveConsumptionEnergy(this.getValueByKey(activeConsumptionEnergyArray, "etotal")); - this.parent._setChargePower(this.getValueByKey(activeConsumptionEnergyArray, "ptotal")); - this.parent._setSetChargePowerLimit(this.getValueByKey(activeConsumptionEnergyArray, "curlhm") - * Phases.THREE_PHASE.getValue() * Evcs.DEFAULT_VOLTAGE); - this.parent._setPhases(this.calculatePhases(activeConsumptionEnergyArray)); + this.parent._setActiveProductionEnergy(getValueByKey(energyArray, "etotal")); + this.parent._setActivePower(getValueByKey(energyArray, "ptotal")); + this.parent._setSetChargePowerLimit( + getValueByKey(energyArray, "curlhm") * THREE_PHASE.getValue() * Evcs.DEFAULT_VOLTAGE); + this.parent._setPhases(this.calculatePhases(energyArray)); this.parent._setStatus(this.getStatus(json)); } private void setEnergySession(JsonElement json) { - final var energy = (Double) this.getValueFromJson(Evcs.ChannelId.ENERGY_SESSION, OpenemsType.DOUBLE, json, - value -> { - return value; - }, "metric", "energy"); + final var energy = (Double) getValueFromJson(Evcs.ChannelId.ENERGY_SESSION, DOUBLE, json, value -> { + return value; + }, "metric", "energy"); this.parent._setEnergySession(energy == null ? null : (int) Math.round(energy)); } private Status getStatus(JsonElement json) { - final var rawChargeStatus = (String) this.getValueFromJson(EvcsDezony.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, - json, value -> { - final String state = TypeUtils.getAsType(OpenemsType.STRING, value); + final var rawChargeStatus = (String) getValueFromJson(EvcsDezony.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, json, + value -> { + final String state = TypeUtils.getAsType(STRING, value); return state == null ? "" : state; }, "state"); @@ -104,10 +113,10 @@ private Status getStatus(JsonElement json) { return status; } - private Integer calculatePhases(JsonArray activeConsumptionEnergyArray) { - final var powerL1 = this.getValueByKey(activeConsumptionEnergyArray, "currl1") * Evcs.DEFAULT_VOLTAGE; - final var powerL2 = this.getValueByKey(activeConsumptionEnergyArray, "currl2") * Evcs.DEFAULT_VOLTAGE; - final var powerL3 = this.getValueByKey(activeConsumptionEnergyArray, "currl3") * Evcs.DEFAULT_VOLTAGE; + private Integer calculatePhases(JsonArray energyArray) { + final var powerL1 = getValueByKey(energyArray, "currl1") * Evcs.DEFAULT_VOLTAGE; + final var powerL2 = getValueByKey(energyArray, "currl2") * Evcs.DEFAULT_VOLTAGE; + final var powerL3 = getValueByKey(energyArray, "currl3") * Evcs.DEFAULT_VOLTAGE; final int maxPower = 900; final int minPower = 300; @@ -138,9 +147,9 @@ private Integer calculatePhases(JsonArray activeConsumptionEnergyArray) { return phases; } - private int getValueByKey(JsonArray activeConsumptionEnergyArray, String searchKey) { - for (var i = 0; i < activeConsumptionEnergyArray.size(); ++i) { - final var object = activeConsumptionEnergyArray.get(i).getAsJsonObject(); + private static int getValueByKey(JsonArray energyArray, String searchKey) { + for (var i = 0; i < energyArray.size(); ++i) { + final var object = energyArray.get(i).getAsJsonObject(); final var key = object.get("short"); if (key.getAsString().equals(searchKey)) { @@ -165,9 +174,9 @@ private int getValueByKey(JsonArray activeConsumptionEnergyArray, String searchK * @return Value of the last JsonElement by running through the specified JSON * path. */ - private Object getValueFromJson(ChannelId channelId, JsonElement json, Function converter, + private static Object getValueFromJson(ChannelId channelId, JsonElement json, Function converter, String... jsonPaths) { - return this.getValueFromJson(channelId, null, json, converter, jsonPaths); + return getValueFromJson(channelId, null, json, converter, jsonPaths); } /** @@ -185,7 +194,7 @@ private Object getValueFromJson(ChannelId channelId, JsonElement json, Function< * @return Value of the last JsonElement by running through the specified JSON * path. */ - private Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeInRawJson, JsonElement json, + private static Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeInRawJson, JsonElement json, Function converter, String... jsonPaths) { var currentJsonElement = json; // Go through the whole jsonPath of the current channelId @@ -198,13 +207,13 @@ private Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeIn : divergentTypeInRawJson; // Last path element - var value = this.getJsonElementValue(currentJsonElement, openemsType, jsonPaths[i]); + var value = getJsonElementValue(currentJsonElement, openemsType, jsonPaths[i]); // Return the converted value return converter.apply(value); } // Not last path element - currentJsonElement = JsonUtils.getAsJsonObject(currentJsonElement, currentPathMember); + currentJsonElement = getAsJsonObject(currentJsonElement, currentPathMember); } catch (OpenemsNamedException e) { return null; } @@ -212,7 +221,7 @@ private Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeIn return null; } - private JsonArray getArrayFromJson(JsonElement json, String... jsonPaths) { + private static JsonArray getArrayFromJson(JsonElement json, String... jsonPaths) { var currentJsonElement = json; // Go through the whole jsonPath of the current channelId for (var i = 0; i < jsonPaths.length; i++) { @@ -222,7 +231,7 @@ private JsonArray getArrayFromJson(JsonElement json, String... jsonPaths) { return JsonUtils.getAsJsonArray(currentJsonElement, jsonPaths[i]); } // Not last path element - currentJsonElement = JsonUtils.getAsJsonObject(currentJsonElement, currentPathMember); + currentJsonElement = getAsJsonObject(currentJsonElement, currentPathMember); } catch (OpenemsNamedException e) { return null; } @@ -239,23 +248,17 @@ private JsonArray getArrayFromJson(JsonElement json, String... jsonPaths) { * @return Value in the required type. * @throws OpenemsNamedException Failed to get the value. */ - private Object getJsonElementValue(JsonElement jsonElement, OpenemsType openemsType, String memberName) + private static Object getJsonElementValue(JsonElement jsonElement, OpenemsType openemsType, String memberName) throws OpenemsNamedException { + // NOTE: could have used JsonUtils.getAsType() return switch (openemsType) { - case BOOLEAN: - yield JsonUtils.getAsInt(jsonElement, memberName) == 1; - case DOUBLE: - yield JsonUtils.getAsDouble(jsonElement, memberName); - case FLOAT: - yield JsonUtils.getAsFloat(jsonElement, memberName); - case INTEGER: - yield JsonUtils.getAsInt(jsonElement, memberName); - case LONG: - yield JsonUtils.getAsLong(jsonElement, memberName); - case SHORT: - yield JsonUtils.getAsShort(jsonElement, memberName); - case STRING: - yield JsonUtils.getAsString(jsonElement, memberName); + case BOOLEAN -> getAsInt(jsonElement, memberName) == 1; + case DOUBLE -> getAsDouble(jsonElement, memberName); + case FLOAT -> getAsFloat(jsonElement, memberName); + case INTEGER -> getAsInt(jsonElement, memberName); + case LONG -> getAsLong(jsonElement, memberName); + case SHORT -> getAsShort(jsonElement, memberName); + case STRING -> getAsString(jsonElement, memberName); }; } } diff --git a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/EvcsDezonyImpl.java b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/EvcsDezonyImpl.java index e26fb8a168d..8aa67bfb7cf 100644 --- a/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/EvcsDezonyImpl.java +++ b/io.openems.edge.evcs.dezony/src/io/openems/edge/evcs/dezony/EvcsDezonyImpl.java @@ -1,5 +1,8 @@ package io.openems.edge.evcs.dezony; +import static io.openems.edge.evcs.api.ChargingType.AC; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; + import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -18,11 +21,10 @@ import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; -import io.openems.edge.evcs.api.ChargingType; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; -import io.openems.edge.evcs.api.Phases; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -35,7 +37,7 @@ EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) public class EvcsDezonyImpl extends AbstractManagedEvcsComponent - implements OpenemsComponent, EventHandler, EvcsDezony, Evcs, ManagedEvcs { + implements OpenemsComponent, EventHandler, EvcsDezony, Evcs, ManagedEvcs, ElectricityMeter { private final Logger log = LoggerFactory.getLogger(EvcsDezonyImpl.class); private final DezonyReadWorker readWorker = new DezonyReadWorker(this); @@ -48,7 +50,11 @@ public class EvcsDezonyImpl extends AbstractManagedEvcsComponent protected boolean masterEvcs = true; public EvcsDezonyImpl() { - super(OpenemsComponent.ChannelId.values(), Evcs.ChannelId.values(), ManagedEvcs.ChannelId.values(), + super(// + OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // + Evcs.ChannelId.values(), // + ManagedEvcs.ChannelId.values(), // EvcsDezony.ChannelId.values()); } @@ -57,11 +63,9 @@ private void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); this.config = config; - this._setChargingType(ChargingType.AC); - this._setFixedMinimumHardwarePower( - config.minHwCurrent() / 1000 * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue()); - this._setFixedMaximumHardwarePower( - config.maxHwCurrent() / 1000 * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue()); + this._setChargingType(AC); + this._setFixedMinimumHardwarePower(config.minHwCurrent() / 1000 * DEFAULT_VOLTAGE * THREE_PHASE.getValue()); + this._setFixedMaximumHardwarePower(config.maxHwCurrent() / 1000 * DEFAULT_VOLTAGE * THREE_PHASE.getValue()); this._setPowerPrecision(230); if (config.enabled()) { @@ -158,12 +162,12 @@ public int getMinimumTimeTillChargingLimitTaken() { @Override public int getConfiguredMinimumHardwarePower() { - return Math.round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + return Math.round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * THREE_PHASE.getValue(); } @Override public int getConfiguredMaximumHardwarePower() { - return Math.round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + return Math.round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * THREE_PHASE.getValue(); } @Override diff --git a/io.openems.edge.evcs.dezony/test/io/openems/edge/evcs/dezony/EvcsDezonyImplTest.java b/io.openems.edge.evcs.dezony/test/io/openems/edge/evcs/dezony/EvcsDezonyImplTest.java index 562d5b84ac9..ec51fd21e79 100644 --- a/io.openems.edge.evcs.dezony/test/io/openems/edge/evcs/dezony/EvcsDezonyImplTest.java +++ b/io.openems.edge.evcs.dezony/test/io/openems/edge/evcs/dezony/EvcsDezonyImplTest.java @@ -7,13 +7,11 @@ public class EvcsDezonyImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsDezonyImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setIp("192.168.50.88") // .setPort(5000) // .setMaxHwCurrent(32) // diff --git a/io.openems.edge.evcs.goe.chargerhome/bnd.bnd b/io.openems.edge.evcs.goe.chargerhome/bnd.bnd index 80e3fed5106..f78d7569e6f 100644 --- a/io.openems.edge.evcs.goe.chargerhome/bnd.bnd +++ b/io.openems.edge.evcs.goe.chargerhome/bnd.bnd @@ -7,7 +7,8 @@ Bundle-Version: 1.0.0.${tstamp} ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHome.java b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHome.java index 1b07248a7d4..4bfc3aabe2c 100644 --- a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHome.java +++ b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHome.java @@ -10,8 +10,9 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; -public interface EvcsGoeChargerHome extends ManagedEvcs, Evcs, OpenemsComponent, EventHandler { +public interface EvcsGoeChargerHome extends ManagedEvcs, Evcs, ElectricityMeter, OpenemsComponent, EventHandler { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { ALIAS(Doc.of(OpenemsType.STRING).text("A human-readable name of this Component")), @@ -21,13 +22,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { STATUS_GOE(Doc.of(Status.values()).text("Current state of the charging station")), ERROR(Doc.of(Errors.values()).text("")), CURR_USER(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIAMPERE).text("Current preset value of the user")), - VOLTAGE_L1(Doc.of(OpenemsType.INTEGER).unit(Unit.VOLT).text("Voltage on L1")), - VOLTAGE_L2(Doc.of(OpenemsType.INTEGER).unit(Unit.VOLT).text("Voltage on L2")), - VOLTAGE_L3(Doc.of(OpenemsType.INTEGER).unit(Unit.VOLT).text("Voltage on L3")), - CURRENT_L1(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIAMPERE).text("Current on L1")), - CURRENT_L2(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIAMPERE).text("Current on L2")), - CURRENT_L3(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIAMPERE).text("Current on L3")), - ACTUAL_POWER(Doc.of(OpenemsType.INTEGER).unit(Unit.MILLIWATT).text("Total real power")), ENERGY_TOTAL(Doc.of(OpenemsType.INTEGER).unit(Unit.CUMULATED_WATT_HOURS).text("Total power consumption")), CHARGINGSTATION_STATE_ERROR(Doc.of(Level.WARNING)); diff --git a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImpl.java b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImpl.java index 7e81a9fb3c8..c565c0cf558 100644 --- a/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImpl.java +++ b/io.openems.edge.evcs.goe.chargerhome/src/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImpl.java @@ -27,6 +27,7 @@ import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -62,6 +63,7 @@ public class EvcsGoeChargerHomeImpl extends AbstractManagedEvcsComponent public EvcsGoeChargerHomeImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // Evcs.ChannelId.values(), // EvcsGoeChargerHome.ChannelId.values() // @@ -128,18 +130,15 @@ public void handleEvent(Event event) { this.channel(EvcsGoeChargerHome.ChannelId.CURR_USER).setNextValue(this.activeCurrent); var nrg = JsonUtils.getAsJsonArray(json, "nrg"); - this.channel(EvcsGoeChargerHome.ChannelId.VOLTAGE_L1).setNextValue(JsonUtils.getAsInt(nrg, 0)); - this.channel(EvcsGoeChargerHome.ChannelId.VOLTAGE_L2).setNextValue(JsonUtils.getAsInt(nrg, 1)); - this.channel(EvcsGoeChargerHome.ChannelId.VOLTAGE_L3).setNextValue(JsonUtils.getAsInt(nrg, 2)); - this.channel(EvcsGoeChargerHome.ChannelId.CURRENT_L1) - .setNextValue(JsonUtils.getAsInt(nrg, 4) * 100); - this.channel(EvcsGoeChargerHome.ChannelId.CURRENT_L2) - .setNextValue(JsonUtils.getAsInt(nrg, 5) * 100); - this.channel(EvcsGoeChargerHome.ChannelId.CURRENT_L3) - .setNextValue(JsonUtils.getAsInt(nrg, 6) * 100); + this._setVoltageL1(JsonUtils.getAsInt(nrg, 0)); + this._setVoltageL2(JsonUtils.getAsInt(nrg, 1)); + this._setVoltageL3(JsonUtils.getAsInt(nrg, 2)); + this._setCurrentL1(JsonUtils.getAsInt(nrg, 4) * 100); + this._setCurrentL2(JsonUtils.getAsInt(nrg, 5) * 100); + this._setCurrentL3(JsonUtils.getAsInt(nrg, 6) * 100); var power = JsonUtils.getAsInt(nrg, 11); - this.channel(EvcsGoeChargerHome.ChannelId.ACTUAL_POWER).setNextValue(power * 10); - this.channel(Evcs.ChannelId.CHARGE_POWER).setNextValue(power * 10); + // TODO set ActivePowerL1/L2/L3 of ElectricityMeter + this._setActivePower(power * 10); // Hardware limits var cableCurrent = JsonUtils.getAsInt(json, "cbl") * 1000; @@ -157,6 +156,7 @@ public void handleEvent(Event event) { this._setPhases(phases); // Energy + // TODO set ActiveProductionEnergy this.channel(EvcsGoeChargerHome.ChannelId.ENERGY_TOTAL) .setNextValue(JsonUtils.getAsInt(json, "eto") * 100); this.channel(Evcs.ChannelId.ENERGY_SESSION) @@ -177,18 +177,13 @@ public void handleEvent(Event event) { } private Status convertGoeStatus(int status) { - switch (status) { - case 1: // ready for charging, car unplugged - return Status.NOT_READY_FOR_CHARGING; - case 2: // charging - return Status.CHARGING; - case 3: // waiting for car - return Status.READY_FOR_CHARGING; - case 4: // charging finished, car plugged - return Status.CHARGING_FINISHED; - default: - return Status.UNDEFINED; - } + return switch (status) { + case 1 -> Status.NOT_READY_FOR_CHARGING; // ready for charging, car unplugged + case 2 -> Status.CHARGING; // charging + case 3 -> Status.READY_FOR_CHARGING; // waiting for car + case 4 -> Status.CHARGING_FINISHED; // charging finished, car plugged + default -> Status.UNDEFINED; + }; } /** @@ -198,17 +193,12 @@ private Status convertGoeStatus(int status) { * @return amount of phases */ private int convertGoePhase(int phase) { - var phasen = (byte) phase & 0b00111000; - switch (phasen) { - case 8: // 0b00001000: Phase 1 is active - return 1; - case 24: // 0b00011000: Phase 1+2 is active - return 2; - case 56: // 0b00111000: Phase1-3 are active - return 3; - default: - return 0; - } + return switch ((byte) phase & 0b00111000) { + case 8 -> 1; // 0b00001000: Phase 1 is active + case 24 -> 2; // 0b00011000: Phase 1+2 is active + case 56 -> 3; // 0b00111000: Phase1-3 are active + default -> 0; // TODO illegal value! + }; } /** diff --git a/io.openems.edge.evcs.goe.chargerhome/test/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImplTest.java b/io.openems.edge.evcs.goe.chargerhome/test/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImplTest.java index ab8cbb6fd48..6ccf8f42662 100644 --- a/io.openems.edge.evcs.goe.chargerhome/test/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImplTest.java +++ b/io.openems.edge.evcs.goe.chargerhome/test/io/openems/edge/evcs/goe/chargerhome/EvcsGoeChargerHomeImplTest.java @@ -6,13 +6,11 @@ public class EvcsGoeChargerHomeImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsGoeChargerHomeImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setIp("192.168.50.88") // .setMaxHwCurrent(32) // .setMinHwCurrent(6) // diff --git a/io.openems.edge.evcs.hardybarth/bnd.bnd b/io.openems.edge.evcs.hardybarth/bnd.bnd index c7861a683c4..060569c9b40 100644 --- a/io.openems.edge.evcs.hardybarth/bnd.bnd +++ b/io.openems.edge.evcs.hardybarth/bnd.bnd @@ -6,8 +6,10 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ io.openems.common,\ + io.openems.edge.bridge.http,\ io.openems.edge.common,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java index 86633930c91..33465046fca 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/Config.java @@ -3,6 +3,8 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.evcs.api.PhaseRotation; + @ObjectClassDefinition(// name = "EVCS Hardy Barth", // description = "Implements the Hardy Barth - Salia electric vehicle charging station.") @@ -29,6 +31,9 @@ @AttributeDefinition(name = "Maximum hardware current", description = "Maximum current of the Charger in mA.", required = true) int maxHwCurrent() default 32000; + @AttributeDefinition(name = "Phase Rotation", description = "Apply standard or rotated wiring") + PhaseRotation phaseRotation() default PhaseRotation.L1_L2_L3; + String webconsole_configurationFactory_nameHint() default "EVCS Hardy Barth [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java index 60b9b2b7623..ad3cba3b5ac 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarth.java @@ -1,31 +1,41 @@ package io.openems.edge.evcs.hardybarth; +import static io.openems.common.channel.Level.WARNING; +import static io.openems.common.channel.Unit.AMPERE; +import static io.openems.common.channel.Unit.CUMULATED_WATT_HOURS; +import static io.openems.common.types.OpenemsType.BOOLEAN; +import static io.openems.common.types.OpenemsType.DOUBLE; +import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.common.types.OpenemsType.LONG; +import static io.openems.common.types.OpenemsType.STRING; + import java.util.function.Function; -import io.openems.common.channel.Level; -import io.openems.common.channel.Unit; -import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.BooleanDoc; import io.openems.edge.common.channel.Doc; -import io.openems.edge.common.type.TypeUtils; public interface EvcsHardyBarth { - public static final double SCALE_FACTOR_MINUS_1 = 0.1; + public static final float SCALE_FACTOR_MINUS_1 = 0.1F; public enum ChannelId implements io.openems.edge.common.channel.ChannelId { // TODO: Correct Type & Unit (Waiting for Manufacturer instructions) // EVSE - RAW_EVSE_GRID_CURRENT_LIMIT(Doc.of(OpenemsType.INTEGER).unit(Unit.AMPERE), "secc", "port0", "ci", "evse", // - "basic", "grid_current_limit", "actual"), // - RAW_PHASE_COUNT(Doc.of(OpenemsType.INTEGER), "secc", "port0", "ci", "evse", "basic", "phase_count"), // + RAW_EVSE_GRID_CURRENT_LIMIT(Doc.of(INTEGER) // + .unit(AMPERE), // + "secc", "port0", "ci", "evse", "basic", "grid_current_limit", "actual"), // + RAW_PHASE_COUNT(Doc.of(INTEGER), // + "secc", "port0", "ci", "evse", "basic", "phase_count"), // // CHARGE - RAW_CHARGE_STATUS_PLUG(Doc.of(OpenemsType.STRING), "secc", "port0", "ci", "charge", "plug", "status"), // - RAW_CHARGE_STATUS_CONTACTOR(Doc.of(OpenemsType.STRING), "secc", "port0", "ci", "charge", "contactor", "status"), // - RAW_CHARGE_STATUS_PWM(Doc.of(OpenemsType.STRING), "secc", "port0", "ci", "charge", "pwm", "status"), // + RAW_CHARGE_STATUS_PLUG(Doc.of(STRING), // + "secc", "port0", "ci", "charge", "plug", "status"), // + RAW_CHARGE_STATUS_CONTACTOR(Doc.of(STRING), // + "secc", "port0", "ci", "charge", "contactor", "status"), // + RAW_CHARGE_STATUS_PWM(Doc.of(STRING), // + "secc", "port0", "ci", "charge", "pwm", "status"), // /** * States of the Hardy Barth. * @@ -39,122 +49,141 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { *

  • F = Failure * */ - RAW_CHARGE_STATUS_CHARGEPOINT(Doc.of(OpenemsType.STRING), "secc", "port0", "ci", "charge", "cp", "status"), // + RAW_CHARGE_STATUS_CHARGEPOINT(Doc.of(STRING), // + "secc", "port0", "ci", "charge", "cp", "status"), // // SALIA - RAW_SALIA_CHARGE_MODE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "chargemode"), // - RAW_SALIA_CHANGE_METER(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "changemeter"), // - RAW_SALIA_AUTHMODE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "authmode"), // - RAW_SALIA_FIRMWARESTATE(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "firmwarestate"), // - RAW_SALIA_FIRMWAREPROGRESS(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "firmwareprogress"), // - RAW_SALIA_PUBLISH(Doc.of(OpenemsType.STRING), "secc", "port0", "salia", "publish"), // + RAW_SALIA_CHARGE_MODE(Doc.of(STRING), // + "secc", "port0", "salia", "chargemode"), // + RAW_SALIA_CHANGE_METER(Doc.of(STRING), // + "secc", "port0", "salia", "changemeter"), // + RAW_SALIA_AUTHMODE(Doc.of(STRING), // + "secc", "port0", "salia", "authmode"), // + RAW_SALIA_FIRMWARESTATE(Doc.of(STRING), // + "secc", "port0", "salia", "firmwarestate"), // + RAW_SALIA_FIRMWAREPROGRESS(Doc.of(STRING), // + "secc", "port0", "salia", "firmwareprogress"), // + RAW_SALIA_PUBLISH(Doc.of(STRING), // + "secc", "port0", "salia", "publish"), // // SESSION - RAW_SESSION_STATUS_AUTHORIZATION(Doc.of(OpenemsType.STRING), "secc", "port0", "session", - "authorization_status"), // - RAW_SESSION_SLAC_STARTED(Doc.of(OpenemsType.STRING), "secc", "port0", "session", "slac_started"), // - RAW_SESSION_AUTHORIZATION_METHOD(Doc.of(OpenemsType.STRING), "secc", "port0", "session", - "authorization_method"), // + RAW_SESSION_STATUS_AUTHORIZATION(Doc.of(STRING), // + "secc", "port0", "session", "authorization_status"), // + RAW_SESSION_SLAC_STARTED(Doc.of(STRING), // + "secc", "port0", "session", "slac_started"), // + RAW_SESSION_AUTHORIZATION_METHOD(Doc.of(STRING), // + "secc", "port0", "session", "authorization_method"), // // CONTACTOR - RAW_CONTACTOR_HLC_TARGET(Doc.of(OpenemsType.STRING), "secc", "port0", "contactor", "state", "hlc_target"), // - RAW_CONTACTOR_ACTUAL(Doc.of(OpenemsType.STRING), "secc", "port0", "contactor", "state", "actual"), // - RAW_CONTACTOR_TARGET(Doc.of(OpenemsType.STRING), "secc", "port0", "contactor", "state", "target"), // - RAW_CONTACTOR_ERROR(Doc.of(OpenemsType.STRING), "secc", "port0", "contactor", "error"), // + RAW_CONTACTOR_HLC_TARGET(Doc.of(STRING), // + "secc", "port0", "contactor", "state", "hlc_target"), // + RAW_CONTACTOR_ACTUAL(Doc.of(STRING), // + "secc", "port0", "contactor", "state", "actual"), // + RAW_CONTACTOR_TARGET(Doc.of(STRING), // + "secc", "port0", "contactor", "state", "target"), // + RAW_CONTACTOR_ERROR(Doc.of(STRING), // + "secc", "port0", "contactor", "error"), // // METERING - METER - RAW_METER_SERIALNUMBER(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "meter", "serialnumber"), // - RAW_METER_TYPE(Doc.of(OpenemsType.STRING), "secc", "port0", "metering", "meter", "type"), // - METER_NOT_AVAILABLE(Doc.of(Level.WARNING) // + RAW_METER_SERIALNUMBER(Doc.of(STRING), // + "secc", "port0", "metering", "meter", "serialnumber"), // + RAW_METER_TYPE(Doc.of(STRING), // + "secc", "port0", "metering", "meter", "type"), // + METER_NOT_AVAILABLE(Doc.of(WARNING) // .translationKey(EvcsHardyBarth.class, "noMeterAvailable")), // RAW_METER_AVAILABLE(new BooleanDoc()// .onChannelSetNextValue((hardyBarth, value) -> { var notAvailable = value.get() == null ? null : !value.get(); hardyBarth.channel(EvcsHardyBarth.ChannelId.METER_NOT_AVAILABLE).setNextValue(notAvailable); - }), "secc", "port0", "metering", "meter", "available"), // - - // METERING - POWER - RAW_ACTIVE_POWER_L1(Doc.of(OpenemsType.LONG).unit(Unit.WATT), value -> { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); - return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); - }, "secc", "port0", "metering", "power", "active", "ac", "l1", "actual"), // - - RAW_ACTIVE_POWER_L2(Doc.of(OpenemsType.LONG).unit(Unit.WATT), value -> { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); - return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); - }, "secc", "port0", "metering", "power", "active", "ac", "l2", "actual"), // - - RAW_ACTIVE_POWER_L3(Doc.of(OpenemsType.LONG).unit(Unit.WATT), value -> { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); - return TypeUtils.getAsType(OpenemsType.LONG, TypeUtils.multiply(doubleValue, SCALE_FACTOR_MINUS_1)); - }, "secc", "port0", "metering", "power", "active", "ac", "l3", "actual"), // - - // METERING - CURRENT - RAW_ACTIVE_CURRENT_L1(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", - "ac", "l1", "actual"), // - RAW_ACTIVE_CURRENT_L2(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", - "ac", "l2", "actual"), // - RAW_ACTIVE_CURRENT_L3(Doc.of(OpenemsType.LONG).unit(Unit.MILLIAMPERE), "secc", "port0", "metering", "current", - "ac", "l3", "actual"), // + }), // + "secc", "port0", "metering", "meter", "available"), // // METERING - ENERGY - RAW_ACTIVE_ENERGY_TOTAL(Doc.of(OpenemsType.DOUBLE).unit(Unit.CUMULATED_WATT_HOURS), "secc", "port0", "metering", - "energy", "active_total", "actual"), // - RAW_ACTIVE_ENERGY_EXPORT(Doc.of(OpenemsType.DOUBLE).unit(Unit.CUMULATED_WATT_HOURS), "secc", "port0", - "metering", "energy", "active_export", "actual"), // + RAW_ACTIVE_ENERGY_TOTAL(Doc.of(DOUBLE) // + .unit(CUMULATED_WATT_HOURS), // + "secc", "port0", "metering", "energy", "active_total", "actual"), // + RAW_ACTIVE_ENERGY_EXPORT(Doc.of(DOUBLE) // + .unit(CUMULATED_WATT_HOURS), // + "secc", "port0", "metering", "energy", "active_export", "actual"), // // EMERGENCY SHUTDOWN - RAW_EMERGENCY_SHUTDOWN(Doc.of(OpenemsType.STRING), "secc", "port0", "emergency_shutdown"), // + RAW_EMERGENCY_SHUTDOWN(Doc.of(STRING), // + "secc", "port0", "emergency_shutdown"), // // RCD - RAW_RCD_AVAILABLE(Doc.of(OpenemsType.BOOLEAN), "secc", "port0", "rcd", "recloser", "available"), // + RAW_RCD_AVAILABLE(Doc.of(BOOLEAN), // + "secc", "port0", "rcd", "recloser", "available"), // // PLUG LOCK - RAW_PLUG_LOCK_STATE_ACTUAL(Doc.of(OpenemsType.STRING), "secc", "port0", "plug_lock", "state", "actual"), // - RAW_PLUG_LOCK_STATE_TARGET(Doc.of(OpenemsType.STRING), "secc", "port0", "plug_lock", "state", "target"), // - RAW_PLUG_LOCK_ERROR(Doc.of(OpenemsType.STRING), "secc", "port0", "plug_lock", "error"), // + RAW_PLUG_LOCK_STATE_ACTUAL(Doc.of(STRING), // + "secc", "port0", "plug_lock", "state", "actual"), // + RAW_PLUG_LOCK_STATE_TARGET(Doc.of(STRING), // + "secc", "port0", "plug_lock", "state", "target"), // + RAW_PLUG_LOCK_ERROR(Doc.of(STRING), // + "secc", "port0", "plug_lock", "error"), // // CHARGE POINT - RAW_CP_STATE(Doc.of(OpenemsType.STRING), "secc", "port0", "cp", "state"), // + RAW_CP_STATE(Doc.of(STRING), // + "secc", "port0", "cp", "state"), // // DIODE PRESENT - RAW_DIODE_PRESENT(Doc.of(OpenemsType.STRING), "secc", "port0", "diode_present"), // + RAW_DIODE_PRESENT(Doc.of(STRING), // + "secc", "port0", "diode_present"), // // CABLE CURRENT LIMIT - RAW_CABLE_CURRENT_LIMIT(Doc.of(OpenemsType.STRING), "secc", "port0", "cable_current_limit"), // + RAW_CABLE_CURRENT_LIMIT(Doc.of(STRING), // + "secc", "port0", "cable_current_limit"), // // VENTILATION - RAW_VENTILATION_STATE_ACTUAL(Doc.of(OpenemsType.STRING), "secc", "port0", "ventilation", "state", "actual"), // - RAW_VENTILATION_STATE_TARGET(Doc.of(OpenemsType.STRING), "secc", "port0", "ventilation", "state", "target"), // - RAW_VENTILATION_AVAILABLE(Doc.of(OpenemsType.BOOLEAN), "secc", "port0", "ventilation", "available"), // + RAW_VENTILATION_STATE_ACTUAL(Doc.of(STRING), // + "secc", "port0", "ventilation", "state", "actual"), // + RAW_VENTILATION_STATE_TARGET(Doc.of(STRING), // + "secc", "port0", "ventilation", "state", "target"), // + RAW_VENTILATION_AVAILABLE(Doc.of(BOOLEAN), // + "secc", "port0", "ventilation", "available"), // // EV - PRESENT - RAW_EV_PRESENT(Doc.of(OpenemsType.STRING), "secc", "port0", "ev_present"), // + RAW_EV_PRESENT(Doc.of(STRING), // + "secc", "port0", "ev_present"), // // CHARGING - RAW_CHARGING(Doc.of(OpenemsType.STRING), "secc", "port0", "charging"), // + RAW_CHARGING(Doc.of(STRING), // + "secc", "port0", "charging"), // // RFID - RAW_RFID_AUTHORIZEREQ(Doc.of(OpenemsType.STRING), "secc", "port0", "rfid", "authorizereq"), // - RAW_RFID_AVAILABLE(Doc.of(OpenemsType.BOOLEAN), "secc", "port0", "rfid", "available"), // + RAW_RFID_AUTHORIZEREQ(Doc.of(STRING), // + "secc", "port0", "rfid", "authorizereq"), // + RAW_RFID_AVAILABLE(Doc.of(BOOLEAN), // + "secc", "port0", "rfid", "available"), // // GRID CURRENT LIMIT - RAW_GRID_CURRENT_LIMIT(Doc.of(OpenemsType.STRING), "secc", "port0", "grid_current_limit"), // + RAW_GRID_CURRENT_LIMIT(Doc.of(STRING), // + "secc", "port0", "grid_current_limit"), // // SLAC ERROR - RAW_SLAC_ERROR(Doc.of(OpenemsType.STRING), "secc", "port0", "slac_error"), // + RAW_SLAC_ERROR(Doc.of(STRING), // + "secc", "port0", "slac_error"), // // DEVICE - RAW_DEVICE_PRODUCT(Doc.of(OpenemsType.STRING), "device", "product"), // - RAW_DEVICE_MODELNAME(Doc.of(OpenemsType.STRING), "device", "modelname"), // - RAW_DEVICE_HARDWARE_VERSION(Doc.of(OpenemsType.STRING), "device", "hardware_version"), // - RAW_DEVICE_SOFTWARE_VERSION(Doc.of(OpenemsType.STRING), "device", "software_version"), // - RAW_DEVICE_VCS_VERSION(Doc.of(OpenemsType.STRING), "device", "vcs_version"), // - RAW_DEVICE_HOSTNAME(Doc.of(OpenemsType.STRING), "device", "hostname"), // - RAW_DEVICE_MAC_ADDRESS(Doc.of(OpenemsType.STRING), "device", "mac_address"), // - RAW_DEVICE_SERIAL(Doc.of(OpenemsType.LONG), "device", "serial"), // - RAW_DEVICE_UUID(Doc.of(OpenemsType.STRING), "device", "uuid"), // + RAW_DEVICE_PRODUCT(Doc.of(STRING), // + "device", "product"), // + RAW_DEVICE_MODELNAME(Doc.of(STRING), // + "device", "modelname"), // + RAW_DEVICE_HARDWARE_VERSION(Doc.of(STRING), // + "device", "hardware_version"), // + RAW_DEVICE_SOFTWARE_VERSION(Doc.of(STRING), // + "device", "software_version"), // + RAW_DEVICE_VCS_VERSION(Doc.of(STRING), // + "device", "vcs_version"), // + RAW_DEVICE_HOSTNAME(Doc.of(STRING), // + "device", "hostname"), // + RAW_DEVICE_MAC_ADDRESS(Doc.of(STRING), // + "device", "mac_address"), // + RAW_DEVICE_SERIAL(Doc.of(LONG), // + "device", "serial"), // + RAW_DEVICE_UUID(Doc.of(STRING), // + "device", "uuid"), // ; private final Doc doc; diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImpl.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImpl.java index 3b77f4df677..4afe4696cd6 100644 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImpl.java +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImpl.java @@ -1,6 +1,17 @@ package io.openems.edge.evcs.hardybarth; +import static io.openems.edge.bridge.http.api.BridgeHttp.DEFAULT_CONNECT_TIMEOUT; +import static io.openems.edge.bridge.http.api.BridgeHttp.DEFAULT_READ_TIMEOUT; +import static io.openems.edge.bridge.http.api.HttpMethod.GET; +import static io.openems.edge.bridge.http.api.HttpMethod.PUT; +import static io.openems.edge.evcs.api.ChargingType.AC; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; +import static java.lang.Math.round; +import static java.util.Collections.emptyMap; + import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -19,16 +30,22 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.utils.JsonUtils; +import io.openems.common.types.HttpStatus; +import io.openems.edge.bridge.http.api.BridgeHttp; +import io.openems.edge.bridge.http.api.BridgeHttp.Endpoint; +import io.openems.edge.bridge.http.api.BridgeHttpFactory; +import io.openems.edge.bridge.http.api.HttpMethod; +import io.openems.edge.bridge.http.api.HttpResponse; import io.openems.edge.common.channel.StringReadChannel; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.event.EdgeEventConstants; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; -import io.openems.edge.evcs.api.ChargingType; +import io.openems.edge.evcs.api.DeprecatedEvcs; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; -import io.openems.edge.evcs.api.Phases; +import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -37,82 +54,113 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) @EventTopics({ // - EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE, // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) public class EvcsHardyBarthImpl extends AbstractManagedEvcsComponent - implements OpenemsComponent, EventHandler, EvcsHardyBarth, Evcs, ManagedEvcs { + implements OpenemsComponent, EventHandler, EvcsHardyBarth, Evcs, ManagedEvcs, DeprecatedEvcs, ElectricityMeter { - protected final Logger log = LoggerFactory.getLogger(EvcsHardyBarthImpl.class); + private final Logger log = LoggerFactory.getLogger(EvcsHardyBarthImpl.class); - @Reference - private EvcsPower evcsPower; + protected final HardyBarthReadUtils readUtils = new HardyBarthReadUtils(this); - /** API for main REST API functions. */ - protected HardyBarthApi api; - /** ReadWorker and WriteHandler: Reading and sending data to the EVCS. */ - private final HardyBarthReadWorker readWorker = new HardyBarthReadWorker(this); /** * Master EVCS is responsible for RFID authentication (Not implemented for now). */ protected boolean masterEvcs = true; - protected Config config; + private BridgeHttp httpBridge; + private Config config; + + @Reference + private EvcsPower evcsPower; + + @Reference + private BridgeHttpFactory httpBridgeFactory; public EvcsHardyBarthImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // - EvcsHardyBarth.ChannelId.values() // + EvcsHardyBarth.ChannelId.values(), // + DeprecatedEvcs.ChannelId.values() // ); + DeprecatedEvcs.copyToDeprecatedEvcsChannels(this); + ElectricityMeter.calculateSumCurrentFromPhases(this); + ElectricityMeter.calculateAverageVoltageFromPhases(this); } @Activate private void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); + // TODO stop here if not enabled this.config = config; - this._setChargingType(ChargingType.AC); + this._setChargingType(AC); this._setFixedMinimumHardwarePower(config.minHwCurrent() / 1000 * 3 * 230); this._setFixedMaximumHardwarePower(config.maxHwCurrent() / 1000 * 3 * 230); this._setPowerPrecision(230); - this._setPhases(Phases.THREE_PHASE); - - if (config.enabled()) { - this.api = new HardyBarthApi(config.ip(), this); + this._setPhases(THREE_PHASE); - // Reading the given values - this.readWorker.activate(config.id()); - this.readWorker.triggerNextRun(); - } + this.httpBridge = this.httpBridgeFactory.get(); + // formerly .setHeartbeat + // The internal heartbeat is currently too fast - it is not enough to write + // every second by default. We have to disable it to run the evcs + // properly. + // TODO: The manufacturer must be asked if it is possible to read the heartbeat + // status so that we can check if the heartbeat is really disabled and if the + // heartbeat time can be increased to be able to use this feature. + this.httpBridge.subscribeCycle(1, // + this.createEndpoint(PUT, "/api/secc", "{\"salia/heartbeat\":\"off\"}"), // + t -> this._setChargingstationCommunicationFailed(false), + t -> this._setChargingstationCommunicationFailed(true)); + this.httpBridge.subscribeCycle(1, // + this.createEndpoint(GET, "/api", null), // + t -> { + this.readUtils.handleGetApiCallResponse(t, config.phaseRotation()); + this._setChargingstationCommunicationFailed(false); + }, // + t -> this._setChargingstationCommunicationFailed(true)); } @Override @Deactivate protected void deactivate() { super.deactivate(); + this.httpBridgeFactory.unget(this.httpBridge); + this.httpBridge = null; + } - if (this.readWorker != null) { - this.readWorker.deactivate(); - } + private Endpoint getTargetEndpoint(int target) { + return this.createEndpoint(PUT, "/api/secc", "{\"salia/pausecharging\":\"" + target + "\"}"); + } + + private Endpoint createEndpoint(HttpMethod httpMethod, String url, String body) { + return createEndpoint(this.config.ip(), httpMethod, url, body); + } + + protected static Endpoint createEndpoint(String ip, HttpMethod httpMethod, String url, String body) { + return new Endpoint(// + new StringBuilder("http://").append(ip).append(url).toString(), // + httpMethod, DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, // + body, // + emptyMap()); } @Override - public void handleEvent(Event event) { + protected WriteHandler createWriteHandler() { + return new HardyBarthWriteHandler(this); + } + @Override + public void handleEvent(Event event) { if (!this.isEnabled()) { return; } super.handleEvent(event); switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: - - this.setManualMode(); - this.setHeartbeat(); - this.readWorker.triggerNextRun(); - - // TODO: intelligent firmware update - break; + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // + -> this.setManualMode(); } } @@ -125,39 +173,14 @@ public void handleEvent(Event event) { private void setManualMode() { StringReadChannel channelChargeMode = this.channel(EvcsHardyBarth.ChannelId.RAW_SALIA_CHARGE_MODE); Optional valueOpt = channelChargeMode.value().asOptional(); - if (valueOpt.isPresent()) { - if (!valueOpt.get().equals("manual")) { - // Set to manual mode - try { - this.debugLog("Setting HardyBarth to manual chargemode"); - JsonElement result = this.api.sendPutRequest("/api/secc", "salia/chargemode", "manual"); - this.debugLog(result.toString()); - } catch (OpenemsNamedException e) { - e.printStackTrace(); - } - } + if (valueOpt.map(t -> !t.equals("manual")).orElse(true)) { + return; } - } - - /** - * Set heartbeat. - * - *

    - * Sets the heartbeat to on or off. - */ - private void setHeartbeat() { - // The internal heartbeat is currently too fast - it is not enough to write - // every second by default. We have to disable it to run the evcs - // properly. - // TODO: The manufacturer must be asked if it is possible to read the heartbeat - // status so that we can check if the heartbeat is really disabled and if the - // heartbeat time can be increased to be able to use this feature. + this.debugLog("Setting HardyBarth to manual chargemode"); + this.httpBridge // + .requestJson(this.createEndpoint(PUT, "/api/secc", "{\"salia/chargemode\":\"manual\"}")) // + .thenAccept(t -> this.debugLog(t.toString())); - try { - this.api.sendPutRequest("/api/secc", "salia/heartbeat", "off"); - } catch (OpenemsNamedException e) { - e.printStackTrace(); - } } /** @@ -219,7 +242,6 @@ public boolean getConfiguredDebugMode() { @Override public boolean applyChargePowerLimit(int power) throws OpenemsNamedException { - // TODO: Use power precision to set valid power if it is used in UI part too // e.g. int precision = TypeUtils.getAsType(OpenemsType.INTEGER, // this.getPowerPrecision().orElse(230d)); @@ -227,7 +249,7 @@ public boolean applyChargePowerLimit(int power) throws OpenemsNamedException { // Convert it to ampere and apply hard limits int phases = this.getPhasesAsInt(); - Integer current = (int) Math.round(power / (double) phases / 230.0); + final var current = round(power / (float) phases / 230.F); return this.setTarget(current); } @@ -244,25 +266,25 @@ public boolean pauseChargeProcess() throws OpenemsNamedException { * @return boolean if the target was set * @throws OpenemsNamedException on error */ - private boolean setTarget(int current) throws OpenemsNamedException { - - JsonElement resultPause; + private boolean setTarget(int current) { + CompletableFuture> resultPause = null; if (current > 0) { // Send stop pause request - resultPause = this.api.sendPutRequest("/api/secc", "salia/pausecharging", "" + 0); + resultPause = this.httpBridge.requestJson(this.getTargetEndpoint(0)); } else { - // Send pause charging request - resultPause = this.api.sendPutRequest("/api/secc", "salia/pausecharging", "" + 1); + resultPause = this.httpBridge.requestJson(this.getTargetEndpoint(1)); this.debugLog("Setting HardyBarth " + this.alias() + " to pause"); } // Send charge power limit - JsonElement resultLimit = this.api.sendPutRequest("/api/secc", "grid_current_limit", "" + current); - - Optional resultLimitVal = JsonUtils.getAsOptionalString(resultLimit, "result"); - Optional resultPauseVal = JsonUtils.getAsOptionalString(resultPause, "result"); - - return resultLimitVal.orElse("").equals("ok") && resultPauseVal.orElse("").equals("ok"); + final var resultLimit = this.httpBridge.requestJson( + this.createEndpoint(PUT, "/api/secc", "{\"" + "grid_current_limit" + "\":\"" + current + "\"}")); + try { + return resultLimit.get().status().equals(HttpStatus.OK) && resultPause.get().status().equals(HttpStatus.OK); + } catch (InterruptedException | ExecutionException e) { + this.log.error("Unable to set EVCS Target"); + return false; + } } @Override @@ -277,12 +299,12 @@ public int getMinimumTimeTillChargingLimitTaken() { @Override public int getConfiguredMinimumHardwarePower() { - return Math.round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + return round(this.config.minHwCurrent() / 1000f) * DEFAULT_VOLTAGE * THREE_PHASE.getValue(); } @Override public int getConfiguredMaximumHardwarePower() { - return Math.round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); + return round(this.config.maxHwCurrent() / 1000f) * DEFAULT_VOLTAGE * THREE_PHASE.getValue(); } @Override diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthApi.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthApi.java deleted file mode 100644 index 4b088d2d5a3..00000000000 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthApi.java +++ /dev/null @@ -1,154 +0,0 @@ -package io.openems.edge.evcs.hardybarth; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.HttpURLConnection; -import java.net.URL; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.exceptions.OpenemsException; -import io.openems.common.utils.JsonUtils; - -/** - * Implements the Hardy Barth Api. - */ -public class HardyBarthApi { - - private final String baseUrl; - private final String authorizationHeader; - private final EvcsHardyBarthImpl hardyBarthImpl; - - public HardyBarthApi(String ip, EvcsHardyBarthImpl hardyBarthImpl) { - this.baseUrl = "http://" + ip; - this.authorizationHeader = "Basic "; - this.hardyBarthImpl = hardyBarthImpl; - } - - /** - * Sends a get request to the Hardy Barth. - * - * @param endpoint the REST Api endpoint - * @return a JsonObject or JsonArray - * @throws OpenemsNamedException on error - */ - public JsonElement sendGetRequest(String endpoint) throws OpenemsNamedException { - var putRequestFailed = false; - JsonObject result = null; - - try { - // Create URL like "http://192.168.8.101/api/" - var url = new URL(this.baseUrl + endpoint); - - // Open http url connection - var con = (HttpURLConnection) url.openConnection(); - - // Set general information - con.setRequestProperty("Authorization", this.authorizationHeader); - con.setRequestMethod("GET"); - con.setConnectTimeout(5000); - con.setReadTimeout(5000); - - // Read response - String body; - try (var in = new BufferedReader(new InputStreamReader(con.getInputStream()))) { - - // Read HTTP response - var content = new StringBuilder(); - String line; - while ((line = in.readLine()) != null) { - content.append(line); - content.append(System.lineSeparator()); - } - body = content.toString(); - } - - // Get response code - var status = con.getResponseCode(); - if (status >= 300) { - putRequestFailed = true; - throw new OpenemsException( - "Error while reading from Hardy Barth API. Response code: " + status + ". " + body); - } - putRequestFailed = false; - // Parse response to JSON - result = JsonUtils.parseToJsonObject(body); - } catch (OpenemsNamedException | IOException e) { - putRequestFailed = true; - } - - // Set state and return result - this.hardyBarthImpl._setChargingstationCommunicationFailed(putRequestFailed); - return result; - } - - /** - * Sends a get request to the Hardy Barth. - * - * @param endpoint the REST Api endpoint @return a JsonObject or - * JsonArray @throws OpenemsNamedException on error @throws - * @param key The key in the properties - * @param value The value of the key property - * @return A JsonObject - * @throws OpenemsNamedException on error - */ - public JsonObject sendPutRequest(String endpoint, String key, String value) throws OpenemsNamedException { - var putRequestFailed = false; - JsonObject result = null; - - try { - // Create URL like "http://192.168.8.101/api/" - var url = new URL(this.baseUrl + endpoint); - - // Open http url connection - var connection = (HttpURLConnection) url.openConnection(); - - // Set general information - connection.setRequestProperty("Authorization", this.authorizationHeader); - connection.setRequestMethod("PUT"); - connection.setDoOutput(true); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - - // Write "topic" and "value" on request properties - var osw = new OutputStreamWriter(connection.getOutputStream()); - osw.write("{\"" + key + "\":\"" + value + "\"}"); - osw.flush(); - osw.close(); - - String body; - try (var in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - - // Read HTTP response - var content = new StringBuilder(); - String line; - while ((line = in.readLine()) != null) { - content.append(line); - content.append(System.lineSeparator()); - } - body = content.toString(); - } - - // Get response code - var status = connection.getResponseCode(); - if ((status >= 300) && (status >= 0)) { - // Respond error status-code - putRequestFailed = true; - throw new OpenemsException( - "Error while reading from Hardy Barth API. Response code: " + status + ". " + body); - } - // Result OK - result = JsonUtils.parseToJsonObject(body); - } catch (IOException e) { - putRequestFailed = true; - } - - // Set state and return result - this.hardyBarthImpl._setChargingstationCommunicationFailed(putRequestFailed); - return result; - } -} diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadUtils.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadUtils.java new file mode 100644 index 00000000000..d20a8a44d04 --- /dev/null +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadUtils.java @@ -0,0 +1,272 @@ +package io.openems.edge.evcs.hardybarth; + +import static io.openems.common.types.OpenemsType.FLOAT; +import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.common.types.OpenemsType.LONG; +import static io.openems.common.types.OpenemsType.STRING; +import static io.openems.common.utils.JsonUtils.parseToJsonObject; +import static io.openems.edge.evcs.api.Evcs.evaluatePhaseCount; +import static io.openems.edge.evcs.hardybarth.EvcsHardyBarth.SCALE_FACTOR_MINUS_1; +import static java.lang.Math.round; + +import java.util.Optional; +import java.util.function.Function; + +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.OpenemsType; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.bridge.http.api.HttpResponse; +import io.openems.edge.common.type.TypeUtils; +import io.openems.edge.evcs.api.PhaseRotation; +import io.openems.edge.evcs.api.PhaseRotation.RotatedPhases; +import io.openems.edge.evcs.api.Status; + +public class HardyBarthReadUtils { + private final EvcsHardyBarthImpl parent; + + private int chargingFinishedCounter = 0; + private int errorCounter = 0; + + public HardyBarthReadUtils(EvcsHardyBarthImpl parent) { + this.parent = parent; + } + + /** + * Set the value for every Evcs.ChannelId. + * + * @param json given raw data in JSON + * @param phaseRotation the configured {@link PhaseRotation} + */ + protected void setEvcsChannelIds(JsonElement json, PhaseRotation phaseRotation) { + final var hb = this.parent; + + // Energy + hb._setEnergySession(getValueFromJson(STRING, json, // + value -> { + if (value == null) { + return null; + } + var chargedata = TypeUtils.getAsType(STRING, value).split("\\|"); + if (chargedata.length == 3) { + return round(TypeUtils.getAsType(FLOAT, chargedata[2]) * 1000); + } + return null; + }, "secc", "port0", "salia", "chargedata")); + hb._setActiveProductionEnergy(getValueFromJson(LONG, json, // + value -> TypeUtils.getAsType(LONG, value), // + "secc", "port0", "metering", "energy", "active_import", "actual")); + + // Current + final var currentL1 = getAsIntOrElse(json, 0, "secc", "port0", "metering", "current", "ac", "l1", "actual"); + final var currentL2 = getAsIntOrElse(json, 0, "secc", "port0", "metering", "current", "ac", "l2", "actual"); + final var currentL3 = getAsIntOrElse(json, 0, "secc", "port0", "metering", "current", "ac", "l3", "actual"); + + // Power + final var activePowerL1 = getAsInteger(json, SCALE_FACTOR_MINUS_1, // + "secc", "port0", "metering", "power", "active", "ac", "l1", "actual"); + final var activePowerL2 = getAsInteger(json, SCALE_FACTOR_MINUS_1, // + "secc", "port0", "metering", "power", "active", "ac", "l2", "actual"); + final var activePowerL3 = getAsInteger(json, SCALE_FACTOR_MINUS_1, // + "secc", "port0", "metering", "power", "active", "ac", "l3", "actual"); + + // Voltage + final var voltageL1 = activePowerL1 == null ? null : round(activePowerL1 * 1_000_000F / currentL1); + final var voltageL2 = activePowerL2 == null ? null : round(activePowerL2 * 1_000_000F / currentL2); + final var voltageL3 = activePowerL3 == null ? null : round(activePowerL3 * 1_000_000F / currentL3); + + var rp = RotatedPhases.from(phaseRotation, // + voltageL1, currentL1, activePowerL1, // + voltageL2, currentL2, activePowerL2, // + voltageL3, currentL3, activePowerL3); + hb._setVoltageL1(rp.voltageL1()); + hb._setVoltageL2(rp.voltageL2()); + hb._setVoltageL3(rp.voltageL3()); + hb._setCurrentL1(rp.currentL1()); + hb._setCurrentL2(rp.currentL2()); + hb._setCurrentL3(rp.currentL3()); + hb._setActivePowerL1(rp.activePowerL1()); + hb._setActivePowerL2(rp.activePowerL2()); + hb._setActivePowerL3(rp.activePowerL3()); + + // Phases: keep last value if no power value was given + var phases = evaluatePhaseCount(rp.activePowerL1(), rp.activePowerL2(), rp.activePowerL3()); + if (phases != null) { + hb._setPhases(phases); + this.parent.debugLog("Used phases: " + phases); + } + + // ACTIVE_POWER + final var activePower = Optional.ofNullable(getAsInteger(json, SCALE_FACTOR_MINUS_1, // + "secc", "port0", "metering", "power", "active_total", "actual")) // + .map(p -> p < 100 ? 0 : p) // Ignore the consumption of the charger itself + .orElse(null); + this.parent._setActivePower(activePower); + + // STATUS + var status = getValueFromJson(STRING, json, value -> { + var stringValue = TypeUtils.getAsType(STRING, value); + if (stringValue == null) { + this.errorCounter++; + this.parent.debugLog("Hardy Barth RAW_STATUS would be null! Raw value: " + value); + if (this.errorCounter > 3) { + return Status.ERROR; + } + return this.parent.getStatus(); + } + + Status rawStatus = switch (stringValue) { + case "A" -> Status.NOT_READY_FOR_CHARGING; + case "B" -> { + var tmpStatus = Status.READY_FOR_CHARGING; + + // Detect if the car is full + if (this.parent.getSetChargePowerLimit().orElse(0) >= this.parent.getMinimumHardwarePower().orElse(0) + && activePower <= 0) { + + if (this.chargingFinishedCounter >= 90) { + tmpStatus = Status.CHARGING_FINISHED; + } else { + this.chargingFinishedCounter++; + } + } else { + this.chargingFinishedCounter = 0; + + // Charging rejected because we are forcing to pause charging + if (this.parent.getSetChargePowerLimit().orElse(0) == 0) { + tmpStatus = Status.CHARGING_REJECTED; + } + } + yield tmpStatus; + } + case "C", "D" -> Status.CHARGING; + case "E", "F" -> { + this.errorCounter++; + this.parent.debugLog("Hardy Barth RAW_STATUS would be an error! Raw value: " + stringValue + + " - Error counter: " + this.errorCounter); + if (this.errorCounter > 3) { + yield Status.ERROR; + } + yield this.parent.getStatus(); + } + default -> { + this.parent.debugLog("State " + stringValue + " is not a valid state"); + yield Status.UNDEFINED; + } + }; + + if (!stringValue.equals("B")) { + this.chargingFinishedCounter = 0; + } + if (!stringValue.equals("E") || !stringValue.equals("F")) { + this.errorCounter = 0; + } + + return rawStatus; + }, "secc", "port0", "ci", "charge", "cp", "status"); + + this.parent._setStatus(status); + } + + private static Integer getAsInteger(JsonElement json, float scaleFactor, String... jsonPaths) { + return getValueFromJson(INTEGER, json, // + value -> value == null ? null // + : round(TypeUtils.getAsType(INTEGER, value) * scaleFactor), // + jsonPaths); + } + + private static int getAsIntOrElse(JsonElement json, int orElse, String... jsonPaths) { + var result = getValueFromJson(INTEGER, json, // + value -> TypeUtils.getAsType(INTEGER, value), // + jsonPaths); + return result == null // + ? orElse // + : result; + } + + /** + * Get the last JSON element and it's value, by running through the given + * jsonPath. + * + * @param openemsType the {@link OpenemsType}s + * @param json Raw JsonElement. + * @param converter Converter, to convert the raw JSON value into a proper + * Channel. + * @param jsonPaths Whole JSON path, where the JsonElement for the given + * channel is located. + * @param return type + * @return Value of the last JsonElement by running through the specified JSON + * path. + */ + private static T getValueFromJson(OpenemsType openemsType, JsonElement json, Function converter, + String... jsonPaths) { + + var currentJsonElement = json; + // Go through the whole jsonPath of the current channelId + for (var i = 0; i < jsonPaths.length; i++) { + var currentPathMember = jsonPaths[i]; + try { + if (i == jsonPaths.length - 1) { + // Last path element + var value = getJsonElementValue(currentJsonElement, openemsType, jsonPaths[i]); + + // Return the converted value + return converter.apply(value); + } + // Not last path element + currentJsonElement = JsonUtils.getAsJsonObject(currentJsonElement, currentPathMember); + } catch (OpenemsNamedException e) { + return null; + } + } + return null; + } + + /** + * Handles a Response froma http call for the endpoint /api GET. + * + * @param response the {@link HttpResponse} to be handled + * @param phaseRotation the configured {@link PhaseRotation} + * @throws OpenemsNamedException when json can not be parsed + */ + public void handleGetApiCallResponse(HttpResponse response, PhaseRotation phaseRotation) + throws OpenemsNamedException { + final var json = parseToJsonObject(response.data()); + for (var channelId : EvcsHardyBarth.ChannelId.values()) { + var jsonPaths = channelId.getJsonPaths(); + var value = getValueFromJson(channelId.doc().getType(), json, channelId.converter, jsonPaths); + + // Set the channel-value + this.parent.channel(channelId).setNextValue(value); + + if (channelId.equals(EvcsHardyBarth.ChannelId.RAW_SALIA_PUBLISH)) { + this.parent.masterEvcs = false; + } + + } + this.setEvcsChannelIds(json, phaseRotation); + } + + /** + * Get Value of the given JsonElement in the required type. + * + * @param jsonElement Element as JSON. + * @param openemsType Required type. + * @param memberName Member name of the JSON Element. + * @return Value in the required type. + * @throws OpenemsNamedException Failed to get the value. + */ + private static Object getJsonElementValue(JsonElement jsonElement, OpenemsType openemsType, String memberName) + throws OpenemsNamedException { + return switch (openemsType) { + case BOOLEAN -> JsonUtils.getAsInt(jsonElement, memberName) == 1; + case DOUBLE -> JsonUtils.getAsDouble(jsonElement, memberName); + case FLOAT -> JsonUtils.getAsFloat(jsonElement, memberName); + case INTEGER -> JsonUtils.getAsInt(jsonElement, memberName); + case LONG -> JsonUtils.getAsLong(jsonElement, memberName); + case SHORT -> JsonUtils.getAsShort(jsonElement, memberName); + case STRING -> JsonUtils.getAsString(jsonElement, memberName); + }; + } +} \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java deleted file mode 100644 index c24f974ba21..00000000000 --- a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthReadWorker.java +++ /dev/null @@ -1,318 +0,0 @@ -package io.openems.edge.evcs.hardybarth; - -import java.util.function.Function; - -import com.google.gson.JsonElement; - -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.types.OpenemsType; -import io.openems.common.utils.JsonUtils; -import io.openems.common.worker.AbstractCycleWorker; -import io.openems.edge.common.channel.ChannelId; -import io.openems.edge.common.type.TypeUtils; -import io.openems.edge.evcs.api.Evcs; -import io.openems.edge.evcs.api.Status; - -public class HardyBarthReadWorker extends AbstractCycleWorker { - - private final EvcsHardyBarthImpl parent; - private int chargingFinishedCounter = 0; - private int errorCounter = 0; - - public HardyBarthReadWorker(EvcsHardyBarthImpl parent) { - this.parent = parent; - } - - @Override - protected void forever() throws OpenemsNamedException { - - // TODO: Read separate JSON files - // - separate configuration -> this.api.sendGetRequest("/saliaconf.json"); - // e.g. min & max hardware power - // - customeredit values -> this.api.sendGetRequest("/customer.json"); - // - rfidtags -> this.api.sendGetRequest("/rfidtags.json"); - // - chargelogs -> this.api.sendGetRequest("/chargelogs.json"); - // - Read separate saliaconf.json and set minimum and maximum dynamically - - var json = this.parent.api.sendGetRequest("/api"); - if (json == null) { - return; - } - - // Set value for every HardyBarth.ChannelId - for (EvcsHardyBarth.ChannelId channelId : EvcsHardyBarth.ChannelId.values()) { - var jsonPaths = channelId.getJsonPaths(); - var value = this.getValueFromJson(channelId, json, channelId.converter, jsonPaths); - - // Set the channel-value - this.parent.channel(channelId).setNextValue(value); - - if (channelId.equals(EvcsHardyBarth.ChannelId.RAW_SALIA_PUBLISH)) { - this.parent.masterEvcs = false; - } - } - - // Set value for every Evcs.ChannelId - this.setEvcsChannelIds(json); - } - - /** - * Set the value for every Evcs.ChannelId. - * - * @param json Given raw data in JSON - */ - private void setEvcsChannelIds(JsonElement json) { - - // ENERGY_SESSION - var energy = (Double) this.getValueFromJson(Evcs.ChannelId.ENERGY_SESSION, OpenemsType.STRING, json, value -> { - if (value == null) { - return null; - } - Double rawEnergy = null; - String[] chargedata = value.toString().split("\\|"); - if (chargedata.length == 3) { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, chargedata[2]); - rawEnergy = doubleValue * 1000; - } - return rawEnergy; - - }, "secc", "port0", "salia", "chargedata"); - this.parent._setEnergySession(energy == null ? null : (int) Math.round(energy)); - - // ACTIVE_CONSUMPTION_ENERGY - var activeConsumptionEnergy = (Long) this.getValueFromJson(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY, json, - value -> { - Double doubleValue = TypeUtils.getAsType(OpenemsType.DOUBLE, value); - return TypeUtils.getAsType(OpenemsType.LONG, doubleValue); - - }, "secc", "port0", "metering", "energy", "active_import", "actual"); // - this.parent._setActiveConsumptionEnergy(activeConsumptionEnergy); - - // PHASES - var powerL1 = (Long) this.getValueForChannel(EvcsHardyBarth.ChannelId.RAW_ACTIVE_POWER_L1, json); - var powerL2 = (Long) this.getValueForChannel(EvcsHardyBarth.ChannelId.RAW_ACTIVE_POWER_L2, json); - var powerL3 = (Long) this.getValueForChannel(EvcsHardyBarth.ChannelId.RAW_ACTIVE_POWER_L3, json); - - // TODO: Handle phases, having each phase value in the Nature - // Keep last value if no power value was given - var phases = this.parent.getPhasesAsInt(); - if (powerL1 != null && powerL2 != null && powerL3 != null) { - - var sum = powerL1 + powerL2 + powerL3; - - if (sum > 300) { - phases = 0; - - if (powerL1 >= 100) { - phases += 1; - } - if (powerL2 >= 100) { - phases += 1; - } - if (powerL3 >= 100) { - phases += 1; - } - } - } - this.parent._setPhases(phases); - this.parent.debugLog("Used phases: " + phases); - - // CHARGE_POWER - var chargePowerLong = (Long) this.getValueFromJson(Evcs.ChannelId.CHARGE_POWER, json, value -> { - Integer integerValue = TypeUtils.getAsType(OpenemsType.INTEGER, value); - if (integerValue == null) { - return null; - } - - long activePower = Math.round(integerValue * EvcsHardyBarth.SCALE_FACTOR_MINUS_1); - - // Ignore the consumption of the charger itself - return activePower < 100 ? 0 : activePower; - }, "secc", "port0", "metering", "power", "active_total", "actual"); - - this.parent._setChargePower(chargePowerLong == null ? null : chargePowerLong.intValue()); - - // STATUS - var status = (Status) this.getValueFromJson(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, json, - value -> { - - String stringValue = TypeUtils.getAsType(OpenemsType.STRING, value); - if (stringValue == null) { - this.errorCounter++; - this.parent.debugLog("Hardy Barth RAW_STATUS would be null! Raw value: " + value); - if (this.errorCounter > 3) { - return Status.ERROR; - } - return this.parent.getStatus(); - } - - Status rawStatus = switch (stringValue) { - case "A" -> Status.NOT_READY_FOR_CHARGING; - case "B" -> { - var tmpStatus = Status.READY_FOR_CHARGING; - - // Detect if the car is full - int chargePower = chargePowerLong == null ? 0 : chargePowerLong.intValue(); - if (this.parent.getSetChargePowerLimit().orElse(0) >= this.parent.getMinimumHardwarePower() - .orElse(0) && chargePower <= 0) { - - if (this.chargingFinishedCounter >= 90) { - tmpStatus = Status.CHARGING_FINISHED; - } else { - this.chargingFinishedCounter++; - } - } else { - this.chargingFinishedCounter = 0; - - // Charging rejected because we are forcing to pause charging - if (this.parent.getSetChargePowerLimit().orElse(0) == 0) { - tmpStatus = Status.CHARGING_REJECTED; - } - } - yield tmpStatus; - } - case "C", "D" -> Status.CHARGING; - case "E", "F" -> { - this.errorCounter++; - this.parent.debugLog("Hardy Barth RAW_STATUS would be an error! Raw value: " + stringValue - + " - Error counter: " + this.errorCounter); - if (this.errorCounter > 3) { - yield Status.ERROR; - } - yield this.parent.getStatus(); - } - default -> { - this.parent.debugLog("State " + stringValue + " is not a valid state"); - yield Status.UNDEFINED; - } - }; - - if (!stringValue.equals("B")) { - this.chargingFinishedCounter = 0; - } - if (!stringValue.equals("E") || !stringValue.equals("F")) { - this.errorCounter = 0; - } - - return rawStatus; - }, "secc", "port0", "ci", "charge", "cp", "status"); - - this.parent._setStatus(status); - } - - /** - * Call the getValueFromJson with the detailed information of the channel. - * - * @param channelId Channel that value will be detect. - * @param json Whole JSON path, where the JsonElement for the given channel - * is located. - * @return Value of the last JsonElement by running through the specified JSON - * path. - */ - private Object getValueForChannel(EvcsHardyBarth.ChannelId channelId, JsonElement json) { - return this.getValueFromJson(channelId, json, channelId.converter, channelId.getJsonPaths()); - } - - /** - * Call the getValueFromJson without a divergent type in the raw json. - * - * @param channelId Channel that value will be detect. - * @param json Raw JsonElement. - * @param converter Converter, to convert the raw JSON value into a proper - * Channel. - * @param jsonPaths Whole JSON path, where the JsonElement for the given channel - * is located. - * @return Value of the last JsonElement by running through the specified JSON - * path. - */ - private Object getValueFromJson(ChannelId channelId, JsonElement json, Function converter, - String... jsonPaths) { - return this.getValueFromJson(channelId, null, json, converter, jsonPaths); - } - - /** - * Get the last JSON element and it's value, by running through the given - * jsonPath. - * - * @param channelId Channel that value will be detect. - * @param divergentTypeInRawJson Divergent type of the value in the depending - * JsonElement. - * @param json Raw JsonElement. - * @param converter Converter, to convert the raw JSON value into a - * proper Channel. - * @param jsonPaths Whole JSON path, where the JsonElement for the - * given channel is located. - * @return Value of the last JsonElement by running through the specified JSON - * path. - */ - private Object getValueFromJson(ChannelId channelId, OpenemsType divergentTypeInRawJson, JsonElement json, - Function converter, String... jsonPaths) { - - var currentJsonElement = json; - // Go through the whole jsonPath of the current channelId - for (var i = 0; i < jsonPaths.length; i++) { - var currentPathMember = jsonPaths[i]; - // System.out.println(currentPathMember); - try { - if (i == jsonPaths.length - 1) { - // - var openemsType = divergentTypeInRawJson == null ? channelId.doc().getType() - : divergentTypeInRawJson; - - // Last path element - var value = this.getJsonElementValue(currentJsonElement, openemsType, jsonPaths[i]); - - // Return the converted value - return converter.apply(value); - } - // Not last path element - currentJsonElement = JsonUtils.getAsJsonObject(currentJsonElement, currentPathMember); - } catch (OpenemsNamedException e) { - return null; - } - } - return null; - } - - /** - * Get Value of the given JsonElement in the required type. - * - * @param jsonElement Element as JSON. - * @param openemsType Required type. - * @param memberName Member name of the JSON Element. - * @return Value in the required type. - * @throws OpenemsNamedException Failed to get the value. - */ - private Object getJsonElementValue(JsonElement jsonElement, OpenemsType openemsType, String memberName) - throws OpenemsNamedException { - final Object value; - - switch (openemsType) { - case BOOLEAN: - value = JsonUtils.getAsInt(jsonElement, memberName) == 1; - break; - case DOUBLE: - value = JsonUtils.getAsDouble(jsonElement, memberName); - break; - case FLOAT: - value = JsonUtils.getAsFloat(jsonElement, memberName); - break; - case INTEGER: - value = JsonUtils.getAsInt(jsonElement, memberName); - break; - case LONG: - value = JsonUtils.getAsLong(jsonElement, memberName); - break; - case SHORT: - value = JsonUtils.getAsShort(jsonElement, memberName); - break; - case STRING: - value = JsonUtils.getAsString(jsonElement, memberName); - break; - default: - value = JsonUtils.getAsString(jsonElement, memberName); - break; - } - return value; - } -} diff --git a/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java new file mode 100644 index 00000000000..cde25123e84 --- /dev/null +++ b/io.openems.edge.evcs.hardybarth/src/io/openems/edge/evcs/hardybarth/HardyBarthWriteHandler.java @@ -0,0 +1,31 @@ +package io.openems.edge.evcs.hardybarth; + +import java.util.concurrent.CompletableFuture; + +import io.openems.edge.evcs.api.ManagedEvcs; +import io.openems.edge.evcs.api.WriteHandler; + +public class HardyBarthWriteHandler extends WriteHandler { + + private CompletableFuture applyChargePowerTask = CompletableFuture.completedFuture(null); + + public HardyBarthWriteHandler(ManagedEvcs parent) { + super(parent); + } + + @Override + protected synchronized void applyChargePower(int power) { + if (!this.applyChargePowerTask.isDone()) { + return; + } + this.applyChargePowerTask = CompletableFuture.runAsync(() -> { + super.applyChargePower(power); + }); + } + + @Override + public synchronized void cancelChargePower() { + this.applyChargePowerTask.cancel(true); + } + +} \ No newline at end of file diff --git a/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImplTest.java b/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImplTest.java index 2d44360154f..8ecb6c334bc 100644 --- a/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImplTest.java +++ b/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/EvcsHardyBarthImplTest.java @@ -1,23 +1,278 @@ package io.openems.edge.evcs.hardybarth; +import static io.openems.common.types.HttpStatus.OK; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.ofDummyBridge; +import static io.openems.edge.evcs.api.PhaseRotation.L2_L3_L1; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; +import static io.openems.edge.evcs.api.Status.CHARGING; + import org.junit.Test; +import io.openems.edge.bridge.http.api.HttpResponse; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.evcs.api.DeprecatedEvcs; +import io.openems.edge.evcs.api.Evcs; +import io.openems.edge.meter.api.ElectricityMeter; public class EvcsHardyBarthImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { - new ComponentTest(new EvcsHardyBarthImpl()) // + final var phaseRotation = L2_L3_L1; + var sut = new EvcsHardyBarthImpl(); + var ru = sut.readUtils; + new ComponentTest(sut) // + .addReference("httpBridgeFactory", ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setIp("192.168.8.101") // .setMaxHwCurrent(32_000) // .setMinHwCurrent(6_000) // - .build()) - .next(new TestCase()); + .setPhaseRotation(phaseRotation).build()) + + .next(new TestCase() // + .onBeforeProcessImage(() -> ru + .handleGetApiCallResponse(new HttpResponse(OK, API_RESPONSE), phaseRotation)) // + .output(EvcsHardyBarth.ChannelId.RAW_EVSE_GRID_CURRENT_LIMIT, 16) // + .output(EvcsHardyBarth.ChannelId.RAW_PHASE_COUNT, 3) // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_PLUG, "locked") // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_CONTACTOR, "closed") // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_PWM, "10.00") // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGE_STATUS_CHARGEPOINT, "C") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_CHARGE_MODE, "manual") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_CHANGE_METER, null) // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_AUTHMODE, "free") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_FIRMWARESTATE, "idle") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_FIRMWAREPROGRESS, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_SALIA_PUBLISH, null) // + .output(EvcsHardyBarth.ChannelId.RAW_SESSION_STATUS_AUTHORIZATION, "") // + .output(EvcsHardyBarth.ChannelId.RAW_SESSION_SLAC_STARTED, null) // + .output(EvcsHardyBarth.ChannelId.RAW_SESSION_AUTHORIZATION_METHOD, null) // + .output(EvcsHardyBarth.ChannelId.RAW_CONTACTOR_HLC_TARGET, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_CONTACTOR_ACTUAL, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_CONTACTOR_TARGET, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_CONTACTOR_ERROR, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_METER_SERIALNUMBER, "21031835") // + .output(EvcsHardyBarth.ChannelId.RAW_METER_TYPE, "klefr") // + .output(EvcsHardyBarth.ChannelId.RAW_METER_AVAILABLE, true) // + .output(EvcsHardyBarth.ChannelId.METER_NOT_AVAILABLE, false) // + .output(EvcsHardyBarth.ChannelId.RAW_ACTIVE_ENERGY_TOTAL, 4658050.0) // + .output(EvcsHardyBarth.ChannelId.RAW_ACTIVE_ENERGY_EXPORT, 0.0) // + .output(EvcsHardyBarth.ChannelId.RAW_EMERGENCY_SHUTDOWN, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_RCD_AVAILABLE, false) // + .output(EvcsHardyBarth.ChannelId.RAW_PLUG_LOCK_STATE_ACTUAL, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_PLUG_LOCK_STATE_TARGET, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_PLUG_LOCK_ERROR, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_CP_STATE, "C") // + .output(EvcsHardyBarth.ChannelId.RAW_DIODE_PRESENT, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_CABLE_CURRENT_LIMIT, "-1") // + .output(EvcsHardyBarth.ChannelId.RAW_VENTILATION_STATE_ACTUAL, "0") // + .output(EvcsHardyBarth.ChannelId.RAW_VENTILATION_STATE_TARGET, null) // + .output(EvcsHardyBarth.ChannelId.RAW_VENTILATION_AVAILABLE, false) // + .output(EvcsHardyBarth.ChannelId.RAW_EV_PRESENT, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_CHARGING, "1") // + .output(EvcsHardyBarth.ChannelId.RAW_RFID_AUTHORIZEREQ, "") // + .output(EvcsHardyBarth.ChannelId.RAW_RFID_AVAILABLE, false) // + .output(EvcsHardyBarth.ChannelId.RAW_GRID_CURRENT_LIMIT, "6") // + .output(EvcsHardyBarth.ChannelId.RAW_SLAC_ERROR, null) // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_PRODUCT, "2310007") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_MODELNAME, "Salia PLCC Slave") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_HARDWARE_VERSION, "1.0") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_SOFTWARE_VERSION, "1.50.0") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_VCS_VERSION, "V0R5e") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_HOSTNAME, "salia") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_MAC_ADDRESS, "00:01:87:13:12:34") // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_SERIAL, 101249323L) // + .output(EvcsHardyBarth.ChannelId.RAW_DEVICE_UUID, "5491ad62-022a-4356-a32c-00018713102x") // + + .output(Evcs.ChannelId.ENERGY_SESSION, 3460) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 4658050L) // + .output(ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY, 4658050L) // + .output(Evcs.ChannelId.PHASES, THREE_PHASE) // + .output(Evcs.ChannelId.STATUS, CHARGING) // + .output(DeprecatedEvcs.ChannelId.CHARGE_POWER, 3192) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 3192) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 1044) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, 1075) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, 1073) // + .output(ElectricityMeter.ChannelId.CURRENT, 14_770) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 4_770) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, 5_000) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, 5_000) // + .output(ElectricityMeter.ChannelId.VOLTAGE, 216_156) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, 218_868) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 215_000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 214_600) // + ); } + + private static final String API_RESPONSE = """ + { + "device":{ + "product":"2310007", + "modelname":"Salia PLCC Slave", + "hardware_version":"1.0", + "software_version":"1.50.0", + "vcs_version":"V0R5e", + "hostname":"salia", + "mac_address":"00:01:87:13:12:34", + "serial":"101249323", + "uuid":"5491ad62-022a-4356-a32c-00018713102x", + "internal_id":"412009" + }, + "secc":{ + "port0":{ + "ci":{ + "evse":{ + "basic":{ + "grid_current_limit":{ + "actual":"16" + }, + "phase_count":"3", + "physical_current_limit":"16", + "offered_current_limit":"6.0" + }, + "phase":{ + "actual":"3" + } + }, + "charge":{ + "cp":{ + "status":"C" + }, + "plug":{ + "status":"locked" + }, + "contactor":{ + "status":"closed" + }, + "pwm":{ + "status":"10.00" + } + } + }, + "salia":{ + "chargemode":"manual", + "thermal":"52893", + "mem":"392276", + "uptime":" 1:04", + "load":"0.37", + "chargedata":"3813|3192|3.46|", + "authmode":"free", + "firmwarestate":"idle", + "firmwareprogress":"0", + "heartbeat":"off", + "pausecharging":"0" + }, + "session":{ + "authorization_status":"" + }, + "contactor":{ + "state":{ + "hlc_target":"0", + "actual":"1", + "target":"1" + }, + "error":"0" + }, + "metering":{ + "meter":{ + "serialnumber":"21031835", + "type":"klefr", + "available":"1" + }, + "eichrecht_protocol":"none", + "power":{ + "active":{ + "ac":{ + "l1":{ + "actual":"10750" + }, + "l2":{ + "actual":"10730" + }, + "l3":{ + "actual":"10440" + } + } + }, + "active_total":{ + "actual":"31920" + } + }, + "current":{ + "ac":{ + "l1":{ + "actual":"5000" + }, + "l2":{ + "actual":"5000" + }, + "l3":{ + "actual":"4770" + } + } + }, + "energy":{ + "active_total":{ + "actual":"4658050" + }, + "active_export":{ + "actual":"0" + }, + "active_import":{ + "actual":"4658050" + } + } + }, + "emergency_shutdown":"0", + "rcd":{ + "feedback":{ + "available":"1" + }, + "state":{ + "actual":"1" + }, + "recloser":{ + "available":"0" + } + }, + "plug_lock":{ + "state":{ + "actual":"1", + "target":"1" + }, + "error":"0" + }, + "availability":{ + "actual":"operative" + }, + "cp":{ + "pwm_state":{ + "actual":"1" + }, + "state":"C", + "duty_cycle":"10.00" + }, + "rfid":{ + "available":"0", + "authorizereq":"" + }, + "diode_present":"1", + "cable_current_limit":"-1", + "ready_for_slac":"0", + "ev_present":"1", + "ventilation":{ + "state":{ + "actual":"0" + }, + "available":"0" + }, + "charging":"1", + "grid_current_limit":"6" + } + } + } + """; } diff --git a/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/MyConfig.java b/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/MyConfig.java index 98cc8717cff..d3a6ee1c74a 100644 --- a/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/MyConfig.java +++ b/io.openems.edge.evcs.hardybarth/test/io/openems/edge/evcs/hardybarth/MyConfig.java @@ -1,6 +1,7 @@ package io.openems.edge.evcs.hardybarth; import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.evcs.api.PhaseRotation; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { @@ -10,6 +11,7 @@ protected static class Builder { private String ip; private int minHwCurrent; private int maxHwCurrent; + private PhaseRotation phaseRotation; private Builder() { } @@ -34,6 +36,11 @@ public Builder setMaxHwCurrent(int maxHwCurrent) { return this; } + public Builder setPhaseRotation(PhaseRotation phaseRotation) { + this.phaseRotation = phaseRotation; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -74,4 +81,9 @@ public int minHwCurrent() { public int maxHwCurrent() { return this.builder.maxHwCurrent; } + + @Override + public PhaseRotation phaseRotation() { + return this.builder.phaseRotation; + } } \ No newline at end of file diff --git a/io.openems.edge.evcs.keba.kecontact/bnd.bnd b/io.openems.edge.evcs.keba.kecontact/bnd.bnd index c1057f1716f..507c805f4d6 100644 --- a/io.openems.edge.evcs.keba.kecontact/bnd.bnd +++ b/io.openems.edge.evcs.keba.kecontact/bnd.bnd @@ -7,7 +7,8 @@ Bundle-Version: 1.0.0.${tstamp} ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ - io.openems.edge.evcs.api + io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/Config.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/Config.java index 66e9e587fde..5bed47bdad4 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/Config.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/Config.java @@ -3,6 +3,8 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.evcs.api.PhaseRotation; + @ObjectClassDefinition(name = "EVCS KEBA KeContact", // description = "Implements the KEBA KeContact P20/P30 electric vehicle charging station.") @interface Config { @@ -25,6 +27,9 @@ @AttributeDefinition(name = "Minimum power", description = "Minimum current of the Charger in mA.", required = true) int minHwCurrent() default 6000; + @AttributeDefinition(name = "Phase Rotation", description = "Apply standard or rotated wiring") + PhaseRotation phaseRotation() default PhaseRotation.L1_L2_L3; + @AttributeDefinition(name = "Use display?", description = "Activates the KEBA display to show the current power or states.", required = true) boolean useDisplay() default true; diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java index 1b4e637e250..97f8a70c07c 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContact.java @@ -16,8 +16,10 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; -public interface EvcsKebaKeContact extends ManagedEvcs, Evcs, OpenemsComponent, EventHandler, ModbusSlave { +public interface EvcsKebaKeContact + extends ManagedEvcs, Evcs, ElectricityMeter, OpenemsComponent, EventHandler, ModbusSlave { public static final int UDP_PORT = 7090; @@ -85,34 +87,9 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { /* * Report 3 */ - VOLTAGE_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .text("Voltage on L1")), // - VOLTAGE_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .text("Voltage on L2")), // - VOLTAGE_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .text("Voltage on L3")), // - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .text("Current on L1")), // - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .text("Current on L2")), // - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .text("Current on L3")), // - ACTUAL_POWER(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIWATT) // - .text("Total real power")), // COS_PHI(Doc.of(OpenemsType.INTEGER) // .unit(Unit.PERCENT) // .text("Power factor")), // - ENERGY_TOTAL(Doc.of(OpenemsType.LONG) // - .unit(Unit.CUMULATED_WATT_HOURS) // - .text("Total power consumption (persistent) without current loading session. " - + "Is summed up after each completed charging session")), // DIP_SWITCH_ERROR_1_3_NOT_SET_FOR_COMM(Doc.of(Level.FAULT) // .debounce(5, Debounce.TRUE_VALUES_IN_A_ROW_TO_SET_TRUE) // .text("Dip-Switch 1.3. for communication must be on")), // @@ -179,19 +156,10 @@ private ModbusSlaveNatureTable getModbusSlaveNatureTable(AccessMode accessMode) .channel(72, EvcsKebaKeContact.ChannelId.CURR_FAILSAFE, ModbusType.UINT16) .channel(73, EvcsKebaKeContact.ChannelId.TIMEOUT_FAILSAFE, ModbusType.UINT16) .channel(74, EvcsKebaKeContact.ChannelId.CURR_TIMER, ModbusType.UINT16) - .channel(75, EvcsKebaKeContact.ChannelId.TIMEOUT_CT, ModbusType.UINT16).uint16Reserved(76) + .channel(75, EvcsKebaKeContact.ChannelId.TIMEOUT_CT, ModbusType.UINT16) // + .uint16Reserved(76) // .channel(77, EvcsKebaKeContact.ChannelId.OUTPUT, ModbusType.UINT16) .channel(78, EvcsKebaKeContact.ChannelId.INPUT, ModbusType.UINT16) - - // Report 3 - .channel(79, EvcsKebaKeContact.ChannelId.VOLTAGE_L1, ModbusType.UINT16) - .channel(80, EvcsKebaKeContact.ChannelId.VOLTAGE_L2, ModbusType.UINT16) - .channel(81, EvcsKebaKeContact.ChannelId.VOLTAGE_L3, ModbusType.UINT16) - .channel(82, EvcsKebaKeContact.ChannelId.CURRENT_L1, ModbusType.UINT16) - .channel(83, EvcsKebaKeContact.ChannelId.CURRENT_L2, ModbusType.UINT16) - .channel(84, EvcsKebaKeContact.ChannelId.CURRENT_L3, ModbusType.UINT16) - .channel(85, EvcsKebaKeContact.ChannelId.ACTUAL_POWER, ModbusType.UINT16) - .channel(86, EvcsKebaKeContact.ChannelId.COS_PHI, ModbusType.UINT16).uint16Reserved(87) - .channel(88, EvcsKebaKeContact.ChannelId.ENERGY_TOTAL, ModbusType.UINT16).build(); + .build(); } } diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImpl.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImpl.java index c036301b188..73452b60b4b 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImpl.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImpl.java @@ -29,11 +29,13 @@ import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; import io.openems.edge.evcs.api.ChargingType; +import io.openems.edge.evcs.api.DeprecatedEvcs; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.keba.kecontact.core.EvcsKebaKeContactCore; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -44,12 +46,13 @@ @EventTopics({ // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) -public class EvcsKebaKeContactImpl extends AbstractManagedEvcsComponent - implements EvcsKebaKeContact, ManagedEvcs, Evcs, OpenemsComponent, EventHandler, ModbusSlave { +public class EvcsKebaKeContactImpl extends AbstractManagedEvcsComponent implements EvcsKebaKeContact, ManagedEvcs, Evcs, + DeprecatedEvcs, ElectricityMeter, OpenemsComponent, EventHandler, ModbusSlave { + + protected final ReadHandler readHandler = new ReadHandler(this); private final Logger log = LoggerFactory.getLogger(EvcsKebaKeContactImpl.class); private final ReadWorker readWorker = new ReadWorker(this); - private final ReadHandler readHandler = new ReadHandler(this); @Reference private EvcsPower evcsPower; @@ -65,10 +68,21 @@ public class EvcsKebaKeContactImpl extends AbstractManagedEvcsComponent public EvcsKebaKeContactImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // Evcs.ChannelId.values(), // + DeprecatedEvcs.ChannelId.values(), // EvcsKebaKeContact.ChannelId.values() // ); + DeprecatedEvcs.copyToDeprecatedEvcsChannels(this); + ElectricityMeter.calculateSumCurrentFromPhases(this); + ElectricityMeter.calculateAverageVoltageFromPhases(this); + + // Set ReactivePower defaults + this._setReactivePower(0); + this._setReactivePowerL1(0); + this._setReactivePowerL2(0); + this._setReactivePowerL3(0); } @Activate diff --git a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadHandler.java b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadHandler.java index 3fafa14550a..0bcb9a43364 100644 --- a/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadHandler.java +++ b/io.openems.edge.evcs.keba.kecontact/src/io/openems/edge/evcs/keba/kecontact/ReadHandler.java @@ -1,19 +1,27 @@ package io.openems.edge.evcs.keba.kecontact; +import static io.openems.common.utils.JsonUtils.getAsOptionalInt; +import static io.openems.common.utils.JsonUtils.getAsOptionalLong; +import static io.openems.common.utils.JsonUtils.getAsOptionalString; +import static io.openems.edge.evcs.api.Evcs.evaluatePhaseCount; +import static io.openems.edge.evcs.api.Phases.THREE_PHASE; +import static io.openems.edge.evcs.api.Status.CHARGING; +import static java.lang.Math.round; + import java.math.BigInteger; import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.channel.ChannelId; import io.openems.edge.evcs.api.Evcs; -import io.openems.edge.evcs.api.Phases; +import io.openems.edge.evcs.api.PhaseRotation.RotatedPhases; import io.openems.edge.evcs.api.Status; /** @@ -34,240 +42,258 @@ public ReadHandler(EvcsKebaKeContactImpl parent) { @Override public void accept(String message) { + final var keba = this.parent; if (message.startsWith("TCH-OK")) { this.log.debug("KEBA confirmed reception of command: TCH-OK"); - this.parent.triggerQuery(); + keba.triggerQuery(); + return; + } - } else if (message.startsWith("TCH-ERR")) { + if (message.startsWith("TCH-ERR")) { this.log.warn("KEBA reported command error: TCH-ERR"); - this.parent.triggerQuery(); + keba.triggerQuery(); + return; + } - } else { - JsonElement jsonMessageElement; - try { - jsonMessageElement = JsonUtils.parse(message); - } catch (OpenemsNamedException e) { - this.log.error("Error while parsing KEBA message: " + e.getMessage()); - return; + keba.logInfoInDebugmode(this.log, message); + + // Parse JsonObject + final JsonObject j; + try { + j = JsonUtils.parseToJsonObject(message); + } catch (OpenemsNamedException e) { + this.log.error("Error while parsing KEBA message: " + e.getMessage()); + return; + } + + switch (getAsOptionalString(j, "ID").orElse("")) { + /* + * report 1 + */ + case "1" -> { + this.receiveReport1 = true; + this.setString(EvcsKebaKeContact.ChannelId.SERIAL, j, "Serial"); + this.setString(EvcsKebaKeContact.ChannelId.FIRMWARE, j, "Firmware"); + this.setInt(EvcsKebaKeContact.ChannelId.COM_MODULE, j, "COM-module"); + + // Dip-Switches + var dipSwitch1 = getAsOptionalString(j, "DIP-Sw1"); + var dipSwitch2 = getAsOptionalString(j, "DIP-Sw2"); + + if (dipSwitch1.isPresent() && dipSwitch2.isPresent()) { + this.checkDipSwitchSettings(dipSwitch1.get(), dipSwitch2.get()); } - this.parent.logInfoInDebugmode(this.log, message); - - var jsonMessage = jsonMessageElement.getAsJsonObject(); - // JsonUtils.prettyPrint(jMessage); - var idOpt = JsonUtils.getAsOptionalString(jsonMessage, "ID"); - if (idOpt.isPresent()) { - // message with ID - var id = idOpt.get(); - if (id.equals("1")) { - /* - * Reply to report 1 - */ - this.receiveReport1 = true; - this.setString(EvcsKebaKeContact.ChannelId.SERIAL, jsonMessage, "Serial"); - this.setString(EvcsKebaKeContact.ChannelId.FIRMWARE, jsonMessage, "Firmware"); - this.setInt(EvcsKebaKeContact.ChannelId.COM_MODULE, jsonMessage, "COM-module"); - - // Dip-Switches - var dipSwitch1 = JsonUtils.getAsOptionalString(jsonMessage, "DIP-Sw1"); - var dipSwitch2 = JsonUtils.getAsOptionalString(jsonMessage, "DIP-Sw2"); - - if (dipSwitch1.isPresent() && dipSwitch2.isPresent()) { - this.checkDipSwitchSettings(dipSwitch1.get(), dipSwitch2.get()); - } - // Product information - var product = JsonUtils.getAsOptionalString(jsonMessage, "Product"); - if (product.isPresent()) { - this.parent.channel(EvcsKebaKeContact.ChannelId.PRODUCT).setNextValue(product.get()); - this.checkProductInformation(product.get()); - } + // Product information + var product = getAsOptionalString(j, "Product"); + keba.channel(EvcsKebaKeContact.ChannelId.PRODUCT).setNextValue(product.orElse(null)); + if (product.isPresent()) { + this.checkProductInformation(product.get()); + } + } - } else if (id.equals("2")) { - /* - * Reply to report 2 - */ - this.receiveReport2 = true; - this.setInt(EvcsKebaKeContact.ChannelId.STATUS_KEBA, jsonMessage, "State"); - - // Value "setenergy" not used, because it is reset by the currtime 0 1 command - - // Set Evcs status - Channel stateChannel = this.parent.channel(EvcsKebaKeContact.ChannelId.STATUS_KEBA); - Channel plugChannel = this.parent.channel(EvcsKebaKeContact.ChannelId.PLUG); - - Plug plug = plugChannel.value().asEnum(); - Status status = stateChannel.value().asEnum(); - if (plug.equals(Plug.PLUGGED_ON_EVCS_AND_ON_EV_AND_LOCKED)) { - - // Charging is rejected (by the Software) if the plug is connected but the EVCS - // still not ready for charging. - if (status.equals(Status.NOT_READY_FOR_CHARGING)) { - status = Status.CHARGING_REJECTED; - } - - // Charging is Finished if 'Plug' is connected, State was charging or already - // finished and the EVCS is still ready for charging. - var evcsStatus = this.parent.getStatus(); - switch (evcsStatus) { - case CHARGING_REJECTED: - case ENERGY_LIMIT_REACHED: - case ERROR: - case NOT_READY_FOR_CHARGING: - case STARTING: - case UNDEFINED: - break; - case READY_FOR_CHARGING: - case CHARGING: - case CHARGING_FINISHED: - if (status.equals(Status.READY_FOR_CHARGING) - && this.parent.getSetChargePowerLimit().orElse(0) > 0) { - status = Status.CHARGING_FINISHED; - } - break; - } - - /* - * Check if the maximum energy limit is reached, informs the user and sets the - * status - */ - int limit = this.parent.getSetEnergyLimit().orElse(0); - int energy = this.parent.getEnergySession().orElse(0); - if (energy >= limit && limit != 0) { - status = Status.ENERGY_LIMIT_REACHED; - } - } else { - // Plug not fully connected - status = Status.NOT_READY_FOR_CHARGING; - } + /* + * report 2 + */ + case "2" -> { + this.receiveReport2 = true; + this.setInt(EvcsKebaKeContact.ChannelId.STATUS_KEBA, j, "State"); - this.parent._setStatus(status); - var errorState = status == Status.ERROR == true; - this.parent.channel(EvcsKebaKeContact.ChannelId.CHARGINGSTATION_STATE_ERROR) - .setNextValue(errorState); - - this.setInt(EvcsKebaKeContact.ChannelId.ERROR_1, jsonMessage, "Error1"); - this.setInt(EvcsKebaKeContact.ChannelId.ERROR_2, jsonMessage, "Error2"); - this.setInt(EvcsKebaKeContact.ChannelId.PLUG, jsonMessage, "Plug"); - this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_SYS, jsonMessage, "Enable sys"); - this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_USER, jsonMessage, "Enable user"); - this.setInt(EvcsKebaKeContact.ChannelId.MAX_CURR_PERCENT, jsonMessage, "Max curr %"); - this.setInt(EvcsKebaKeContact.ChannelId.CURR_FAILSAFE, jsonMessage, "Curr FS"); - this.setInt(EvcsKebaKeContact.ChannelId.TIMEOUT_FAILSAFE, jsonMessage, "Tmo FS"); - this.setInt(EvcsKebaKeContact.ChannelId.CURR_TIMER, jsonMessage, "Curr timer"); - this.setInt(EvcsKebaKeContact.ChannelId.TIMEOUT_CT, jsonMessage, "Tmo CT"); - this.setBoolean(EvcsKebaKeContact.ChannelId.OUTPUT, jsonMessage, "Output"); - this.setBoolean(EvcsKebaKeContact.ChannelId.INPUT, jsonMessage, "Input"); - this.setInt(EvcsKebaKeContact.ChannelId.MAX_CURR, jsonMessage, "Curr HW"); - this.setInt(EvcsKebaKeContact.ChannelId.CURR_USER, jsonMessage, "Curr user"); - - } else if (id.equals("3")) { - /* - * Reply to report 3 - */ - this.receiveReport3 = true; - this.setInt(EvcsKebaKeContact.ChannelId.VOLTAGE_L1, jsonMessage, "U1"); - this.setInt(EvcsKebaKeContact.ChannelId.VOLTAGE_L2, jsonMessage, "U2"); - this.setInt(EvcsKebaKeContact.ChannelId.VOLTAGE_L3, jsonMessage, "U3"); - this.setInt(EvcsKebaKeContact.ChannelId.CURRENT_L1, jsonMessage, "I1"); - this.setInt(EvcsKebaKeContact.ChannelId.CURRENT_L2, jsonMessage, "I2"); - this.setInt(EvcsKebaKeContact.ChannelId.CURRENT_L3, jsonMessage, "I3"); - this.setInt(EvcsKebaKeContact.ChannelId.ACTUAL_POWER, jsonMessage, "P"); - this.setInt(EvcsKebaKeContact.ChannelId.COS_PHI, jsonMessage, "PF"); - - long totalEnergy = Math - .round(JsonUtils.getAsOptionalLong(jsonMessage, "E total").orElse(0L) * 0.1F); - this.parent.channel(EvcsKebaKeContact.ChannelId.ENERGY_TOTAL).setNextValue(totalEnergy); - this.parent._setActiveConsumptionEnergy(totalEnergy); - - // Set the count of the Phases that are currently used - Channel currentL1 = this.parent.channel(EvcsKebaKeContact.ChannelId.CURRENT_L1); - Channel currentL2 = this.parent.channel(EvcsKebaKeContact.ChannelId.CURRENT_L2); - Channel currentL3 = this.parent.channel(EvcsKebaKeContact.ChannelId.CURRENT_L3); - var currentSum = currentL1.getNextValue().orElse(0) + currentL2.getNextValue().orElse(0) - + currentL3.getNextValue().orElse(0); - - if (currentSum > 300) { - - this.parent._setStatus(Status.CHARGING); - var phases = 0; - - if (currentL1.getNextValue().orElse(0) >= 100) { - phases += 1; - } - if (currentL2.getNextValue().orElse(0) >= 100) { - phases += 1; - } - if (currentL3.getNextValue().orElse(0) >= 100) { - phases += 1; - } - this.parent._setPhases(phases); - - this.parent.logInfoInDebugmode(this.log, "Used phases: " + phases); - } + // Value "setenergy" not used, because it is reset by the currtime 0 1 command - /* - * Set FIXED_MAXIMUM_HARDWARE_POWER of Evcs - this is setting internally the - * dynamically calculated MAXIMUM_HARDWARE_POWER including the current used - * phases. - */ - Channel maxDipSwitchLimitChannel = this.parent - .channel(EvcsKebaKeContact.ChannelId.DIP_SWITCH_MAX_HW); - int maxDipSwitchPowerLimit = Math.round( - maxDipSwitchLimitChannel.value().orElse(Evcs.DEFAULT_MAXIMUM_HARDWARE_CURRENT) / 1000f) - * Evcs.DEFAULT_VOLTAGE * Phases.THREE_PHASE.getValue(); - - // Minimum of hardware setting and component configuration will be set. - int maximumHardwareLimit = Math.min(maxDipSwitchPowerLimit, - this.parent.getConfiguredMaximumHardwarePower()); - - this.parent._setFixedMaximumHardwarePower(maximumHardwareLimit); - - /* - * Set FIXED_MINIMUM_HARDWARE_POWER of Evcs - this is setting internally the - * dynamically calculated MINIMUM_HARDWARE_POWER including the current used - * phases. - */ - this.parent._setFixedMinimumHardwarePower(this.parent.getConfiguredMinimumHardwarePower()); - - /* - * Set CHARGE_POWER of Evcs - */ - var powerMw = JsonUtils.getAsOptionalInt(jsonMessage, "P"); // in [mW] - Integer power = null; - if (powerMw.isPresent()) { - power = powerMw.get() / 1000; // convert to [W] - } - this.parent.channel(Evcs.ChannelId.CHARGE_POWER).setNextValue(power); + // Set Evcs status + Channel stateChannel = keba.channel(EvcsKebaKeContact.ChannelId.STATUS_KEBA); + Channel plugChannel = keba.channel(EvcsKebaKeContact.ChannelId.PLUG); + + Plug plug = plugChannel.value().asEnum(); + Status status = stateChannel.value().asEnum(); + if (plug.equals(Plug.PLUGGED_ON_EVCS_AND_ON_EV_AND_LOCKED)) { - /* - * Set ENERGY_SESSION of Evcs - */ - this.parent.channel(Evcs.ChannelId.ENERGY_SESSION) - .setNextValue(JsonUtils.getAsOptionalInt(jsonMessage, "E pres").orElse(0) * 0.1); + // Charging is rejected (by the Software) if the plug is connected but the EVCS + // still not ready for charging. + if (status.equals(Status.NOT_READY_FOR_CHARGING)) { + status = Status.CHARGING_REJECTED; + } + + // Charging is Finished if 'Plug' is connected, State was charging or already + // finished and the EVCS is still ready for charging. + switch (keba.getStatus()) { + case CHARGING_REJECTED: + case ENERGY_LIMIT_REACHED: + case ERROR: + case NOT_READY_FOR_CHARGING: + case STARTING: + case UNDEFINED: + break; + case READY_FOR_CHARGING: + case CHARGING: + case CHARGING_FINISHED: + if (status.equals(Status.READY_FOR_CHARGING) && keba.getSetChargePowerLimit().orElse(0) > 0) { + status = Status.CHARGING_FINISHED; + } + break; } - } else { /* - * message without ID -> UDP broadcast + * Check if the maximum energy limit is reached, informs the user and sets the + * status */ - if (jsonMessage.has("State")) { - this.setInt(EvcsKebaKeContact.ChannelId.STATUS_KEBA, jsonMessage, "State"); - } - if (jsonMessage.has("Plug")) { - this.setInt(EvcsKebaKeContact.ChannelId.PLUG, jsonMessage, "Plug"); - } - if (jsonMessage.has("Input")) { - this.setBoolean(EvcsKebaKeContact.ChannelId.INPUT, jsonMessage, "Input"); - } - if (jsonMessage.has("Enable sys")) { - this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_SYS, jsonMessage, "Enable sys"); - } - if (jsonMessage.has("E pres")) { - this.parent.channel(Evcs.ChannelId.ENERGY_SESSION) - .setNextValue(JsonUtils.getAsOptionalInt(jsonMessage, "E pres").orElse(0) * 0.1); + int limit = keba.getSetEnergyLimit().orElse(0); + int energy = keba.getEnergySession().orElse(0); + if (energy >= limit && limit != 0) { + status = Status.ENERGY_LIMIT_REACHED; } + } else { + // Plug not fully connected + status = Status.NOT_READY_FOR_CHARGING; } + + keba._setStatus(status); + var errorState = status == Status.ERROR == true; + keba.channel(EvcsKebaKeContact.ChannelId.CHARGINGSTATION_STATE_ERROR).setNextValue(errorState); + + this.setInt(EvcsKebaKeContact.ChannelId.ERROR_1, j, "Error1"); + this.setInt(EvcsKebaKeContact.ChannelId.ERROR_2, j, "Error2"); + this.setInt(EvcsKebaKeContact.ChannelId.PLUG, j, "Plug"); + this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_SYS, j, "Enable sys"); + this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_USER, j, "Enable user"); + this.setInt(EvcsKebaKeContact.ChannelId.MAX_CURR_PERCENT, j, "Max curr %"); + this.setInt(EvcsKebaKeContact.ChannelId.CURR_FAILSAFE, j, "Curr FS"); + this.setInt(EvcsKebaKeContact.ChannelId.TIMEOUT_FAILSAFE, j, "Tmo FS"); + this.setInt(EvcsKebaKeContact.ChannelId.CURR_TIMER, j, "Curr timer"); + this.setInt(EvcsKebaKeContact.ChannelId.TIMEOUT_CT, j, "Tmo CT"); + this.setBoolean(EvcsKebaKeContact.ChannelId.OUTPUT, j, "Output"); + this.setBoolean(EvcsKebaKeContact.ChannelId.INPUT, j, "Input"); + this.setInt(EvcsKebaKeContact.ChannelId.MAX_CURR, j, "Curr HW"); + this.setInt(EvcsKebaKeContact.ChannelId.CURR_USER, j, "Curr user"); + } + + /* + * report 3 + */ + case "3" -> { + /* + * Reply to report 3 + */ + this.receiveReport3 = true; + + // Voltage + final var voltageL1 = getAsOptionalInt(j, "U1").map(v -> v != 0 ? v * 1000 : null).orElse(null); + final var voltageL2 = getAsOptionalInt(j, "U2").map(v -> v != 0 ? v * 1000 : null).orElse(null); + final var voltageL3 = getAsOptionalInt(j, "U3").map(v -> v != 0 ? v * 1000 : null).orElse(null); + + // Current + final var currentL1 = getAsOptionalInt(j, "I1").orElse(0).intValue(); + final var currentL2 = getAsOptionalInt(j, "I2").orElse(0).intValue(); + final var currentL3 = getAsOptionalInt(j, "I3").orElse(0).intValue(); + + // Power + final var activePower = getAsOptionalInt(j, "P") // + .map(p -> p / 1000) // convert [mW] to [W] + .orElse(null); + keba._setActivePower(activePower); + + // Round power per phase and apply rotated phases + var appp = ActivePowerPerPhase.from(activePower, // + voltageL1, currentL1, voltageL2, currentL2, voltageL3, currentL3); + var rp = RotatedPhases.from(keba.config.phaseRotation(), // + voltageL1, currentL1, appp.activePowerL1, // + voltageL2, currentL2, appp.activePowerL2, // + voltageL3, currentL3, appp.activePowerL3); + keba._setVoltageL1(rp.voltageL1()); + keba._setVoltageL2(rp.voltageL2()); + keba._setVoltageL3(rp.voltageL3()); + keba._setCurrentL1(rp.currentL1()); + keba._setCurrentL2(rp.currentL2()); + keba._setCurrentL3(rp.currentL3()); + keba._setActivePowerL1(rp.activePowerL1()); + keba._setActivePowerL2(rp.activePowerL2()); + keba._setActivePowerL3(rp.activePowerL3()); + + // Energy + keba._setActiveProductionEnergy(// + getAsOptionalLong(j, "E total") // + .map(e -> round(e * 0.1F)) // + .orElse(null)); + keba._setEnergySession(// + getAsOptionalInt(j, "E pres") // + .map(e -> round(e * 0.1F)) // + .orElse(null)); + + // TODO use COS_PHI to calculate ReactivePower + this.setInt(EvcsKebaKeContact.ChannelId.COS_PHI, j, "PF"); + + final var phases = evaluatePhaseCount(appp.activePowerL1, appp.activePowerL2, appp.activePowerL3); + keba._setPhases(phases); + if (phases != null) { + keba.logInfoInDebugmode(this.log, "Used phases: " + phases); + keba._setStatus(CHARGING); + } + + /* + * Set FIXED_MAXIMUM_HARDWARE_POWER of Evcs - this is setting internally the + * dynamically calculated MAXIMUM_HARDWARE_POWER including the current used + * phases. + */ + Channel maxDipSwitchLimitChannel = keba.channel(EvcsKebaKeContact.ChannelId.DIP_SWITCH_MAX_HW); + int maxDipSwitchPowerLimit = round(maxDipSwitchLimitChannel.value() // + .orElse(Evcs.DEFAULT_MAXIMUM_HARDWARE_CURRENT) / 1000f) * Evcs.DEFAULT_VOLTAGE + * THREE_PHASE.getValue(); + + // Minimum of hardware setting and component configuration will be set. + int maximumHardwareLimit = Math.min(maxDipSwitchPowerLimit, keba.getConfiguredMaximumHardwarePower()); + + keba._setFixedMaximumHardwarePower(maximumHardwareLimit); + + /* + * Set FIXED_MINIMUM_HARDWARE_POWER of Evcs - this is setting internally the + * dynamically calculated MINIMUM_HARDWARE_POWER including the current used + * phases. + */ + keba._setFixedMinimumHardwarePower(keba.getConfiguredMinimumHardwarePower()); + } + + /* + * message without ID -> UDP broadcast + */ + default -> { + if (j.has("State")) { + this.setInt(EvcsKebaKeContact.ChannelId.STATUS_KEBA, j, "State"); + } + if (j.has("Plug")) { + this.setInt(EvcsKebaKeContact.ChannelId.PLUG, j, "Plug"); + } + if (j.has("Input")) { + this.setBoolean(EvcsKebaKeContact.ChannelId.INPUT, j, "Input"); + } + if (j.has("Enable sys")) { + this.setBoolean(EvcsKebaKeContact.ChannelId.ENABLE_SYS, j, "Enable sys"); + } + if (j.has("E pres")) { + keba._setEnergySession(// + getAsOptionalInt(j, "E pres") // + .map(e -> round(e * 0.1F)) // + .orElse(null)); + } + } + } + } + + public record ActivePowerPerPhase(Integer activePowerL1, Integer activePowerL2, Integer activePowerL3) { + protected static ActivePowerPerPhase from(Integer activePowerSum, Integer voltageL1, int currentL1, + Integer voltageL2, int currentL2, Integer voltageL3, int currentL3) { + if (activePowerSum == null) { + return new ActivePowerPerPhase(null, null, null); + } + + var pL1 = voltageL1 != null ? voltageL1 / 1000 * currentL1 : 0; + var pL2 = voltageL2 != null ? voltageL2 / 1000 * currentL2 : 0; + var pL3 = voltageL3 != null ? voltageL3 / 1000 * currentL3 : 0; + var pSum = pL1 + pL2 + pL3; + var factor = activePowerSum / (float) pSum; // distribute power to match sum + + return new ActivePowerPerPhase(round(pL1 * factor), round(pL2 * factor), round(pL3 * factor)); } } @@ -318,29 +344,15 @@ private void checkDipSwitchSettings(String dipSwitch1, String dipSwitch2) { this.setnextStateChannelValue(EvcsKebaKeContact.ChannelId.DIP_SWITCH_INFO_2_8_SET_FOR_INSTALLATION, setState); // Set Channel for the configured maximum limit in mA - Integer hwLimit = null; - var hwLimitDips = dipSwitch1.substring(5); - - switch (hwLimitDips) { - case "000": - hwLimit = 10_000; - break; - case "100": - hwLimit = 13_000; - break; - case "010": - hwLimit = 16_000; - break; - case "110": - hwLimit = 20_000; - break; - case "001": - hwLimit = 25_000; - break; - case "101": - hwLimit = 32_000; - break; - } + var hwLimit = switch (dipSwitch1.substring(5)) { + case "000" -> 10_000; + case "100" -> 13_000; + case "010" -> 16_000; + case "110" -> 20_000; + case "001" -> 25_000; + case "101" -> 32_000; + default -> null; + }; this.parent.channel(EvcsKebaKeContact.ChannelId.DIP_SWITCH_MAX_HW).setNextValue(hwLimit); } @@ -383,20 +395,20 @@ protected static String hexStringToBinaryString(String dipSwitches) { return binaryString; } - private void set(EvcsKebaKeContact.ChannelId channelId, Object value) { + private void set(ChannelId channelId, Object value) { this.parent.channel(channelId).setNextValue(value); } - private void setString(EvcsKebaKeContact.ChannelId channelId, JsonObject jMessage, String name) { - this.set(channelId, JsonUtils.getAsOptionalString(jMessage, name).orElse(null)); + private void setString(ChannelId channelId, JsonObject jMessage, String name) { + this.set(channelId, getAsOptionalString(jMessage, name).orElse(null)); } - private void setInt(EvcsKebaKeContact.ChannelId channelId, JsonObject jMessage, String name) { - this.set(channelId, JsonUtils.getAsOptionalInt(jMessage, name).orElse(null)); + private void setInt(ChannelId channelId, JsonObject jMessage, String name) { + this.set(channelId, getAsOptionalInt(jMessage, name).orElse(null)); } - private void setBoolean(EvcsKebaKeContact.ChannelId channelId, JsonObject jMessage, String name) { - var enableSysOpt = JsonUtils.getAsOptionalInt(jMessage, name); + private void setBoolean(ChannelId channelId, JsonObject jMessage, String name) { + var enableSysOpt = getAsOptionalInt(jMessage, name); if (enableSysOpt.isPresent()) { this.set(channelId, enableSysOpt.get() == 1); } else { diff --git a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImplTest.java b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImplTest.java index 68b6c75ecd2..db60737ebb8 100644 --- a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImplTest.java +++ b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/EvcsKebaKeContactImplTest.java @@ -1,27 +1,143 @@ package io.openems.edge.evcs.keba.kecontact; +import static io.openems.edge.evcs.api.PhaseRotation.L2_L3_L1; +import static io.openems.edge.evcs.api.Status.CHARGING_REJECTED; +import static io.openems.edge.evcs.keba.kecontact.Plug.PLUGGED_ON_EVCS_AND_ON_EV_AND_LOCKED; + import org.junit.Test; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.keba.kecontact.core.EvcsKebaKeContactCoreImpl; import io.openems.edge.evcs.test.DummyEvcsPower; +import io.openems.edge.meter.api.ElectricityMeter; public class EvcsKebaKeContactImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { - new ComponentTest(new EvcsKebaKeContactImpl()) // + var sut = new EvcsKebaKeContactImpl(); + var rh = sut.readHandler; + new ComponentTest(sut) // .addReference("evcsPower", new DummyEvcsPower()) // .addReference("kebaKeContactCore", new EvcsKebaKeContactCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setDebugMode(false) // .setIp("172.0.0.1") // .setMinHwCurrent(6000) // + .setPhaseRotation(L2_L3_L1) // .setUseDisplay(false) // - .build()); // + .build()) // + + .next(new TestCase() // + .onBeforeProcessImage(() -> rh.accept(REPORT_1)) // + .output(EvcsKebaKeContact.ChannelId.SERIAL, "12345678") // + .output(EvcsKebaKeContact.ChannelId.FIRMWARE, "P30 v 3.10.57 (240521-093236)") // + .output(EvcsKebaKeContact.ChannelId.COM_MODULE, "0") // + .output(EvcsKebaKeContact.ChannelId.DIP_SWITCH_1, "00100101") // + .output(EvcsKebaKeContact.ChannelId.DIP_SWITCH_2, "00000010") // + .output(EvcsKebaKeContact.ChannelId.PRODUCT, "KC-P30-EC240422-E00")) // + + .next(new TestCase() // + .onBeforeProcessImage(() -> rh.accept(REPORT_2)) // + .output(EvcsKebaKeContact.ChannelId.STATUS_KEBA, CHARGING_REJECTED) // + .output(EvcsKebaKeContact.ChannelId.ERROR_1, 0) // + .output(EvcsKebaKeContact.ChannelId.ERROR_2, 0) // + .output(EvcsKebaKeContact.ChannelId.PLUG, PLUGGED_ON_EVCS_AND_ON_EV_AND_LOCKED) // + .output(EvcsKebaKeContact.ChannelId.ENABLE_SYS, false) // + .output(EvcsKebaKeContact.ChannelId.ENABLE_USER, false) // + .output(EvcsKebaKeContact.ChannelId.MAX_CURR_PERCENT, 1_000) // + .output(EvcsKebaKeContact.ChannelId.CURR_FAILSAFE, 0) // + .output(EvcsKebaKeContact.ChannelId.TIMEOUT_FAILSAFE, 0) // + .output(EvcsKebaKeContact.ChannelId.CURR_TIMER, 0) // + .output(EvcsKebaKeContact.ChannelId.TIMEOUT_CT, 0) // + .output(EvcsKebaKeContact.ChannelId.OUTPUT, false) // + .output(EvcsKebaKeContact.ChannelId.INPUT, false) // + .output(EvcsKebaKeContact.ChannelId.MAX_CURR, 32_000) // + .output(EvcsKebaKeContact.ChannelId.CURR_USER, 1_0000)) // + + .next(new TestCase() // + .onBeforeProcessImage(() -> rh.accept(REPORT_3)) // + .output(ElectricityMeter.ChannelId.VOLTAGE, 227_500) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L1, null) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L2, 228_000) // + .output(ElectricityMeter.ChannelId.VOLTAGE_L3, 227_000) // + .output(ElectricityMeter.ChannelId.CURRENT, 9_075) // + .output(ElectricityMeter.ChannelId.CURRENT_L1, 0) // + .output(ElectricityMeter.ChannelId.CURRENT_L2, 9_075) // + .output(ElectricityMeter.ChannelId.CURRENT_L3, 0) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER, 1_866) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, 0) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, 1_866) // + .output(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, 0) // + .output(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, 7747834L) // + .output(Evcs.ChannelId.ENERGY_SESSION, 6530) // + .output(EvcsKebaKeContact.ChannelId.COS_PHI, 905) // + + ); } + private static final String REPORT_1 = """ + { + "ID": "1", + "Product": "KC-P30-EC240422-E00", + "Serial": "12345678", + "Firmware":"P30 v 3.10.57 (240521-093236)", + "COM-module": 0, + "Backend": 0, + "timeQ": 3, + "setBoot": 0, + "DIP-Sw1": "0x25", + "DIP-Sw2": "0x02", + "Sec": 530786 + } + """; + private static final String REPORT_2 = """ + { + "ID": "2", + "State": 5, + "Error1": 0, + "Error2": 0, + "Plug": 7, + "AuthON": 0, + "Authreq": 0, + "Enable sys": 0, + "Enable user": 0, + "Max curr": 0, + "Max curr %": 1000, + "Curr HW": 32000, + "Curr user": 10000, + "Curr FS": 0, + "Tmo FS": 0, + "Curr timer": 0, + "Tmo CT": 0, + "Setenergy": 0, + "Output": 0, + "Input": 0, + "X2 phaseSwitch source": 0, + "X2 phaseSwitch": 0, + "Serial": "22054282", + "Sec": 530786 + } + """; + private static final String REPORT_3 = """ + { + "ID": "3", + "U1": 228, + "U2": 227, + "U3": 0, + "I1": 9075, + "I2": 0, + "I3": 0, + "P": 1866156, + "PF": 905, + "E pres": 65302, + "E total": 77478335, + "Serial": "22054282", + "Sec": 534926 + } + """; + } diff --git a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/MyConfig.java b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/MyConfig.java index 005b60b872a..6920b20339f 100644 --- a/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/MyConfig.java +++ b/io.openems.edge.evcs.keba.kecontact/test/io/openems/edge/evcs/keba/kecontact/MyConfig.java @@ -1,15 +1,17 @@ package io.openems.edge.evcs.keba.kecontact; import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.evcs.api.PhaseRotation; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id = null; - private int minHwCurrent; private String ip; private boolean debugMode; + private int minHwCurrent; + private PhaseRotation phaseRotation; private boolean useDisplay; private Builder() { @@ -35,6 +37,11 @@ public Builder setDebugMode(boolean debugMode) { return this; } + public Builder setPhaseRotation(PhaseRotation phaseRotation) { + this.phaseRotation = phaseRotation; + return this; + } + public Builder setUseDisplay(boolean useDisplay) { this.useDisplay = useDisplay; return this; @@ -76,6 +83,11 @@ public int minHwCurrent() { return this.builder.minHwCurrent; } + @Override + public PhaseRotation phaseRotation() { + return this.builder.phaseRotation; + } + @Override public boolean useDisplay() { return this.builder.useDisplay; diff --git a/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAbl.java b/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAbl.java index 1cbcf9933a7..d76f92df33b 100644 --- a/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAbl.java +++ b/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAbl.java @@ -7,8 +7,10 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.MeasuringEvcs; +import io.openems.edge.meter.api.ElectricityMeter; -public interface EvcsOcppAbl extends Evcs, MeasuringEvcs, ManagedEvcs, OpenemsComponent, EventHandler { +public interface EvcsOcppAbl + extends Evcs, MeasuringEvcs, ManagedEvcs, ElectricityMeter, OpenemsComponent, EventHandler { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { ; diff --git a/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImpl.java b/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImpl.java index 30901ec28f0..874de8df500 100644 --- a/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImpl.java +++ b/io.openems.edge.evcs.ocpp.abl/src/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImpl.java @@ -38,6 +38,7 @@ import io.openems.edge.evcs.ocpp.common.OcppInformations; import io.openems.edge.evcs.ocpp.common.OcppProfileType; import io.openems.edge.evcs.ocpp.common.OcppStandardRequests; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.Timedata; @Designate(ocd = Config.class, factory = true) @@ -51,7 +52,7 @@ EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) public class EvcsOcppAblImpl extends AbstractManagedOcppEvcsComponent - implements EvcsOcppAbl, Evcs, MeasuringEvcs, ManagedEvcs, OpenemsComponent, EventHandler { + implements EvcsOcppAbl, Evcs, MeasuringEvcs, ManagedEvcs, ElectricityMeter, OpenemsComponent, EventHandler { // Default value for the hardware limit private static final Integer DEFAULT_HARDWARE_LIMIT = 22080; @@ -86,8 +87,8 @@ public EvcsOcppAblImpl() { super(// PROFILE_TYPES, // OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // - AbstractManagedOcppEvcsComponent.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // MeasuringEvcs.ChannelId.values(), // EvcsOcppAbl.ChannelId.values() // diff --git a/io.openems.edge.evcs.ocpp.abl/test/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImplTest.java b/io.openems.edge.evcs.ocpp.abl/test/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImplTest.java index c3dc9a84329..13123f22149 100644 --- a/io.openems.edge.evcs.ocpp.abl/test/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImplTest.java +++ b/io.openems.edge.evcs.ocpp.abl/test/io/openems/edge/evcs/ocpp/abl/EvcsOcppAblImplTest.java @@ -8,15 +8,13 @@ public class EvcsOcppAblImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsOcppAblImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("evcsPower", new DummyEvcsPower()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setConnectorId(0) // .setOcppId("") // .setLogicalId("") // diff --git a/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/AbstractManagedOcppEvcsComponent.java b/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/AbstractManagedOcppEvcsComponent.java index f4724d48eff..5096e9b3588 100644 --- a/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/AbstractManagedOcppEvcsComponent.java +++ b/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/AbstractManagedOcppEvcsComponent.java @@ -1,5 +1,7 @@ package io.openems.edge.evcs.ocpp.common; +import static io.openems.edge.common.type.TypeUtils.getAsType; + import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -20,14 +22,13 @@ import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Channel; -import io.openems.edge.common.channel.Doc; import io.openems.edge.common.event.EdgeEventConstants; -import io.openems.edge.common.type.TypeUtils; import io.openems.edge.evcs.api.AbstractManagedEvcsComponent; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.MeasuringEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.TimedataProvider; /** @@ -59,7 +60,7 @@ * */ public abstract class AbstractManagedOcppEvcsComponent extends AbstractManagedEvcsComponent - implements Evcs, ManagedEvcs, MeasuringEvcs, EventHandler, TimedataProvider { + implements Evcs, ManagedEvcs, MeasuringEvcs, ElectricityMeter, EventHandler, TimedataProvider { private final Logger log = LoggerFactory.getLogger(AbstractManagedOcppEvcsComponent.class); @@ -83,20 +84,6 @@ protected AbstractManagedOcppEvcsComponent(OcppProfileType[] profileTypes, this.profileTypes = new HashSet<>(Arrays.asList(profileTypes)); } - public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - ; - private final Doc doc; - - private ChannelId(Doc doc) { - this.doc = doc; - } - - @Override - public Doc doc() { - return this.doc; - } - } - @Override protected void activate(ComponentContext context, String id, String alias, boolean enabled) { super.activate(context, id, alias, enabled); @@ -114,11 +101,10 @@ protected void modified(ComponentContext context, String id, String alias, boole private void setInitialSettings() { // Normally the limits are set automatically when the phase channel is set, but // not every OCPP charger provides the information about the number of phases. - int fixedMaximum = this.getFixedMaximumHardwarePower().orElse(DEFAULT_MAXIMUM_HARDWARE_POWER); - int fixedMinimum = this.getFixedMinimumHardwarePower().orElse(DEFAULT_MINIMUM_HARDWARE_POWER); - - this.getMaximumHardwarePowerChannel().setNextValue(fixedMaximum); - this.getMinimumHardwarePowerChannel().setNextValue(fixedMinimum); + this.getMaximumHardwarePowerChannel().setNextValue(// + this.getFixedMaximumHardwarePower().orElse(DEFAULT_MAXIMUM_HARDWARE_POWER)); + this.getMinimumHardwarePowerChannel().setNextValue(// + this.getFixedMinimumHardwarePower().orElse(DEFAULT_MINIMUM_HARDWARE_POWER)); } @Override @@ -157,22 +143,22 @@ private void setInitialTotalEnergyFromTimedata() { if (timedata == null || componentId == null) { return; } else { - timedata.getLatestValue(new ChannelAddress(componentId, Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY.id())) + timedata.getLatestValue( + new ChannelAddress(componentId, ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY.id())) .thenAccept(totalEnergyOpt -> { - if (this.getActiveConsumptionEnergy().isDefined()) { + if (this.getActiveProductionEnergy().isDefined()) { // Value has been read from device in the meantime return; } if (totalEnergyOpt.isPresent()) { try { - this._setActiveConsumptionEnergy( - TypeUtils.getAsType(OpenemsType.LONG, totalEnergyOpt.get())); + this._setActiveProductionEnergy(getAsType(OpenemsType.LONG, totalEnergyOpt.get())); } catch (IllegalArgumentException e) { - this._setActiveConsumptionEnergy(TypeUtils.getAsType(OpenemsType.LONG, 0L)); + this._setActiveProductionEnergy(getAsType(OpenemsType.LONG, 0L)); } } else { - this._setActiveConsumptionEnergy(TypeUtils.getAsType(OpenemsType.LONG, 0L)); + this._setActiveConsumptionEnergy(getAsType(OpenemsType.LONG, 0L)); } }); } @@ -284,7 +270,7 @@ private void resetMeasuredChannelValues() { Channel channel = this.channel(c); channel.setNextValue(null); } - this._setChargePower(0); + this._setActivePower(0); } /** @@ -305,7 +291,7 @@ private void checkCurrentState() { case NOT_READY_FOR_CHARGING: case STARTING: case UNDEFINED: - this._setChargePower(0); + this._setActivePower(0); break; } } @@ -338,11 +324,9 @@ protected void logWarn(Logger log, String message) { @Override public String debugLog() { - if (this instanceof ManagedEvcs) { - return "Limit:" + ((ManagedEvcs) this).getSetChargePowerLimit().orElse(null) + "|" - + this.getStatus().getName(); - } - return "Power:" + this.getChargePower().orElse(0) + "|" + this.getStatus().getName(); + return "P:" + this.getActivePower().orElse(null) // + + "|Limit:" + this.getSetChargePowerLimit().orElse(null) // + + "|" + this.getStatus().getName(); } @Override diff --git a/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/OcppInformations.java b/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/OcppInformations.java index f4f4d7be976..6751117b3fb 100644 --- a/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/OcppInformations.java +++ b/io.openems.edge.evcs.ocpp.common/src/io/openems/edge/evcs/ocpp/common/OcppInformations.java @@ -1,9 +1,9 @@ package io.openems.edge.evcs.ocpp.common; import io.openems.edge.common.channel.ChannelId; -import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.MeasuringEvcs; import io.openems.edge.evcs.api.SocEvcs; +import io.openems.edge.meter.api.ElectricityMeter; public enum OcppInformations { @@ -95,7 +95,7 @@ public enum OcppInformations { * UnitOfMeasure for frequency, the UnitOfMeasure for any SampledValue with * measurand: Frequency is Hertz. */ - CORE_METER_VALUES_FREQUENCY("Frequency", MeasuringEvcs.ChannelId.FREQUENCY), + CORE_METER_VALUES_FREQUENCY("Frequency", ElectricityMeter.ChannelId.FREQUENCY), /** * Instantaneous active power exported by EV. (W) @@ -105,7 +105,7 @@ public enum OcppInformations { /** * Instantaneous active power imported by EV. (W) */ - CORE_METER_VALUES_POWER_ACTIVE_IMPORT("Power.Active.Import", Evcs.ChannelId.CHARGE_POWER), + CORE_METER_VALUES_POWER_ACTIVE_IMPORT("Power.Active.Import", ElectricityMeter.ChannelId.ACTIVE_POWER), /** * Instantaneous power factor of total energy flow. @@ -146,7 +146,7 @@ public enum OcppInformations { /** * Instantaneous AC RMS supply voltage. */ - CORE_METER_VALUES_VOLTAGE("Voltage", MeasuringEvcs.ChannelId.VOLTAGE); + CORE_METER_VALUES_VOLTAGE("Voltage", ElectricityMeter.ChannelId.VOLTAGE); private final String ocppValue; private final ChannelId channelId; diff --git a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingle.java b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingle.java index fed8dedd6f2..4f3ac815d89 100644 --- a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingle.java +++ b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingle.java @@ -8,9 +8,10 @@ import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.MeasuringEvcs; import io.openems.edge.evcs.api.SocEvcs; +import io.openems.edge.meter.api.ElectricityMeter; public interface EvcsOcppIesKeywattSingle - extends Evcs, ManagedEvcs, MeasuringEvcs, OpenemsComponent, EventHandler, SocEvcs { + extends Evcs, ManagedEvcs, MeasuringEvcs, ElectricityMeter, OpenemsComponent, EventHandler, SocEvcs { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { ; diff --git a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImpl.java b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImpl.java index b27dde46b05..57eeb4c67b2 100644 --- a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImpl.java +++ b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/src/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImpl.java @@ -36,6 +36,7 @@ import io.openems.edge.evcs.ocpp.common.OcppInformations; import io.openems.edge.evcs.ocpp.common.OcppProfileType; import io.openems.edge.evcs.ocpp.common.OcppStandardRequests; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.timedata.api.Timedata; @Designate(ocd = Config.class, factory = true) @@ -48,8 +49,8 @@ EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // EdgeEventConstants.TOPIC_CYCLE_AFTER_PROCESS_IMAGE // }) -public class EvcsOcppIesKeywattSingleImpl extends AbstractManagedOcppEvcsComponent - implements EvcsOcppIesKeywattSingle, Evcs, ManagedEvcs, MeasuringEvcs, OpenemsComponent, EventHandler, SocEvcs { +public class EvcsOcppIesKeywattSingleImpl extends AbstractManagedOcppEvcsComponent implements EvcsOcppIesKeywattSingle, + Evcs, ManagedEvcs, MeasuringEvcs, ElectricityMeter, OpenemsComponent, EventHandler, SocEvcs { // Profiles that a Ies KeyWatt is supporting private static final OcppProfileType[] PROFILE_TYPES = { // @@ -76,8 +77,8 @@ public EvcsOcppIesKeywattSingleImpl() { super(// PROFILE_TYPES, // OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // - AbstractManagedOcppEvcsComponent.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // MeasuringEvcs.ChannelId.values(), // SocEvcs.ChannelId.values(), // diff --git a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/test/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImplTest.java b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/test/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImplTest.java index 8d0bea95587..313bd8288ff 100644 --- a/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/test/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImplTest.java +++ b/io.openems.edge.evcs.ocpp.ies.keywatt.singleccs/test/io/openems/edge/evcs/ocpp/ies/keywatt/singleccs/EvcsOcppIesKeywattSingleImplTest.java @@ -8,15 +8,13 @@ public class EvcsOcppIesKeywattSingleImplTest { - private static final String COMPONENT_ID = "evcs0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsOcppIesKeywattSingleImpl()) // .addReference("componentManager", new DummyComponentManager()) // .addReference("evcsPower", new DummyEvcsPower()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("evcs0") // .setConnectorId(0) // .setOcppId("") // .setDebugMode(false) // diff --git a/io.openems.edge.evcs.ocpp.server/bnd.bnd b/io.openems.edge.evcs.ocpp.server/bnd.bnd index 500e91981af..da432b59483 100644 --- a/io.openems.edge.evcs.ocpp.server/bnd.bnd +++ b/io.openems.edge.evcs.ocpp.server/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.common,\ io.openems.edge.evcs.api,\ io.openems.edge.evcs.ocpp.common,\ + io.openems.edge.meter.api,\ io.openems.edge.timedata.api,\ io.openems.wrapper.eu.chargetime.ocpp,\ diff --git a/io.openems.edge.evcs.ocpp.server/src/io/openems/edge/evcs/ocpp/server/CoreEventHandlerImpl.java b/io.openems.edge.evcs.ocpp.server/src/io/openems/edge/evcs/ocpp/server/CoreEventHandlerImpl.java index 1342ff71913..5189a6ffe41 100644 --- a/io.openems.edge.evcs.ocpp.server/src/io/openems/edge/evcs/ocpp/server/CoreEventHandlerImpl.java +++ b/io.openems.edge.evcs.ocpp.server/src/io/openems/edge/evcs/ocpp/server/CoreEventHandlerImpl.java @@ -121,7 +121,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter */ var format = value.getFormat(); if (format.equals(ValueFormat.SignedData)) { - val = this.fromHexToDezString(val); + val = fromHexToDezString(val); } var measurand = OcppInformations @@ -143,14 +143,14 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter case CORE_METER_VALUES_ENERGY_ACTIVE_IMPORT_INTERVAL: case CORE_METER_VALUES_ENERGY_ACTIVE_EXPORT_INTERVAL: if (unit.equals(Unit.KWH)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = Double.valueOf(val); break; case CORE_METER_VALUES_ENERGY_ACTIVE_IMPORT_REGISTER: if (unit.equals(Unit.KWH)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = Double.valueOf(val); @@ -165,13 +165,12 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter break; } - var sessionEnergy = 0; - var totalEnergy = 0L; - /* * Calculating the energy in this session and in total for the given energy * value. */ + final int sessionEnergy; + final long totalEnergy; if (evcs.returnsSessionEnergy()) { sessionEnergy = (int) energy; totalEnergy = evcs.getSessionStart().getEnergy() + energy; @@ -180,7 +179,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter totalEnergy = energy; } evcs._setEnergySession(sessionEnergy); - evcs._setActiveConsumptionEnergy(totalEnergy); + evcs._setActiveProductionEnergy(totalEnergy); break; case CORE_METER_VALUES_ENERGY_REACTIVE_EXPORT_REGISTER: @@ -188,7 +187,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter case CORE_METER_VALUES_ENERGY_REACTIVE_EXPORT_INTERVAL: case CORE_METER_VALUES_ENERGY_REACTIVE_IMPORT_INTERVAL: if (unit.equals(Unit.KVARH)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = Double.valueOf(val); break; @@ -197,7 +196,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter case CORE_METER_VALUES_POWER_ACTIVE_IMPORT: case CORE_METER_VALUES_POWER_OFFERED: if (unit.equals(Unit.KW)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = (int) Math.round(Double.parseDouble(val)); @@ -226,7 +225,7 @@ public MeterValuesConfirmation handleMeterValuesRequest(UUID sessionIndex, Meter case CORE_METER_VALUES_POWER_REACTIVE_EXPORT: case CORE_METER_VALUES_POWER_REACTIVE_IMPORT: if (unit.equals(Unit.KVAR)) { - val = this.multipliedByThousand(val); + val = multipliedByThousand(val); } correctValue = (int) Math.round(Double.parseDouble(val)); break; @@ -277,8 +276,9 @@ public StatusNotificationConfirmation handleStatusNotificationRequest(UUID sessi evcs.getSessionEnd().resetChargeSessionStampIfPresent(); // Set the start charge session stamp - evcs.getSessionStart().setChargeSessionStampIfNotPresent( - Instant.now(this.parent.componentManager.getClock()), evcs.getActiveConsumptionEnergy().orElse(0L)); + evcs.getSessionStart().setChargeSessionStampIfNotPresent(// + Instant.now(this.parent.componentManager.getClock()), // + evcs.getActiveProductionEnergy().orElse(0L)); break; case Faulted: evcsStatus = Status.ERROR; @@ -289,8 +289,9 @@ public StatusNotificationConfirmation handleStatusNotificationRequest(UUID sessi // Reset the start charge session stamp evcs.getSessionStart().resetChargeSessionStampIfPresent(); - evcs.getSessionEnd().setChargeSessionStampIfNotPresent(Instant.now(this.parent.componentManager.getClock()), - evcs.getActiveConsumptionEnergy().orElse(0L)); + evcs.getSessionEnd().setChargeSessionStampIfNotPresent(// + Instant.now(this.parent.componentManager.getClock()), // + evcs.getActiveProductionEnergy().orElse(0L)); break; case Preparing: evcsStatus = Status.READY_FOR_CHARGING; @@ -403,7 +404,7 @@ private AbstractManagedOcppEvcsComponent getEvcsBySessionIndexAndConnector(UUID * @param hex given value in hex * @return Decimal value as String */ - public String fromHexToDezString(String hex) { + public static String fromHexToDezString(String hex) { var dezValue = Integer.parseInt(hex, 16); return String.valueOf(dezValue); } @@ -414,7 +415,7 @@ public String fromHexToDezString(String hex) { * @param val value * @return Value / 1000 as String */ - private String multipliedByThousand(String val) { + private static String multipliedByThousand(String val) { if (val.isEmpty()) { return val; } @@ -435,8 +436,8 @@ private void setPowerDependingOnEnergy(AbstractManagedOcppEvcsComponent evcs, Do var power = 0; if (lastChargingProperty != null) { - power = this.calculateChargePower(lastChargingProperty, currentEnergy, timestamp); - evcs._setChargePower(power); + power = this.calculateActivePower(lastChargingProperty, currentEnergy, timestamp); + evcs._setActivePower(power); } evcs.setLastChargingProperty(new ChargingProperty(power, currentEnergy, timestamp)); } @@ -449,7 +450,7 @@ private void setPowerDependingOnEnergy(AbstractManagedOcppEvcsComponent evcs, Do * @param timestamp Time when the current Energy was measured. * @return current power */ - private int calculateChargePower(ChargingProperty lastMeterValue, double currentEnergy, ZonedDateTime timestamp) { + private int calculateActivePower(ChargingProperty lastMeterValue, double currentEnergy, ZonedDateTime timestamp) { double diffseconds = Duration.between(timestamp, lastMeterValue.getTimestamp()).getSeconds(); diff --git a/io.openems.edge.evcs.spelsberg/bnd.bnd b/io.openems.edge.evcs.spelsberg/bnd.bnd index 352d8d2c11e..be0c8b9d00b 100644 --- a/io.openems.edge.evcs.spelsberg/bnd.bnd +++ b/io.openems.edge.evcs.spelsberg/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} \ No newline at end of file diff --git a/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmart.java b/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmart.java index 4b05ab4ca21..5e16a5ffacf 100644 --- a/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmart.java +++ b/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmart.java @@ -64,30 +64,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .accessMode(AccessMode.READ_ONLY) // .text("Maximum current signaled to the EV for charging")), - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE).accessMode(AccessMode.READ_ONLY) // - .text("Current on L1")), - - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE).accessMode(AccessMode.READ_ONLY) // - .text("Current on L2")), - - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE).accessMode(AccessMode.READ_ONLY) // - .text("Current on L3")), - - POWER_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // - .text("Charging power on L1")), - - POWER_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // - .text("Charging power on L2")), - - POWER_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // - .text("Charging power on L3")), - POWER_TOTAL(Doc.of(OpenemsType.INTEGER) // .unit(Unit.WATT).accessMode(AccessMode.READ_ONLY) // .text("Sum of active charging power")), @@ -132,60 +108,6 @@ public default void setApplyChargePowerLimit(Integer value) throws OpenemsNamedE this.getApplyChargePowerLimitChannel().setNextWriteValue(value); } - /** - * Gets the Channel for {@link ChannelId#POWER_L1}. - * - * @return the Channel - */ - public default Channel getChargePowerL1Channel() { - return this.channel(ChannelId.POWER_L1); - } - - /** - * Gets the Power on phase L1 in [W]. See {@link ChannelId#POWER_L1}. - * - * @return the Channel {@link Value} - */ - public default Value getChargePowerL1() { - return this.getChargePowerL1Channel().value(); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_L2}. - * - * @return the Channel - */ - public default Channel getChargePowerL2Channel() { - return this.channel(ChannelId.POWER_L2); - } - - /** - * Gets the Power on phase L2 in [W]. See {@link ChannelId#POWER_L2}. - * - * @return the Channel {@link Value} - */ - public default Value getChargePowerL2() { - return this.getChargePowerL2Channel().value(); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_L3}. - * - * @return the Channel - */ - public default Channel getChargePowerL3Channel() { - return this.channel(ChannelId.POWER_L3); - } - - /** - * Gets the Power on phase L3 in [W]. See {@link ChannelId#POWER_L3}. - * - * @return the Channel {@link Value} - */ - public default Value getChargePowerL3() { - return this.getChargePowerL3Channel().value(); - } - /** * Gets the Channel for {@link ChannelId#POWER_TOTAL}. * diff --git a/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImpl.java b/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImpl.java index 71e6df3342a..cec1e44f812 100644 --- a/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImpl.java +++ b/io.openems.edge.evcs.spelsberg/src/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImpl.java @@ -44,6 +44,7 @@ import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -54,8 +55,8 @@ @EventTopics({ // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // }) -public class EvcsSpelsbergSmartImpl extends AbstractOpenemsModbusComponent - implements EvcsSpelsbergSmart, Evcs, ManagedEvcs, ModbusComponent, OpenemsComponent, EventHandler { +public class EvcsSpelsbergSmartImpl extends AbstractOpenemsModbusComponent implements EvcsSpelsbergSmart, Evcs, + ManagedEvcs, ModbusComponent, OpenemsComponent, EventHandler, ElectricityMeter { private final Logger log = LoggerFactory.getLogger(EvcsSpelsbergSmartImpl.class); private final ChargeStateHandler chargeStateHandler = new ChargeStateHandler(this); @@ -77,6 +78,7 @@ protected void setModbus(BridgeModbus modbus) { public EvcsSpelsbergSmartImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ModbusComponent.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // @@ -127,23 +129,24 @@ protected ModbusProtocol defineModbusProtocol() { m(EvcsSpelsbergSmart.ChannelId.CABLE_STATE, new UnsignedWordElement(1004))), new FC3ReadRegistersTask(1008, Priority.LOW, - m(EvcsSpelsbergSmart.ChannelId.CURRENT_L1, new UnsignedWordElement(1008)), + m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(1008)), new DummyRegisterElement(1009), - m(EvcsSpelsbergSmart.ChannelId.CURRENT_L2, new UnsignedWordElement(1010)), + m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(1010)), new DummyRegisterElement(1011), - m(EvcsSpelsbergSmart.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), + m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), new FC3ReadRegistersTask(1020, Priority.HIGH, - m(Evcs.ChannelId.CHARGE_POWER, new UnsignedDoublewordElement(1020)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER, new UnsignedDoublewordElement(1020)), + // TODO whats the difference between 1020 and 1022? m(EvcsSpelsbergSmart.ChannelId.POWER_TOTAL, new UnsignedDoublewordElement(1022)), - m(EvcsSpelsbergSmart.ChannelId.POWER_L1, new UnsignedDoublewordElement(1024)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(1024)), new DummyRegisterElement(1026, 1027), - m(EvcsSpelsbergSmart.ChannelId.POWER_L2, new UnsignedDoublewordElement(1028)), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(1028)), new DummyRegisterElement(1030, 1031), - m(EvcsSpelsbergSmart.ChannelId.POWER_L3, new UnsignedDoublewordElement(1032))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(1032))), new FC3ReadRegistersTask(1036, Priority.LOW, - m(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new UnsignedDoublewordElement(1036))), + m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, new UnsignedDoublewordElement(1036))), new FC3ReadRegistersTask(1100, Priority.LOW, m(EvcsSpelsbergSmart.ChannelId.MAX_HARDWARE_CURRENT, new UnsignedWordElement(1100)), @@ -290,21 +293,13 @@ private void addStatusCallback() { */ private void addPhaseDetectionCallback() { final Consumer> setPhasesCallback = ignore -> { - - var phases = 0; - if (this.getChargePowerL1().isDefined() && this.getChargePowerL1().get() > 0) { - phases++; - } - if (this.getChargePowerL2().isDefined() && this.getChargePowerL2().get() > 0) { - phases++; - } - if (this.getChargePowerL3().isDefined() && this.getChargePowerL3().get() > 0) { - phases++; - } - - this._setPhases(phases); + this._setPhases(Evcs.evaluatePhaseCount(// + this.getActivePowerL1().get(), // + this.getActivePowerL2().get(), // + this.getActivePowerL3().get())); }; + // TODO remove this channel this.getChargePowerTotalChannel().onUpdate(setPhasesCallback); } diff --git a/io.openems.edge.evcs.spelsberg/test/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImplTest.java b/io.openems.edge.evcs.spelsberg/test/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImplTest.java index c1b4be1ea43..61854c8a834 100644 --- a/io.openems.edge.evcs.spelsberg/test/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImplTest.java +++ b/io.openems.edge.evcs.spelsberg/test/io/openems/edge/evcs/spelsberg/smart/EvcsSpelsbergSmartImplTest.java @@ -8,17 +8,14 @@ public class EvcsSpelsbergSmartImplTest { - private static final String EVCS_ID = "evcs0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsSpelsbergSmartImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setModbusId(MODBUS_ID) // - .setId(EVCS_ID) // + .setModbusId("modbus0") // + .setId("evcs0") // .setModbusUnitId(255) // .setMaxHwCurrent(16000) // .setMinHwCurrent(6000) // diff --git a/io.openems.edge.evcs.webasto.next/bnd.bnd b/io.openems.edge.evcs.webasto.next/bnd.bnd index d8f6c880de2..dedafbd9bf3 100644 --- a/io.openems.edge.evcs.webasto.next/bnd.bnd +++ b/io.openems.edge.evcs.webasto.next/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} \ No newline at end of file diff --git a/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNext.java b/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNext.java index fe1dc68577d..06b57a37520 100644 --- a/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNext.java +++ b/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNext.java @@ -5,7 +5,6 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; -import io.openems.edge.common.channel.IntegerReadChannel; import io.openems.edge.common.channel.IntegerWriteChannel; import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; @@ -31,26 +30,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { EVSE_ERROR_CODE(Doc.of(EvseErrorCode.values())), // - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE)), // - - POWER_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT)), // - - POWER_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT)), // - - POWER_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT)), // - MAX_HW_CURRENT(Doc.of(OpenemsType.INTEGER) // .unit(Unit.AMPERE)), // @@ -161,58 +140,4 @@ public default Value getEvSetChargePowerLimit() { public default void setEvSetChargePowerLimit(Integer value) throws OpenemsNamedException { this.getEvSetChargePowerLimitChannel().setNextWriteValue(value); } - - /** - * Gets the Channel for {@link ChannelId#POWER_L1}. - * - * @return the Channel - */ - public default IntegerReadChannel getPowerL1Channel() { - return this.channel(ChannelId.POWER_L1); - } - - /** - * Gets the Power on phase 1 in [W]. See {@link ChannelId#POWER_L1}. - * - * @return the Channel {@link Value} - */ - public default Value getPowerL1() { - return this.getPowerL1Channel().value(); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_L2}. - * - * @return the Channel - */ - public default IntegerReadChannel getPowerL2Channel() { - return this.channel(ChannelId.POWER_L2); - } - - /** - * Gets the Power on phase 2 in [W]. See {@link ChannelId#POWER_L2}. - * - * @return the Channel {@link Value} - */ - public default Value getPowerL2() { - return this.getPowerL2Channel().value(); - } - - /** - * Gets the Channel for {@link ChannelId#POWER_L3}. - * - * @return the Channel - */ - public default IntegerReadChannel getPowerL3Channel() { - return this.channel(ChannelId.POWER_L3); - } - - /** - * Gets the Power on phase 3 in [W]. See {@link ChannelId#POWER_L3}. - * - * @return the Channel {@link Value} - */ - public default Value getPowerL3() { - return this.getPowerL3Channel().value(); - } } diff --git a/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImpl.java b/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImpl.java index d57c72553c9..c1748ed171a 100644 --- a/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImpl.java +++ b/io.openems.edge.evcs.webasto.next/src/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImpl.java @@ -45,6 +45,7 @@ import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.api.WriteHandler; import io.openems.edge.evcs.webasto.next.enums.ChargePointState; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -55,11 +56,10 @@ @EventTopics({ // EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) -public class EvcsWebastoNextImpl extends AbstractOpenemsModbusComponent - implements EvcsWebastoNext, Evcs, ManagedEvcs, ModbusComponent, OpenemsComponent, EventHandler { +public class EvcsWebastoNextImpl extends AbstractOpenemsModbusComponent implements EvcsWebastoNext, Evcs, ManagedEvcs, + ElectricityMeter, ModbusComponent, OpenemsComponent, EventHandler { private static final int DEFAULT_LIFE_BIT = 1; - private static final int DETECT_PHASE_ACTIVITY = 100; // W private final Logger log = LoggerFactory.getLogger(EvcsWebastoNext.class); @@ -84,6 +84,7 @@ protected void setModbus(BridgeModbus modbus) { public EvcsWebastoNextImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ModbusComponent.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // @@ -151,21 +152,21 @@ protected ModbusProtocol defineModbusProtocol() { new FC3ReadRegistersTask(1006, Priority.LOW, m(EvcsWebastoNext.ChannelId.EVSE_ERROR_CODE, new UnsignedWordElement(1006))), new FC3ReadRegistersTask(1008, Priority.LOW, - m(EvcsWebastoNext.ChannelId.CURRENT_L1, new UnsignedWordElement(1008))), + m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(1008))), new FC3ReadRegistersTask(1010, Priority.LOW, - m(EvcsWebastoNext.ChannelId.CURRENT_L2, new UnsignedWordElement(1010))), + m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(1010))), new FC3ReadRegistersTask(1012, Priority.LOW, - m(EvcsWebastoNext.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), + m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), new FC3ReadRegistersTask(1020, Priority.HIGH, - m(Evcs.ChannelId.CHARGE_POWER, new UnsignedDoublewordElement(1020))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER, new UnsignedDoublewordElement(1020))), new FC3ReadRegistersTask(1024, Priority.LOW, - m(EvcsWebastoNext.ChannelId.POWER_L1, new UnsignedDoublewordElement(1024))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(1024))), new FC3ReadRegistersTask(1028, Priority.LOW, - m(EvcsWebastoNext.ChannelId.POWER_L2, new UnsignedDoublewordElement(1028))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(1028))), new FC3ReadRegistersTask(1032, Priority.LOW, - m(EvcsWebastoNext.ChannelId.POWER_L3, new UnsignedDoublewordElement(1032))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(1032))), new FC3ReadRegistersTask(1036, Priority.LOW, - m(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY, new UnsignedDoublewordElement(1036))), + m(ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY, new UnsignedDoublewordElement(1036))), new FC3ReadRegistersTask(1100, Priority.LOW, m(EvcsWebastoNext.ChannelId.MAX_HW_CURRENT, new UnsignedWordElement(1100))), new FC3ReadRegistersTask(1102, Priority.LOW, @@ -241,25 +242,15 @@ private void addStatusListener() { } private void addPhasesListener() { - final Consumer> setPhases = ignore -> { - var phases = 0; - if (this.getPowerL1().orElse(0) > DETECT_PHASE_ACTIVITY) { - phases++; - } - if (this.getPowerL2().orElse(0) > DETECT_PHASE_ACTIVITY) { - phases++; - } - if (this.getPowerL3().orElse(0) > DETECT_PHASE_ACTIVITY) { - phases++; - } - if (phases == 0) { - phases = 3; - } - this._setPhases(phases); + final Consumer> setPhasesCallback = ignore -> { + this._setPhases(Evcs.evaluatePhaseCount(// + this.getActivePowerL1().get(), // + this.getActivePowerL2().get(), // + this.getActivePowerL3().get())); }; - this.getPowerL1Channel().onUpdate(setPhases); - this.getPowerL2Channel().onUpdate(setPhases); - this.getPowerL3Channel().onUpdate(setPhases); + this.getActivePowerL1Channel().onUpdate(setPhasesCallback); + this.getActivePowerL2Channel().onUpdate(setPhasesCallback); + this.getActivePowerL3Channel().onUpdate(setPhasesCallback); } @Override diff --git a/io.openems.edge.evcs.webasto.next/test/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImplTest.java b/io.openems.edge.evcs.webasto.next/test/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImplTest.java index b19ec679450..7ffc73c4d6a 100644 --- a/io.openems.edge.evcs.webasto.next/test/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImplTest.java +++ b/io.openems.edge.evcs.webasto.next/test/io/openems/edge/evcs/webasto/next/EvcsWebastoNextImplTest.java @@ -8,17 +8,14 @@ public class EvcsWebastoNextImplTest { - private static final String EVCS_ID = "evcs0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsWebastoNextImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setModbusId(MODBUS_ID) // - .setId(EVCS_ID) // + .setModbusId("modbus0") // + .setId("evcs0") // .setModbusUnitId(1) // .setMaxHwCurrent(32000) // .setMinHwCurrent(6000) // diff --git a/io.openems.edge.evcs.webasto.unite/bnd.bnd b/io.openems.edge.evcs.webasto.unite/bnd.bnd index 2f4e7fe8a62..a4ed98727d0 100644 --- a/io.openems.edge.evcs.webasto.unite/bnd.bnd +++ b/io.openems.edge.evcs.webasto.unite/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.bridge.modbus,\ io.openems.edge.common,\ io.openems.edge.evcs.api,\ + io.openems.edge.meter.api,\ -testpath: \ ${testpath} \ No newline at end of file diff --git a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUnite.java b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUnite.java index a7a8803ec81..1341e62a1c1 100644 --- a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUnite.java +++ b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUnite.java @@ -8,8 +8,10 @@ import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.WriteChannel; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.evcs.api.Evcs; +import io.openems.edge.meter.api.ElectricityMeter; -public interface EvcsWebastoUnite extends OpenemsComponent { +public interface EvcsWebastoUnite extends ElectricityMeter, Evcs, OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { SERIAL_NUMBER(Doc.of(OpenemsType.STRING) // @@ -41,36 +43,6 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { .accessMode(AccessMode.READ_ONLY)), // EVSE_FAULT_CODE(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_ONLY)), // - CURRENT_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - CURRENT_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - CURRENT_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.MILLIAMPERE) // - .accessMode(AccessMode.READ_ONLY)), // - VOLTAGE_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY)), // - VOLTAGE_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY)), // - VOLTAGE_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.VOLT) // - .accessMode(AccessMode.READ_ONLY)), // - ACTIVE_POWER_TOTAL(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY)), // - ACTIVE_POWER_L1(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY)), // - ACTIVE_POWER_L2(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY)), // - ACTIVE_POWER_L3(Doc.of(OpenemsType.INTEGER) // - .unit(Unit.WATT) // - .accessMode(AccessMode.READ_ONLY)), // METER_READING(Doc.of(OpenemsType.INTEGER) // .accessMode(AccessMode.READ_ONLY)), // SESSION_MAX_CURRENT(Doc.of(OpenemsType.INTEGER) // @@ -133,25 +105,6 @@ default void _setAliveValue(int value) throws OpenemsError.OpenemsNamedException channel.setNextWriteValue(value); } - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#ACTIVE_POWER_TOTAL}. - * - * @return the Channel - */ - default Channel getActivePowerChannel() { - return this.channel(ChannelId.ACTIVE_POWER_TOTAL); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#ACTIVE_POWER_TOTAL}. - * - * @return the Channel - */ - default int getActivePower() { - Channel channel = this.getActivePowerChannel(); - return channel.value().orElse(channel.getNextValue().orElse(0)); - } - /** * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CHARGE_POINT_STATE}. * @@ -171,63 +124,6 @@ default int getChargePointState() { return channel.value().orElse(channel.getNextValue().orElse(-1)); } - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L1}. - * - * @return the Channel - */ - default Channel getActivePowerL1Channel() { - return this.channel(ChannelId.ACTIVE_POWER_L1); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L1}. - * - * @return the Channel - */ - default int getActivePowerL1() { - Channel channel = this.getActivePowerL1Channel(); - return channel.value().orElse(channel.getNextValue().orElse(-1)); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L2}. - * - * @return the Channel - */ - default Channel getActivePowerL2Channel() { - return this.channel(ChannelId.ACTIVE_POWER_L2); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L2}. - * - * @return the Channel - */ - default int getActivePowerL2() { - Channel channel = this.getActivePowerL2Channel(); - return channel.value().orElse(channel.getNextValue().orElse(-1)); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L3}. - * - * @return the Channel - */ - default Channel getActivePowerL3Channel() { - return this.channel(ChannelId.ACTIVE_POWER_L3); - } - - /** - * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CURRENT_L3}. - * - * @return the Channel - */ - default int getActivePowerL3() { - Channel channel = this.getActivePowerL3Channel(); - return channel.value().orElse(channel.getNextValue().orElse(-1)); - } - /** * Gets the Channel for {@link EvcsWebastoUnite.ChannelId#CHARGING_CURRENT}. * diff --git a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImpl.java b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImpl.java index 8c0d5a821f6..460b3f7ac29 100644 --- a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImpl.java +++ b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImpl.java @@ -41,6 +41,7 @@ import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Phases; import io.openems.edge.evcs.api.WriteHandler; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -53,7 +54,7 @@ EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE // }) public class EvcsWebastoUniteImpl extends AbstractOpenemsModbusComponent - implements EvcsWebastoUnite, Evcs, ManagedEvcs, EventHandler, OpenemsComponent { + implements EvcsWebastoUnite, Evcs, ManagedEvcs, EventHandler, ElectricityMeter, OpenemsComponent { private final Logger log = LoggerFactory.getLogger(EvcsWebastoUniteImpl.class); @@ -74,6 +75,7 @@ public class EvcsWebastoUniteImpl extends AbstractOpenemsModbusComponent public EvcsWebastoUniteImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // Evcs.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // ModbusComponent.ChannelId.values(), // @@ -150,25 +152,25 @@ protected ModbusProtocol defineModbusProtocol() { new FC4ReadInputRegistersTask(1006, Priority.LOW, m(EvcsWebastoUnite.ChannelId.EVSE_FAULT_CODE, new UnsignedDoublewordElement(1006))), new FC4ReadInputRegistersTask(1008, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.CURRENT_L1, new UnsignedWordElement(1008))), + m(ElectricityMeter.ChannelId.CURRENT_L1, new UnsignedWordElement(1008))), new FC4ReadInputRegistersTask(1010, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.CURRENT_L2, new UnsignedWordElement(1010))), + m(ElectricityMeter.ChannelId.CURRENT_L2, new UnsignedWordElement(1010))), new FC4ReadInputRegistersTask(1012, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), + m(ElectricityMeter.ChannelId.CURRENT_L3, new UnsignedWordElement(1012))), new FC4ReadInputRegistersTask(1014, Priority.LOW, - m(EvcsWebastoUnite.ChannelId.VOLTAGE_L1, new UnsignedWordElement(1014))), + m(ElectricityMeter.ChannelId.VOLTAGE_L1, new UnsignedWordElement(1014))), new FC4ReadInputRegistersTask(1016, Priority.LOW, - m(EvcsWebastoUnite.ChannelId.VOLTAGE_L2, new UnsignedWordElement(1016))), + m(ElectricityMeter.ChannelId.VOLTAGE_L2, new UnsignedWordElement(1016))), new FC4ReadInputRegistersTask(1018, Priority.LOW, - m(EvcsWebastoUnite.ChannelId.VOLTAGE_L3, new UnsignedWordElement(1018))), + m(ElectricityMeter.ChannelId.VOLTAGE_L3, new UnsignedWordElement(1018))), new FC4ReadInputRegistersTask(1020, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.ACTIVE_POWER_TOTAL, new UnsignedDoublewordElement(1020))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER, new UnsignedDoublewordElement(1020))), new FC4ReadInputRegistersTask(1024, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(1024))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L1, new UnsignedDoublewordElement(1024))), new FC4ReadInputRegistersTask(1028, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(1028))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L2, new UnsignedDoublewordElement(1028))), new FC4ReadInputRegistersTask(1032, Priority.HIGH, - m(EvcsWebastoUnite.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(1032))), + m(ElectricityMeter.ChannelId.ACTIVE_POWER_L3, new UnsignedDoublewordElement(1032))), new FC4ReadInputRegistersTask(1036, Priority.LOW, m(EvcsWebastoUnite.ChannelId.METER_READING, new UnsignedDoublewordElement(1036))), new FC4ReadInputRegistersTask(1100, Priority.LOW, diff --git a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/WebastoReadHandler.java b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/WebastoReadHandler.java index 3018446dacc..68a8e7fdbbb 100644 --- a/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/WebastoReadHandler.java +++ b/io.openems.edge.evcs.webasto.unite/src/io/openems/edge/evcs/webasto/unite/WebastoReadHandler.java @@ -1,5 +1,7 @@ package io.openems.edge.evcs.webasto.unite; +import static io.openems.edge.evcs.api.Evcs.evaluatePhaseCount; + import io.openems.edge.evcs.api.Status; // TODO: Can also be done by registering onSetNextValue listeners on the depending channel in the WebastoImpl. @@ -14,54 +16,28 @@ protected WebastoReadHandler(EvcsWebastoUniteImpl parent) { protected void run() { this.setPhaseCount(); this.setStatus(); - this.parent._setChargePower((int) this.parent.getActivePower()); } private void setStatus() { - switch (this.parent.getChargePointState()) { - case (0): - this.parent._setStatus(Status.NOT_READY_FOR_CHARGING); - break; - case (1): - this.parent._setStatus(Status.READY_FOR_CHARGING); - break; - case (2): - this.parent._setStatus(Status.CHARGING); - break; - case (3): - case (4): - this.parent._setStatus(Status.CHARGING_REJECTED); - break; - case (5): - // TODO Check if this state is also reached while paused - this.parent._setStatus(Status.CHARGING_FINISHED); - break; - case (7): - case (8): - this.parent._setStatus(Status.ERROR); - } + this.parent._setStatus(switch (this.parent.getChargePointState()) { + case 0 -> Status.NOT_READY_FOR_CHARGING; + case 1 -> Status.READY_FOR_CHARGING; + case 2 -> Status.CHARGING; + case 3, 4 -> Status.CHARGING_REJECTED; + case 5 -> Status.CHARGING_FINISHED; + // TODO Check if this state is also reached while paused + case 7, 8 -> Status.ERROR; + default -> null; + }); } /** * Writes the Amount of Phases in the Phase channel. */ private void setPhaseCount() { - int phases = 0; - /* - * The EVCS will pull power from the grid for its own consumption and report - * that on one of the phases. This value is different from EVCS to EVCS but can - * be high. Because of this, this will only register a Phase starting with 100W - * because then we definitively know that this load is caused by a car. - */ - if (this.parent.getActivePowerL1() >= 100) { - phases += 1; - } - if (this.parent.getActivePowerL2() >= 100) { - phases += 1; - } - if (this.parent.getActivePowerL3() >= 100) { - phases += 1; - } - this.parent._setPhases(phases); + this.parent._setPhases(evaluatePhaseCount(// + this.parent.getActivePowerL1().get(), // + this.parent.getActivePowerL2().get(), // + this.parent.getActivePowerL3().get())); } } diff --git a/io.openems.edge.evcs.webasto.unite/test/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImplTest.java b/io.openems.edge.evcs.webasto.unite/test/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImplTest.java index 41bc0851964..c5bea2c96cb 100644 --- a/io.openems.edge.evcs.webasto.unite/test/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImplTest.java +++ b/io.openems.edge.evcs.webasto.unite/test/io/openems/edge/evcs/webasto/unite/EvcsWebastoUniteImplTest.java @@ -8,17 +8,14 @@ public class EvcsWebastoUniteImplTest { - private static final String EVCS_ID = "evcs0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new EvcsWebastoUniteImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setModbusId(MODBUS_ID) // - .setId(EVCS_ID) // + .setModbusId("modbus0") // + .setId("evcs0") // .setModbusUnitId(255) // .setMaxHwCurrent(32_000) // .setMinHwCurrent(6_000) // diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger1Test.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger1Test.java index 20325ec4cbf..146b7240c85 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger1Test.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger1Test.java @@ -10,19 +10,15 @@ public class FeneconDessCharger1Test { - private static final String CHARGER_ID = "charger0"; - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { var ess = new FeneconDessEssImpl(); new ComponentTest(ess) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyEssConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .build()) // ; @@ -30,9 +26,9 @@ public void test() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyChargerConfig.create() // - .setId(CHARGER_ID) // - .setEssId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("charger0") // + .setEssId("ess0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger2Test.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger2Test.java index 879abb0d8fe..18f69c54492 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger2Test.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/charger/FeneconDessCharger2Test.java @@ -10,19 +10,15 @@ public class FeneconDessCharger2Test { - private static final String CHARGER_ID = "charger1"; - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { var ess = new FeneconDessEssImpl(); new ComponentTest(ess) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyEssConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .build()) // ; @@ -30,9 +26,9 @@ public void test() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("ess", ess) // .activate(MyChargerConfig.create() // - .setId(CHARGER_ID) // - .setEssId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("charger1") // + .setEssId("ess0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/ess/FeneconDessEssImplTest.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/ess/FeneconDessEssImplTest.java index 9a71817d9c8..8fb795dd74d 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/ess/FeneconDessEssImplTest.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/ess/FeneconDessEssImplTest.java @@ -8,17 +8,14 @@ public class FeneconDessEssImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconDessEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyEssConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImplTest.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImplTest.java index fd85f499448..a29b0a99e7c 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImplTest.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/gridmeter/FeneconDessGridMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconDessGridMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconDessGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImplTest.java b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImplTest.java index c849a026396..4af1a5dfcd3 100644 --- a/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImplTest.java +++ b/io.openems.edge.fenecon.dess/test/io/openems/edge/fenecon/dess/pvmeter/FeneconDessPvMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconDessPvMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconDessPvMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/ess/FeneconMiniEssImplTest.java b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/ess/FeneconMiniEssImplTest.java index 7535b25a0c3..b83208448ac 100644 --- a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/ess/FeneconMiniEssImplTest.java +++ b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/ess/FeneconMiniEssImplTest.java @@ -1,8 +1,11 @@ package io.openems.edge.fenecon.mini.ess; +import static io.openems.edge.fenecon.mini.ess.FeneconMiniEss.ChannelId.PCS_MODE; +import static io.openems.edge.fenecon.mini.ess.FeneconMiniEss.ChannelId.SETUP_MODE; +import static io.openems.edge.fenecon.mini.ess.FeneconMiniEss.ChannelId.STATE_MACHINE; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyConfigurationAdmin; @@ -13,14 +16,6 @@ public class FeneconMiniEssImplTest { - private static final String MODBUS_ID = "modbus0"; - - private static final String ESS_ID = "ess0"; - - private static final ChannelAddress ESS_STATE_MACHINE = new ChannelAddress(ESS_ID, "StateMachine"); - private static final ChannelAddress ESS_PCS_MODE = new ChannelAddress(ESS_ID, "PcsMode"); - private static final ChannelAddress ESS_SETUP_MODE = new ChannelAddress(ESS_ID, "SetupMode"); - /** * Tests activating write-mode when it was not activated before. * @@ -31,34 +26,34 @@ public void testWriteModeSet() throws Exception { new ManagedSymmetricEssTest(new FeneconMiniEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(SinglePhase.L1) // .setReadonly(false) // .build()) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.ECONOMIC) // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .input(PCS_MODE, PcsMode.ECONOMIC) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_WRITE_MODE)) // + .output(STATE_MACHINE, State.GO_WRITE_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_1)) // + .output(STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_1)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.ON) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_2)) // + .input(SETUP_MODE, SetupMode.ON) // + .output(STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_2)) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.DEBUG) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_3)) // + .input(PCS_MODE, PcsMode.DEBUG) // + .output(STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_3)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_4)) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.ACTIVATE_DEBUG_MODE_4)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_WRITE_MODE)) // + .output(STATE_MACHINE, State.GO_WRITE_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.WRITE_MODE)) // + .output(STATE_MACHINE, State.WRITE_MODE)) // ; } @@ -72,21 +67,21 @@ public void testWriteModeAlreadySet() throws Exception { new ManagedSymmetricEssTest(new FeneconMiniEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(SinglePhase.L1) // .setReadonly(false) // .build()) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.DEBUG) // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .input(PCS_MODE, PcsMode.DEBUG) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_WRITE_MODE)) // + .output(STATE_MACHINE, State.GO_WRITE_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.WRITE_MODE)) // + .output(STATE_MACHINE, State.WRITE_MODE)) // ; } @@ -100,34 +95,34 @@ public void testReadonlyModeSet() throws Exception { new ManagedSymmetricEssTest(new FeneconMiniEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(SinglePhase.L1) // .setReadonly(true) // .build()) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.DEBUG) // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .input(PCS_MODE, PcsMode.DEBUG) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_READONLY_MODE)) // + .output(STATE_MACHINE, State.GO_READONLY_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_1)) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_1)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.ON) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_2)) // + .input(SETUP_MODE, SetupMode.ON) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_2)) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.ECONOMIC) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_3)) // + .input(PCS_MODE, PcsMode.ECONOMIC) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_3)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_4)) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_4)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_READONLY_MODE)) // + .output(STATE_MACHINE, State.GO_READONLY_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.READONLY_MODE)) // + .output(STATE_MACHINE, State.READONLY_MODE)) // ; } @@ -141,34 +136,34 @@ public void testReadonlyModeAlreadySet() throws Exception { new ManagedSymmetricEssTest(new FeneconMiniEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .setPhase(SinglePhase.L1) // .setReadonly(true) // .build()) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.DEBUG) // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.UNDEFINED)) // + .input(PCS_MODE, PcsMode.DEBUG) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.UNDEFINED)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_READONLY_MODE)) // + .output(STATE_MACHINE, State.GO_READONLY_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_1)) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_1)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.ON) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_2)) // + .input(SETUP_MODE, SetupMode.ON) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_2)) // .next(new TestCase() // - .input(ESS_PCS_MODE, PcsMode.ECONOMIC) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_3)) // + .input(PCS_MODE, PcsMode.ECONOMIC) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_3)) // .next(new TestCase() // - .input(ESS_SETUP_MODE, SetupMode.OFF) // - .output(ESS_STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_4)) // + .input(SETUP_MODE, SetupMode.OFF) // + .output(STATE_MACHINE, State.ACTIVATE_ECONOMIC_MODE_4)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.GO_READONLY_MODE)) // + .output(STATE_MACHINE, State.GO_READONLY_MODE)) // .next(new TestCase() // - .output(ESS_STATE_MACHINE, State.READONLY_MODE)) // + .output(STATE_MACHINE, State.READONLY_MODE)) // ; } } diff --git a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImplTest.java b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImplTest.java index 9f22123b8b5..e478c89f29f 100644 --- a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImplTest.java +++ b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/gridmeter/FeneconMiniGridMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconMiniGridMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconMiniGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImplTest.java b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImplTest.java index 143b1e2d51a..59e45468495 100644 --- a/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImplTest.java +++ b/io.openems.edge.fenecon.mini/test/io/openems/edge/fenecon/mini/pvmeter/FeneconMiniPvMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconMiniPvMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconMiniPvMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/ess/FeneconProEssImplTest.java b/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/ess/FeneconProEssImplTest.java index 05e9ee82109..31a40767aca 100644 --- a/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/ess/FeneconProEssImplTest.java +++ b/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/ess/FeneconProEssImplTest.java @@ -9,18 +9,15 @@ public class FeneconProEssImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconProEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("power", new DummyPower()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("ess0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImplTest.java b/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImplTest.java index d9163f8438f..d46d42f0cc1 100644 --- a/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImplTest.java +++ b/io.openems.edge.fenecon.pro/test/io/openems/edge/fenecon/pro/pvmeter/FeneconProPvMeterImplTest.java @@ -8,17 +8,14 @@ public class FeneconProPvMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new FeneconProPvMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java index 5473b431528..c8180b66657 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/batteryinverter/GoodWeBatteryInverterImplTest.java @@ -1,13 +1,45 @@ package io.openems.edge.goodwe.batteryinverter; +import static io.openems.edge.battery.api.Battery.ChannelId.CHARGE_MAX_CURRENT; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.batteryinverter.api.SymmetricBatteryInverter.ChannelId.MAX_APPARENT_POWER; +import static io.openems.edge.common.sum.Sum.ChannelId.GRID_ACTIVE_POWER; +import static io.openems.edge.ess.dccharger.api.EssDcCharger.ChannelId.ACTUAL_POWER; +import static io.openems.edge.ess.dccharger.api.EssDcCharger.ChannelId.CURRENT; +import static io.openems.edge.ess.dccharger.api.EssDcCharger.ChannelId.VOLTAGE; +import static io.openems.edge.goodwe.GoodWeConstants.DEFAULT_UNIT_ID; import static io.openems.edge.goodwe.batteryinverter.GoodWeBatteryInverterImpl.doSetBmsVoltage; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.EMS_POWER_MODE; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.EMS_POWER_SET; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MAX_AC_EXPORT; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MAX_AC_IMPORT; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.METER_COMMUNICATE_STATUS; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT1_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT1_P; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT2_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT2_P; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT3_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.MPPT3_P; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV1_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV1_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV2_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV2_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV3_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV3_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV4_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV4_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV5_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV5_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV6_I; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.TWO_S_PV6_V; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.WBMS_CHARGE_MAX_CURRENT; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.WBMS_DISCHARGE_MAX_CURRENT; +import static io.openems.edge.goodwe.common.GoodWe.ChannelId.WBMS_VOLTAGE; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.channel.value.Value; @@ -18,7 +50,6 @@ import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.test.DummyPower; -import io.openems.edge.goodwe.GoodWeConstants; import io.openems.edge.goodwe.charger.singlestring.GoodWeChargerPv1; import io.openems.edge.goodwe.charger.twostring.GoodWeChargerTwoStringImpl; import io.openems.edge.goodwe.charger.twostring.PvPort; @@ -32,85 +63,17 @@ @SuppressWarnings("deprecation") public class GoodWeBatteryInverterImplTest { - private static final String MODBUS_ID = "modbus0"; - private static final String BATTERY_ID = "battery0"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - private static final String CHARGER_ID = "charger0"; - private static final String CHARGER_2_ID = "charger1"; - private static final String CHARGER_3_ID = "charger2"; - private static final String CHARGER_4_ID = "charger3"; - private static final String CHARGER_5_ID = "charger4"; - private static final String CHARGER_6_ID = "charger5"; - private static final String SUM_ID = "_sum"; - - private static final Battery BATTERY = new DummyBattery(BATTERY_ID); - - private static final ChannelAddress EMS_POWER_MODE = new ChannelAddress(BATTERY_INVERTER_ID, "EmsPowerMode"); - private static final ChannelAddress EMS_POWER_SET = new ChannelAddress(BATTERY_INVERTER_ID, "EmsPowerSet"); - private static final ChannelAddress METER_COMMUNICATE_STATUS = new ChannelAddress(BATTERY_INVERTER_ID, - "MeterCommunicateStatus"); - private static final ChannelAddress MAX_AC_IMPORT = new ChannelAddress(BATTERY_INVERTER_ID, "MaxAcImport"); - private static final ChannelAddress MAX_AC_EXPORT = new ChannelAddress(BATTERY_INVERTER_ID, "MaxAcExport"); - private static final ChannelAddress GRID_ACTIVE_POWER = new ChannelAddress(SUM_ID, "GridActivePower"); - private static final ChannelAddress ACTIVE_POWER = new ChannelAddress(BATTERY_INVERTER_ID, "ActivePower"); - private static final ChannelAddress CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_ID, "ChargeMaxCurrent"); - private static final ChannelAddress WBMS_CHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_INVERTER_ID, - "WbmsChargeMaxCurrent"); - private static final ChannelAddress WBMS_DISCHARGE_MAX_CURRENT = new ChannelAddress(BATTERY_INVERTER_ID, - "WbmsDischargeMaxCurrent"); - private static final ChannelAddress WBMS_VOLTAGE = new ChannelAddress(BATTERY_INVERTER_ID, "WbmsVoltage"); - private static final ChannelAddress MAX_APPARENT_POWER = new ChannelAddress(BATTERY_INVERTER_ID, - "MaxApparentPower"); - - private static final ChannelAddress CHARGER_ACTUAL_POWER = new ChannelAddress(CHARGER_ID, "ActualPower"); - private static final ChannelAddress CHARGER_VOLTAGE = new ChannelAddress(CHARGER_ID, "Voltage"); - private static final ChannelAddress CHARGER_CURRENT = new ChannelAddress(CHARGER_ID, "Current"); - private static final ChannelAddress CHARGER_2_ACTUAL_POWER = new ChannelAddress(CHARGER_2_ID, "ActualPower"); - private static final ChannelAddress CHARGER_2_VOLTAGE = new ChannelAddress(CHARGER_2_ID, "Voltage"); - private static final ChannelAddress CHARGER_2_CURRENT = new ChannelAddress(CHARGER_2_ID, "Current"); - private static final ChannelAddress CHARGER_3_ACTUAL_POWER = new ChannelAddress(CHARGER_3_ID, "ActualPower"); - private static final ChannelAddress CHARGER_3_VOLTAGE = new ChannelAddress(CHARGER_3_ID, "Voltage"); - private static final ChannelAddress CHARGER_3_CURRENT = new ChannelAddress(CHARGER_3_ID, "Current"); - private static final ChannelAddress CHARGER_4_ACTUAL_POWER = new ChannelAddress(CHARGER_4_ID, "ActualPower"); - private static final ChannelAddress CHARGER_4_VOLTAGE = new ChannelAddress(CHARGER_4_ID, "Voltage"); - private static final ChannelAddress CHARGER_4_CURRENT = new ChannelAddress(CHARGER_4_ID, "Current"); - private static final ChannelAddress CHARGER_5_ACTUAL_POWER = new ChannelAddress(CHARGER_5_ID, "ActualPower"); - private static final ChannelAddress CHARGER_5_VOLTAGE = new ChannelAddress(CHARGER_5_ID, "Voltage"); - private static final ChannelAddress CHARGER_5_CURRENT = new ChannelAddress(CHARGER_5_ID, "Current"); - private static final ChannelAddress CHARGER_6_ACTUAL_POWER = new ChannelAddress(CHARGER_6_ID, "ActualPower"); - private static final ChannelAddress CHARGER_6_VOLTAGE = new ChannelAddress(CHARGER_6_ID, "Voltage"); - private static final ChannelAddress CHARGER_6_CURRENT = new ChannelAddress(CHARGER_6_ID, "Current"); - - private static final ChannelAddress MPPT1_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt1P"); - private static final ChannelAddress MPPT1_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt1I"); - private static final ChannelAddress MPPT2_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt2P"); - private static final ChannelAddress MPPT2_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt2I"); - private static final ChannelAddress MPPT3_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt3P"); - private static final ChannelAddress MPPT3_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt3I"); - private static final ChannelAddress TWO_S_PV1_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv1I"); - private static final ChannelAddress TWO_S_PV1_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv1V"); - private static final ChannelAddress TWO_S_PV2_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv2I"); - private static final ChannelAddress TWO_S_PV2_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv2V"); - private static final ChannelAddress TWO_S_PV3_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv3I"); - private static final ChannelAddress TWO_S_PV3_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv3V"); - private static final ChannelAddress TWO_S_PV4_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv4I"); - private static final ChannelAddress TWO_S_PV4_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv4V"); - private static final ChannelAddress TWO_S_PV5_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv5I"); - private static final ChannelAddress TWO_S_PV5_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv5V"); - private static final ChannelAddress TWO_S_PV6_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv6I"); - private static final ChannelAddress TWO_S_PV6_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv6V"); - @Test public void testEt() throws Exception { var charger = new GoodWeChargerPv1(); new ComponentTest(charger) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.goodwe.charger.singlestring.MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("charger0") // + .setBatteryInverterId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .build()); var ess = new GoodWeBatteryInverterImpl(); @@ -119,13 +82,13 @@ public void testEt() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -140,9 +103,9 @@ public void testEt() throws Exception { .input(ACTIVE_POWER, 0) // .input(MAX_AC_IMPORT, 0) // .input(MAX_AC_EXPORT, 0) // - .input(CHARGER_ACTUAL_POWER, 2000) // + .input("charger0", ACTUAL_POWER, 2000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 1000, 0); + ess.run(new DummyBattery("battery0"), 1000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.CHARGE_BAT) // .output(EMS_POWER_SET, 1000)); @@ -155,12 +118,12 @@ public void testNegativSetActivePoint() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -176,7 +139,7 @@ public void testNegativSetActivePoint() throws Exception { .input(MAX_AC_IMPORT, 0) // .input(MAX_AC_EXPORT, 0) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, -1000, 0); + ess.run(new DummyBattery("battery0"), -1000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.CHARGE_BAT) // .output(EMS_POWER_SET, 1000)); @@ -189,12 +152,12 @@ public void testDischargeBattery() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -210,7 +173,7 @@ public void testDischargeBattery() throws Exception { .input(MAX_AC_IMPORT, 0) // .input(MAX_AC_EXPORT, 0) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 1000, 0); + ess.run(new DummyBattery("battery0"), 1000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.DISCHARGE_BAT) // .output(EMS_POWER_SET, 1000)); @@ -223,12 +186,12 @@ public void testEmsPowerModeAutoWithBalancing() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -243,7 +206,7 @@ public void testEmsPowerModeAutoWithBalancing() throws Exception { .input(GRID_ACTIVE_POWER, 2000) // .input(ACTIVE_POWER, 4000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 6000, 0); + ess.run(new DummyBattery("battery0"), 6000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -254,12 +217,12 @@ public void testEmsPowerModeAutoWithSurplus() throws Exception { var charger = new GoodWeChargerPv1(); new ComponentTest(charger) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.goodwe.charger.singlestring.MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("charger0") // + .setBatteryInverterId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .build()); var ess = new GoodWeBatteryInverterImpl(); @@ -267,15 +230,15 @@ public void testEmsPowerModeAutoWithSurplus() throws Exception { new ComponentTest(ess) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("componentManager", new DummyComponentManager()) // .addReference("sum", new DummySum()) // .addComponent(charger) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -287,9 +250,9 @@ public void testEmsPowerModeAutoWithSurplus() throws Exception { .build()) // .next(new TestCase() // .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // - .input(CHARGER_ACTUAL_POWER, 10000) // - .input(CHARGE_MAX_CURRENT, 20).onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 10000, 0); + .input("charger0", ACTUAL_POWER, 10000) // + .input("battery0", CHARGE_MAX_CURRENT, 20).onExecuteWriteCallbacks(() -> { + ess.run(new DummyBattery("battery0"), 10000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -302,12 +265,12 @@ public void testEmsPowerModeAutoWithMaxAcImport() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -321,7 +284,7 @@ public void testEmsPowerModeAutoWithMaxAcImport() throws Exception { .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // .input(MAX_AC_IMPORT, 3000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 3000, 0); + ess.run(new DummyBattery("battery0"), 3000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -334,12 +297,12 @@ public void testEmsPowerModeAutoWithMaxAcExport() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -353,7 +316,7 @@ public void testEmsPowerModeAutoWithMaxAcExport() throws Exception { .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // .input(MAX_AC_EXPORT, 8000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 8000, 0); + ess.run(new DummyBattery("battery0"), 8000, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -366,12 +329,12 @@ public void testBatteryIsFull() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -385,7 +348,7 @@ public void testBatteryIsFull() throws Exception { .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // .input(MAX_AC_IMPORT, 0) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 0, 0); + ess.run(new DummyBattery("battery0"), 0, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -398,12 +361,12 @@ public void testBatteryIsEmpty() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -417,7 +380,7 @@ public void testBatteryIsEmpty() throws Exception { .input(METER_COMMUNICATE_STATUS, MeterCommunicateStatus.OK) // .input(MAX_AC_EXPORT, 0) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 0, 0); + ess.run(new DummyBattery("battery0"), 0, 0); }) // .output(EMS_POWER_MODE, EmsPowerMode.AUTO) // .output(EMS_POWER_SET, 0)); @@ -430,12 +393,12 @@ public void testAcCalculation() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // - .addComponent(BATTERY).activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .addComponent(new DummyBattery("battery0")).activate(MyConfig.create() // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -451,7 +414,7 @@ public void testAcCalculation() throws Exception { .input(WBMS_VOLTAGE, 325) // .input(MAX_APPARENT_POWER, 10000) // .onExecuteWriteCallbacks(() -> { - ess.run(BATTERY, 0, 0); + ess.run(new DummyBattery("battery0"), 0, 0); }) // .output(MAX_AC_IMPORT, 0) // .output(MAX_AC_EXPORT, 325)); @@ -471,8 +434,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger0") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_1) // .build()); @@ -480,8 +443,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_2_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger1") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_2) // .build()); @@ -489,8 +452,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_3_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger2") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_3) // .build()); @@ -498,8 +461,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_4_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger3") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_4) // .build()); @@ -507,8 +470,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_5_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger4") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_5) // .build()); @@ -516,8 +479,8 @@ public void testTwoStringCharger() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", ess) // .activate(io.openems.edge.goodwe.charger.twostring.MyConfig.create() // - .setId(CHARGER_6_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger5") // + .setBatteryInverterId("batteryInverter0") // .setPvPort(PvPort.PV_6) // .build()); @@ -527,15 +490,15 @@ public void testTwoStringCharger() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger1) // .addComponent(charger2) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -554,19 +517,19 @@ public void testTwoStringCharger() throws Exception { .input(TWO_S_PV2_V, 240) // // Values applied in the next cycle - .output(CHARGER_ACTUAL_POWER, 0) // - .output(CHARGER_2_ACTUAL_POWER, 0) // - .output(CHARGER_CURRENT, null) // - .output(CHARGER_2_CURRENT, null) // - .output(CHARGER_VOLTAGE, null) // - .output(CHARGER_2_VOLTAGE, null)) // + .output("charger0", ACTUAL_POWER, 0) // + .output("charger1", ACTUAL_POWER, 0) // + .output("charger0", CURRENT, null) // + .output("charger1", CURRENT, null) // + .output("charger0", VOLTAGE, null) // + .output("charger1", VOLTAGE, null)) // .next(new TestCase() // - .output(CHARGER_ACTUAL_POWER, 1000) // - .output(CHARGER_2_ACTUAL_POWER, 1000) // - .output(CHARGER_CURRENT, 10) // - .output(CHARGER_2_CURRENT, 10) // - .output(CHARGER_VOLTAGE, 240) // - .output(CHARGER_2_VOLTAGE, 240)) // + .output("charger0", ACTUAL_POWER, 1000) // + .output("charger1", ACTUAL_POWER, 1000) // + .output("charger0", CURRENT, 10) // + .output("charger1", CURRENT, 10) // + .output("charger0", VOLTAGE, 240) // + .output("charger1", VOLTAGE, 240)) // // Chargers with different current values .next(new TestCase() // @@ -574,22 +537,22 @@ public void testTwoStringCharger() throws Exception { .input(MPPT1_P, 2000) // .input(TWO_S_PV1_I, 5) // .input(TWO_S_PV2_I, 15) // - .output(CHARGER_ACTUAL_POWER, 1000) // - .output(CHARGER_2_ACTUAL_POWER, 1000)) // + .output("charger0", ACTUAL_POWER, 1000) // + .output("charger1", ACTUAL_POWER, 1000)) // .next(new TestCase() // - .output(CHARGER_ACTUAL_POWER, 500) // - .output(CHARGER_2_ACTUAL_POWER, 1500)) // + .output("charger0", ACTUAL_POWER, 500) // + .output("charger1", ACTUAL_POWER, 1500)) // .next(new TestCase() // .input(MPPT1_I, 20) // .input(MPPT1_P, 2000) // .input(TWO_S_PV1_I, 20) // .input(TWO_S_PV2_I, 0) // - .output(CHARGER_ACTUAL_POWER, 500) // - .output(CHARGER_2_ACTUAL_POWER, 1500)) // + .output("charger0", ACTUAL_POWER, 500) // + .output("charger1", ACTUAL_POWER, 1500)) // .next(new TestCase() // - .output(CHARGER_ACTUAL_POWER, 2000) // - .output(CHARGER_2_ACTUAL_POWER, 0) // + .output("charger0", ACTUAL_POWER, 2000) // + .output("charger1", ACTUAL_POWER, 0) // ); /* @@ -601,15 +564,15 @@ public void testTwoStringCharger() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger3) // .addComponent(charger4) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -628,19 +591,19 @@ public void testTwoStringCharger() throws Exception { .input(TWO_S_PV4_V, 240) // // Values applied in the next cycle - .output(CHARGER_3_ACTUAL_POWER, 0) // - .output(CHARGER_4_ACTUAL_POWER, 0) // - .output(CHARGER_3_CURRENT, null) // - .output(CHARGER_4_CURRENT, null) // - .output(CHARGER_3_VOLTAGE, null) // - .output(CHARGER_4_VOLTAGE, null)) // + .output("charger2", ACTUAL_POWER, 0) // + .output("charger3", ACTUAL_POWER, 0) // + .output("charger2", CURRENT, null) // + .output("charger3", CURRENT, null) // + .output("charger2", VOLTAGE, null) // + .output("charger3", VOLTAGE, null)) // .next(new TestCase() // - .output(CHARGER_3_ACTUAL_POWER, 1000) // - .output(CHARGER_4_ACTUAL_POWER, 1000) // - .output(CHARGER_3_CURRENT, 10) // - .output(CHARGER_4_CURRENT, 10) // - .output(CHARGER_3_VOLTAGE, 240) // - .output(CHARGER_4_VOLTAGE, 240)) // + .output("charger2", ACTUAL_POWER, 1000) // + .output("charger3", ACTUAL_POWER, 1000) // + .output("charger2", CURRENT, 10) // + .output("charger3", CURRENT, 10) // + .output("charger2", VOLTAGE, 240) // + .output("charger3", VOLTAGE, 240)) // // Chargers with different current values .next(new TestCase() // @@ -648,22 +611,22 @@ public void testTwoStringCharger() throws Exception { .input(MPPT2_P, 2000) // .input(TWO_S_PV3_I, 5) // .input(TWO_S_PV4_I, 15) // - .output(CHARGER_3_ACTUAL_POWER, 1000) // - .output(CHARGER_4_ACTUAL_POWER, 1000)) // + .output("charger2", ACTUAL_POWER, 1000) // + .output("charger3", ACTUAL_POWER, 1000)) // .next(new TestCase() // - .output(CHARGER_3_ACTUAL_POWER, 500) // - .output(CHARGER_4_ACTUAL_POWER, 1500)) // + .output("charger2", ACTUAL_POWER, 500) // + .output("charger3", ACTUAL_POWER, 1500)) // .next(new TestCase() // .input(MPPT2_I, 20) // .input(MPPT2_P, 2000) // .input(TWO_S_PV3_I, 20) // .input(TWO_S_PV4_I, 0) // - .output(CHARGER_3_ACTUAL_POWER, 500) // - .output(CHARGER_4_ACTUAL_POWER, 1500)) // + .output("charger2", ACTUAL_POWER, 500) // + .output("charger3", ACTUAL_POWER, 1500)) // .next(new TestCase() // - .output(CHARGER_3_ACTUAL_POWER, 2000) // - .output(CHARGER_4_ACTUAL_POWER, 0) // + .output("charger2", ACTUAL_POWER, 2000) // + .output("charger3", ACTUAL_POWER, 0) // ); /* @@ -675,15 +638,15 @@ public void testTwoStringCharger() throws Exception { .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger5) // .addComponent(charger6) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // .setBackupEnable(EnableDisable.ENABLE) // @@ -702,19 +665,19 @@ public void testTwoStringCharger() throws Exception { .input(TWO_S_PV6_V, 240) // // Values applied in the next cycle - .output(CHARGER_5_ACTUAL_POWER, 0) // - .output(CHARGER_6_ACTUAL_POWER, 0) // - .output(CHARGER_5_CURRENT, null) // - .output(CHARGER_6_CURRENT, null) // - .output(CHARGER_5_VOLTAGE, null) // - .output(CHARGER_6_VOLTAGE, null)) // + .output("charger4", ACTUAL_POWER, 0) // + .output("charger5", ACTUAL_POWER, 0) // + .output("charger4", CURRENT, null) // + .output("charger5", CURRENT, null) // + .output("charger4", VOLTAGE, null) // + .output("charger5", VOLTAGE, null)) // .next(new TestCase() // - .output(CHARGER_5_ACTUAL_POWER, 1000) // - .output(CHARGER_6_ACTUAL_POWER, 1000) // - .output(CHARGER_5_CURRENT, 10) // - .output(CHARGER_6_CURRENT, 10) // - .output(CHARGER_5_VOLTAGE, 240) // - .output(CHARGER_6_VOLTAGE, 240)) // + .output("charger4", ACTUAL_POWER, 1000) // + .output("charger5", ACTUAL_POWER, 1000) // + .output("charger4", CURRENT, 10) // + .output("charger5", CURRENT, 10) // + .output("charger4", VOLTAGE, 240) // + .output("charger5", VOLTAGE, 240)) // // Chargers with different current values .next(new TestCase() // @@ -722,22 +685,22 @@ public void testTwoStringCharger() throws Exception { .input(MPPT3_P, 2000) // .input(TWO_S_PV5_I, 5) // .input(TWO_S_PV6_I, 15) // - .output(CHARGER_5_ACTUAL_POWER, 1000) // - .output(CHARGER_6_ACTUAL_POWER, 1000)) // + .output("charger4", ACTUAL_POWER, 1000) // + .output("charger5", ACTUAL_POWER, 1000)) // .next(new TestCase() // - .output(CHARGER_5_ACTUAL_POWER, 500) // - .output(CHARGER_6_ACTUAL_POWER, 1500)) // + .output("charger4", ACTUAL_POWER, 500) // + .output("charger5", ACTUAL_POWER, 1500)) // .next(new TestCase() // .input(MPPT3_I, 20) // .input(MPPT3_P, 2000) // .input(TWO_S_PV5_I, 20) // .input(TWO_S_PV6_I, 0) // - .output(CHARGER_5_ACTUAL_POWER, 500) // - .output(CHARGER_6_ACTUAL_POWER, 1500)) // + .output("charger4", ACTUAL_POWER, 500) // + .output("charger5", ACTUAL_POWER, 1500)) // .next(new TestCase() // - .output(CHARGER_5_ACTUAL_POWER, 2000) // - .output(CHARGER_6_ACTUAL_POWER, 0) // + .output("charger4", ACTUAL_POWER, 2000) // + .output("charger5", ACTUAL_POWER, 0) // ); } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/mppt/twostring/GoodWeChargerMpptTwoStringImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/mppt/twostring/GoodWeChargerMpptTwoStringImplTest.java index 591db516cd4..a7ad15707c2 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/mppt/twostring/GoodWeChargerMpptTwoStringImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/mppt/twostring/GoodWeChargerMpptTwoStringImplTest.java @@ -2,8 +2,6 @@ import org.junit.Test; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.battery.api.Battery; import io.openems.edge.battery.test.DummyBattery; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.startstop.StartStopConfig; @@ -12,9 +10,11 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.ess.dccharger.api.EssDcCharger; import io.openems.edge.ess.test.DummyPower; import io.openems.edge.goodwe.GoodWeConstants; import io.openems.edge.goodwe.batteryinverter.GoodWeBatteryInverterImpl; +import io.openems.edge.goodwe.common.GoodWe; import io.openems.edge.goodwe.common.enums.ControlMode; import io.openems.edge.goodwe.common.enums.EnableDisable; import io.openems.edge.goodwe.common.enums.FeedInPowerSettings; @@ -22,94 +22,57 @@ public class GoodWeChargerMpptTwoStringImplTest { - private static final String MODBUS_ID = "modbus0"; - private static final String BATTERY_ID = "battery0"; - private static final String CHARGER_1_ID = "charger0"; - private static final String CHARGER_2_ID = "charger1"; - private static final String CHARGER_3_ID = "charger2"; - private static final String BATTERY_INVERTER_ID = "batteryInverter0"; - - private static final Battery BATTERY = new DummyBattery(BATTERY_ID); - - private static final ChannelAddress MPPT1_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt1P"); - private static final ChannelAddress MPPT1_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt1I"); - private static final ChannelAddress MPPT2_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt2P"); - private static final ChannelAddress MPPT2_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt2I"); - private static final ChannelAddress MPPT3_P = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt3P"); - private static final ChannelAddress MPPT3_I = new ChannelAddress(BATTERY_INVERTER_ID, "Mppt3I"); - private static final ChannelAddress TWO_S_PV1_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv1I"); - private static final ChannelAddress TWO_S_PV1_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv1V"); - private static final ChannelAddress TWO_S_PV2_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv2I"); - private static final ChannelAddress TWO_S_PV2_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv2V"); - private static final ChannelAddress TWO_S_PV3_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv3I"); - private static final ChannelAddress TWO_S_PV3_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv3V"); - private static final ChannelAddress TWO_S_PV4_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv4I"); - private static final ChannelAddress TWO_S_PV4_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv4V"); - private static final ChannelAddress TWO_S_PV5_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv5I"); - private static final ChannelAddress TWO_S_PV5_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv5V"); - private static final ChannelAddress TWO_S_PV6_I = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv6I"); - private static final ChannelAddress TWO_S_PV6_V = new ChannelAddress(BATTERY_INVERTER_ID, "TwoSPv6V"); - private static final ChannelAddress CHARGER_1_ACTUAL_POWER = new ChannelAddress(CHARGER_1_ID, "ActualPower"); - private static final ChannelAddress CHARGER_1_VOLTAGE = new ChannelAddress(CHARGER_1_ID, "Voltage"); - private static final ChannelAddress CHARGER_1_CURRENT = new ChannelAddress(CHARGER_1_ID, "Current"); - private static final ChannelAddress CHARGER_2_ACTUAL_POWER = new ChannelAddress(CHARGER_2_ID, "ActualPower"); - private static final ChannelAddress CHARGER_2_VOLTAGE = new ChannelAddress(CHARGER_2_ID, "Voltage"); - private static final ChannelAddress CHARGER_2_CURRENT = new ChannelAddress(CHARGER_2_ID, "Current"); - private static final ChannelAddress CHARGER_3_ACTUAL_POWER = new ChannelAddress(CHARGER_3_ID, "ActualPower"); - private static final ChannelAddress CHARGER_3_VOLTAGE = new ChannelAddress(CHARGER_3_ID, "Voltage"); - private static final ChannelAddress CHARGER_3_CURRENT = new ChannelAddress(CHARGER_3_ID, "Current"); - @Test public void test() throws Exception { - var ess = new GoodWeBatteryInverterImpl(); + var inverter = new GoodWeBatteryInverterImpl(); var charger1 = new GoodWeChargerMpptTwoStringImpl(); var charger2 = new GoodWeChargerMpptTwoStringImpl(); var charger3 = new GoodWeChargerMpptTwoStringImpl(); new ComponentTest(charger1) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("essOrBatteryInverter", ess) // + .addReference("essOrBatteryInverter", inverter) // .activate(MyConfig.create() // - .setId(CHARGER_1_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger0") // + .setBatteryInverterId("batteryInverter0") // .setMpptPort(MpptPort.MPPT_1) // .build()); new ComponentTest(charger2) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("essOrBatteryInverter", ess) // + .addReference("essOrBatteryInverter", inverter) // .activate(MyConfig.create() // - .setId(CHARGER_2_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger1") // + .setBatteryInverterId("batteryInverter0") // .setMpptPort(MpptPort.MPPT_2) // .build()); new ComponentTest(charger3) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("essOrBatteryInverter", ess) // + .addReference("essOrBatteryInverter", inverter) // .activate(MyConfig.create() // - .setId(CHARGER_3_ID) // - .setBatteryInverterId(BATTERY_INVERTER_ID) // + .setId("charger2") // + .setBatteryInverterId("batteryInverter0") // .setMpptPort(MpptPort.MPPT_3) // .build()); - ess.addCharger(charger1); - ess.addCharger(charger2); - ess.addCharger(charger3); + inverter.addCharger(charger1); + inverter.addCharger(charger2); + inverter.addCharger(charger3); - new ComponentTest(ess) // + new ComponentTest(inverter) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", new DummyComponentManager()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addReference("sum", new DummySum()) // .addComponent(charger1) // .addComponent(charger2) // .addComponent(charger3) // - .addComponent(BATTERY) // + .addComponent(new DummyBattery("battery0")) // .activate(io.openems.edge.goodwe.batteryinverter.MyConfig.create() // - .setId(BATTERY_INVERTER_ID) // - .setModbusId(MODBUS_ID) // + .setId("batteryInverter0") // + .setModbusId("modbus0") // .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // .setSafetyCountry(SafetyCountry.GERMANY) // .setMpptForShadowEnable(EnableDisable.ENABLE) // @@ -121,74 +84,74 @@ public void test() throws Exception { .setStartStop(StartStopConfig.START) // .build()) // .next(new TestCase() // - .input(MPPT1_I, 20) // - .input(MPPT1_P, 2000) // - .input(TWO_S_PV1_I, 10) // - .input(TWO_S_PV2_I, 10) // - .input(TWO_S_PV1_V, 240) // - .input(TWO_S_PV2_V, 240)) // + .input(GoodWe.ChannelId.MPPT1_I, 20) // + .input(GoodWe.ChannelId.MPPT1_P, 2000) // + .input(GoodWe.ChannelId.TWO_S_PV1_I, 10) // + .input(GoodWe.ChannelId.TWO_S_PV2_I, 10) // + .input(GoodWe.ChannelId.TWO_S_PV1_V, 240) // + .input(GoodWe.ChannelId.TWO_S_PV2_V, 240)) // // Values applied in the next cycle .next(new TestCase() // - .output(CHARGER_1_ACTUAL_POWER, 2000) // - .output(CHARGER_1_CURRENT, 20) // - .output(CHARGER_1_VOLTAGE, 240) // - .output(CHARGER_2_ACTUAL_POWER, null) // - .output(CHARGER_2_CURRENT, null) // - .output(CHARGER_2_VOLTAGE, null) // - .output(CHARGER_3_ACTUAL_POWER, null) // - .output(CHARGER_3_CURRENT, null) // - .output(CHARGER_3_VOLTAGE, null) // + .output("charger0", EssDcCharger.ChannelId.ACTUAL_POWER, 2000) // + .output("charger0", EssDcCharger.ChannelId.CURRENT, 20) // + .output("charger0", EssDcCharger.ChannelId.VOLTAGE, 240) // + .output("charger1", EssDcCharger.ChannelId.ACTUAL_POWER, null) // + .output("charger1", EssDcCharger.ChannelId.CURRENT, null) // + .output("charger1", EssDcCharger.ChannelId.VOLTAGE, null) // + .output("charger2", EssDcCharger.ChannelId.ACTUAL_POWER, null) // + .output("charger2", EssDcCharger.ChannelId.CURRENT, null) // + .output("charger2", EssDcCharger.ChannelId.VOLTAGE, null) // ) // // Chargers with different current values .next(new TestCase() // - .input(MPPT1_I, 20) // - .input(MPPT1_P, 3000) // - .input(TWO_S_PV1_I, 5) // - .input(TWO_S_PV2_I, 15) // - .input(TWO_S_PV1_V, 250) // - .input(TWO_S_PV2_V, 250)) // + .input(GoodWe.ChannelId.MPPT1_I, 20) // + .input(GoodWe.ChannelId.MPPT1_P, 3000) // + .input(GoodWe.ChannelId.TWO_S_PV1_I, 5) // + .input(GoodWe.ChannelId.TWO_S_PV2_I, 15) // + .input(GoodWe.ChannelId.TWO_S_PV1_V, 250) // + .input(GoodWe.ChannelId.TWO_S_PV2_V, 250)) // .next(new TestCase() // - .output(CHARGER_1_ACTUAL_POWER, 3000) // - .output(CHARGER_1_CURRENT, 20) // - .output(CHARGER_1_VOLTAGE, 250) // - .output(CHARGER_2_ACTUAL_POWER, null) // - .output(CHARGER_2_CURRENT, null) // - .output(CHARGER_2_VOLTAGE, null). // - output(CHARGER_3_ACTUAL_POWER, null) // - .output(CHARGER_3_CURRENT, null) // - .output(CHARGER_3_VOLTAGE, null) // + .output("charger0", EssDcCharger.ChannelId.ACTUAL_POWER, 3000) // + .output("charger0", EssDcCharger.ChannelId.CURRENT, 20) // + .output("charger0", EssDcCharger.ChannelId.VOLTAGE, 250) // + .output("charger1", EssDcCharger.ChannelId.ACTUAL_POWER, null) // + .output("charger1", EssDcCharger.ChannelId.CURRENT, null) // + .output("charger1", EssDcCharger.ChannelId.VOLTAGE, null) // + .output("charger2", EssDcCharger.ChannelId.ACTUAL_POWER, null) // + .output("charger2", EssDcCharger.ChannelId.CURRENT, null) // + .output("charger2", EssDcCharger.ChannelId.VOLTAGE, null) // ) .next(new TestCase() // - .input(MPPT1_I, 20) // - .input(MPPT1_P, 2000) // - .input(MPPT2_I, 30) // - .input(MPPT2_P, 3000) // - .input(MPPT3_I, 40) // - .input(MPPT3_P, 4000) // - .input(TWO_S_PV1_I, 10) // - .input(TWO_S_PV1_V, 250) // - .input(TWO_S_PV2_I, 10) // - .input(TWO_S_PV2_V, 250) // - .input(TWO_S_PV3_I, 15) // - .input(TWO_S_PV3_V, 280) // - .input(TWO_S_PV4_I, 15) // - .input(TWO_S_PV4_V, 280) // - .input(TWO_S_PV5_I, 20) // - .input(TWO_S_PV5_V, 299) // - .input(TWO_S_PV6_I, 20) // - .input(TWO_S_PV6_V, 299)) // + .input(GoodWe.ChannelId.MPPT1_I, 20) // + .input(GoodWe.ChannelId.MPPT1_P, 2000) // + .input(GoodWe.ChannelId.MPPT2_I, 30) // + .input(GoodWe.ChannelId.MPPT2_P, 3000) // + .input(GoodWe.ChannelId.MPPT3_I, 40) // + .input(GoodWe.ChannelId.MPPT3_P, 4000) // + .input(GoodWe.ChannelId.TWO_S_PV1_I, 10) // + .input(GoodWe.ChannelId.TWO_S_PV1_V, 250) // + .input(GoodWe.ChannelId.TWO_S_PV2_I, 10) // + .input(GoodWe.ChannelId.TWO_S_PV2_V, 250) // + .input(GoodWe.ChannelId.TWO_S_PV3_I, 15) // + .input(GoodWe.ChannelId.TWO_S_PV3_V, 280) // + .input(GoodWe.ChannelId.TWO_S_PV4_I, 15) // + .input(GoodWe.ChannelId.TWO_S_PV4_V, 280) // + .input(GoodWe.ChannelId.TWO_S_PV5_I, 20) // + .input(GoodWe.ChannelId.TWO_S_PV5_V, 299) // + .input(GoodWe.ChannelId.TWO_S_PV6_I, 20) // + .input(GoodWe.ChannelId.TWO_S_PV6_V, 299)) // .next(new TestCase() // - .output(CHARGER_1_ACTUAL_POWER, 2000) // - .output(CHARGER_1_CURRENT, 20) // - .output(CHARGER_1_VOLTAGE, 250) // - .output(CHARGER_2_ACTUAL_POWER, 3000) // - .output(CHARGER_2_CURRENT, 30) // - .output(CHARGER_2_VOLTAGE, 280). // - output(CHARGER_3_ACTUAL_POWER, 4000) // - .output(CHARGER_3_CURRENT, 40) // - .output(CHARGER_3_VOLTAGE, 299) // + .output("charger0", EssDcCharger.ChannelId.ACTUAL_POWER, 2000) // + .output("charger0", EssDcCharger.ChannelId.CURRENT, 20) // + .output("charger0", EssDcCharger.ChannelId.VOLTAGE, 250) // + .output("charger1", EssDcCharger.ChannelId.ACTUAL_POWER, 3000) // + .output("charger1", EssDcCharger.ChannelId.CURRENT, 30) // + .output("charger1", EssDcCharger.ChannelId.VOLTAGE, 280) // + .output("charger2", EssDcCharger.ChannelId.ACTUAL_POWER, 4000) // + .output("charger2", EssDcCharger.ChannelId.CURRENT, 40) // + .output("charger2", EssDcCharger.ChannelId.VOLTAGE, 299) // ); } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv1Test.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv1Test.java index 36ec9b90b80..e296380ac59 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv1Test.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv1Test.java @@ -8,19 +8,15 @@ public class GoodWeChargerPv1Test { - private static final String MODBUS_ID = "modbus0"; - private static final String ESS_ID = "ess0"; - private static final String CHARGER_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new GoodWeChargerPv1()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("charger0") // + .setBatteryInverterId("ess0") // + .setModbusId("modbus0") // .build()); } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv2Test.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv2Test.java index 754badab30e..77dd4956860 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv2Test.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/singlestring/GoodWeChargerPv2Test.java @@ -8,19 +8,15 @@ public class GoodWeChargerPv2Test { - private static final String MODBUS_ID = "modbus0"; - private static final String ESS_ID = "ess0"; - private static final String CHARGER_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new GoodWeChargerPv2()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(ESS_ID) // - .setModbusId(MODBUS_ID) // + .setId("charger0") // + .setBatteryInverterId("ess0") // + .setModbusId("modbus0") // .build()); } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java index 3d2e5340e78..2c678e67e38 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/charger/twostring/GoodWeChargerTwoStringImplTest.java @@ -8,9 +8,6 @@ public class GoodWeChargerTwoStringImplTest { - private static final String ESS_ID = "ess0"; - private static final String CHARGER_ID = "charger0"; - @SuppressWarnings("deprecation") @Test public void test() throws Exception { @@ -18,8 +15,8 @@ public void test() throws Exception { .addReference("cm", new DummyConfigurationAdmin()) // .addReference("essOrBatteryInverter", new GoodWeEssImpl()) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(ESS_ID) // + .setId("charger0") // + .setBatteryInverterId("ess0") // .setPvPort(PvPort.PV_1) // .build()); } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterTest.java index 6c08cecae9b..463fd4d32f4 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/emergencypowermeter/GoodWeEmergencyPowerMeterTest.java @@ -8,18 +8,14 @@ public class GoodWeEmergencyPowerMeterTest { - private static final String MODBUS_ID = "modbus0"; - - private static final String METER_ID = "meter2"; - @Test public void test() throws Exception { new ComponentTest(new GoodWeEmergencyPowerMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter2") // + .setModbusId("modbus0") // .build()); } } diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/ess/GoodWeEssImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/ess/GoodWeEssImplTest.java index bfde5088729..f6dec937023 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/ess/GoodWeEssImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/ess/GoodWeEssImplTest.java @@ -1,5 +1,7 @@ package io.openems.edge.goodwe.ess; +import static io.openems.edge.goodwe.GoodWeConstants.DEFAULT_UNIT_ID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; @@ -7,27 +9,22 @@ import io.openems.edge.common.test.DummyConfigurationAdmin; import io.openems.edge.ess.test.DummyPower; import io.openems.edge.ess.test.ManagedSymmetricEssTest; -import io.openems.edge.goodwe.GoodWeConstants; import io.openems.edge.goodwe.charger.singlestring.GoodWeChargerPv1; import io.openems.edge.goodwe.common.enums.ControlMode; public class GoodWeEssImplTest { - private static final String ESS_ID = "ess0"; - private static final String MODBUS_ID = "modbus0"; - private static final String CHARGER_ID = "charger0"; - @Test public void testEt() throws Exception { var charger = new GoodWeChargerPv1(); new ComponentTest(charger) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(io.openems.edge.goodwe.charger.singlestring.MyConfig.create() // - .setId(CHARGER_ID) // - .setBatteryInverterId(ESS_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("charger0") // + .setBatteryInverterId("ess0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .build()); var ess = new GoodWeEssImpl(); @@ -35,12 +32,12 @@ public void testEt() throws Exception { new ManagedSymmetricEssTest(ess) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .addComponent(charger) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("ess0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setCapacity(9_000) // .setMaxBatteryPower(5_200) // .setControlMode(ControlMode.SMART) // @@ -54,11 +51,11 @@ public void testBt() throws Exception { new ManagedSymmetricEssTest(ess) // .addReference("power", new DummyPower()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setModbusId(MODBUS_ID) // - .setModbusUnitId(GoodWeConstants.DEFAULT_UNIT_ID) // + .setId("ess0") // + .setModbusId("modbus0") // + .setModbusUnitId(DEFAULT_UNIT_ID) // .setCapacity(9_000) // .setMaxBatteryPower(5_200) // .setControlMode(ControlMode.SMART) // diff --git a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImplTest.java b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImplTest.java index 47b532280ad..79d1e0cf4d3 100644 --- a/io.openems.edge.goodwe/test/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImplTest.java +++ b/io.openems.edge.goodwe/test/io/openems/edge/goodwe/gridmeter/GoodWeGridMeterImplTest.java @@ -1,10 +1,17 @@ package io.openems.edge.goodwe.gridmeter; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeter.ChannelId.EXTERNAL_METER_RATIO; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeter.ChannelId.METER_CON_CORRECTLY_L1; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeter.ChannelId.METER_CON_INCORRECTLY_L1; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeter.ChannelId.METER_CON_REVERSE_L1; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeterCategory.COMMERCIAL_METER; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeterCategory.SMART_METER; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeterImpl.calculateRatio; +import static io.openems.edge.goodwe.gridmeter.GoodWeGridMeterImpl.getPhaseConnectionValue; import static org.junit.Assert.assertEquals; import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; @@ -13,30 +20,17 @@ public class GoodWeGridMeterImplTest { - private static final String MODBUS_ID = "modbus0"; - - private static final String METER_ID = "meter0"; - - private static final ChannelAddress METER_CON_CORRECTLY_L1 = new ChannelAddress(METER_ID, - GoodWeGridMeter.ChannelId.METER_CON_CORRECTLY_L1.id()); - private static final ChannelAddress METER_CON_INCORRECTLY_L1 = new ChannelAddress(METER_ID, - GoodWeGridMeter.ChannelId.METER_CON_INCORRECTLY_L1.id()); - private static final ChannelAddress METER_CON_REVERSE_L1 = new ChannelAddress(METER_ID, - GoodWeGridMeter.ChannelId.METER_CON_REVERSE_L1.id()); - private static final ChannelAddress EXTERNAL_METER_RATIO = new ChannelAddress(METER_ID, - GoodWeGridMeter.ChannelId.EXTERNAL_METER_RATIO.id()); - @Test public void test() throws Exception { final var sut = new GoodWeGridMeterImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setGoodWeMeterCategory(GoodWeGridMeterCategory.SMART_METER) // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(SMART_METER) // .setExternalMeterRatioValueA(0) // .setExternalMeterRatioValueB(0) // .build()) // @@ -65,31 +59,31 @@ public void test() throws Exception { @Test public void testMeterConnectStateConverter() throws Exception { - var l1Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L1, 0x0124); - var l2Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L2, 0x0124); - var l3Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L3, 0x0124); + var l1Result = getPhaseConnectionValue(Phase.L1, 0x0124); + var l2Result = getPhaseConnectionValue(Phase.L2, 0x0124); + var l3Result = getPhaseConnectionValue(Phase.L3, 0x0124); assertEquals(4, (int) l1Result); assertEquals(2, (int) l2Result); assertEquals(1, (int) l3Result); - l1Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L1, 0x0524); - l2Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L2, 0x0462); - l3Result = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L3, 0x1647); + l1Result = getPhaseConnectionValue(Phase.L1, 0x0524); + l2Result = getPhaseConnectionValue(Phase.L2, 0x0462); + l3Result = getPhaseConnectionValue(Phase.L3, 0x1647); assertEquals(4, (int) l1Result); assertEquals(6, (int) l2Result); assertEquals(6, (int) l3Result); - var l1NoResult = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L1, 0x000); - var l2NoResult = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L2, 0x000); - var l3NoResult = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L3, 0x000); + var l1NoResult = getPhaseConnectionValue(Phase.L1, 0x000); + var l2NoResult = getPhaseConnectionValue(Phase.L2, 0x000); + var l3NoResult = getPhaseConnectionValue(Phase.L3, 0x000); assertEquals(0, (int) l1NoResult); assertEquals(0, (int) l2NoResult); assertEquals(0, (int) l3NoResult); - var noResult = GoodWeGridMeterImpl.getPhaseConnectionValue(Phase.L3, 0x000); + var noResult = getPhaseConnectionValue(Phase.L3, 0x000); assert noResult == 0x000; } @@ -98,11 +92,11 @@ public void testMeterConnectStateConverter() throws Exception { public void testExternalMeterRatio() throws Exception { new ComponentTest(new GoodWeGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setGoodWeMeterCategory(GoodWeGridMeterCategory.COMMERCIAL_METER) // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(COMMERCIAL_METER) // .setExternalMeterRatioValueA(3000) // .setExternalMeterRatioValueB(5) // .build()) // @@ -111,11 +105,11 @@ public void testExternalMeterRatio() throws Exception { new ComponentTest(new GoodWeGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setGoodWeMeterCategory(GoodWeGridMeterCategory.COMMERCIAL_METER) // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(COMMERCIAL_METER) // .setExternalMeterRatioValueA(500) // .setExternalMeterRatioValueB(5) // .build()) // @@ -124,11 +118,11 @@ public void testExternalMeterRatio() throws Exception { new ComponentTest(new GoodWeGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setGoodWeMeterCategory(GoodWeGridMeterCategory.SMART_METER) // + .setId("meter0") // + .setModbusId("modbus0") // + .setGoodWeMeterCategory(SMART_METER) // .setExternalMeterRatioValueA(3000) // .setExternalMeterRatioValueB(5) // .build()) // @@ -138,12 +132,11 @@ public void testExternalMeterRatio() throws Exception { @Test public void testCalculateRatio() { - - assertEquals(600, (int) GoodWeGridMeterImpl.calculateRatio(3000, 5)); - assertEquals(100, (int) GoodWeGridMeterImpl.calculateRatio(500, 5)); - assertEquals(null, GoodWeGridMeterImpl.calculateRatio(-5, 5)); - assertEquals(null, GoodWeGridMeterImpl.calculateRatio(3000, 0)); - assertEquals(null, GoodWeGridMeterImpl.calculateRatio(500, -5)); + assertEquals(600, calculateRatio(3000, 5).intValue()); + assertEquals(100, calculateRatio(500, 5).intValue()); + assertEquals(null, calculateRatio(-5, 5)); + assertEquals(null, calculateRatio(3000, 0)); + assertEquals(null, calculateRatio(500, -5)); } } diff --git a/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyCustomInputOutput.java b/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyCustomInputOutput.java new file mode 100644 index 00000000000..694f61e0348 --- /dev/null +++ b/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyCustomInputOutput.java @@ -0,0 +1,57 @@ +package io.openems.edge.io.test; + +import io.openems.common.channel.AccessMode; +import io.openems.common.types.OpenemsType; +import io.openems.edge.common.channel.BooleanReadChannel; +import io.openems.edge.common.channel.BooleanWriteChannel; +import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.test.AbstractDummyOpenemsComponent; +import io.openems.edge.io.api.DigitalInput; +import io.openems.edge.io.api.DigitalOutput; + +/** + * Provides a simple, simulated Digital Input/Output component that can be used + * together with the OpenEMS Component test framework. + */ +public class DummyCustomInputOutput extends AbstractDummyOpenemsComponent + implements DigitalInput, DigitalOutput { + + private final BooleanWriteChannel[] ioChannels; + + public DummyCustomInputOutput(String id) { + this(id, "INPUT_OUTPUT", 0, 10); + } + + public DummyCustomInputOutput(String id, String prefix, int start, int numberOfIOs) { + super(id, // + OpenemsComponent.ChannelId.values(), // + DigitalInput.ChannelId.values(), // + DigitalOutput.ChannelId.values() // + ); + + this.ioChannels = new BooleanWriteChannel[numberOfIOs]; + for (int i = 0; i < numberOfIOs; i++) { + this.ioChannels[i] = (BooleanWriteChannel) this + .addChannel(new ChannelIdImpl(prefix + "_" + (i + start), Doc.of(OpenemsType.BOOLEAN).// + accessMode(AccessMode.READ_WRITE))); + } + } + + @Override + protected DummyCustomInputOutput self() { + return this; + } + + @Override + public BooleanWriteChannel[] digitalOutputChannels() { + return this.ioChannels; + } + + @Override + public BooleanReadChannel[] digitalInputChannels() { + return this.ioChannels; + } + +} diff --git a/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyInputOutput.java b/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyInputOutput.java index f9114bef534..92e6b4a26c1 100644 --- a/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyInputOutput.java +++ b/io.openems.edge.io.api/src/io/openems/edge/io/test/DummyInputOutput.java @@ -1,10 +1,11 @@ package io.openems.edge.io.test; +import java.util.stream.Stream; + import io.openems.common.channel.AccessMode; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.BooleanReadChannel; import io.openems.edge.common.channel.BooleanWriteChannel; -import io.openems.edge.common.channel.ChannelId.ChannelIdImpl; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.test.AbstractDummyOpenemsComponent; @@ -18,25 +19,53 @@ public class DummyInputOutput extends AbstractDummyOpenemsComponent implements DigitalInput, DigitalOutput { - private final BooleanWriteChannel[] ioChannels; + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + INPUT_OUTPUT0(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT1(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT2(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT3(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT4(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT5(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT6(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT7(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT8(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)), // + INPUT_OUTPUT9(Doc.of(OpenemsType.BOOLEAN) // + .accessMode(AccessMode.READ_WRITE)); - public DummyInputOutput(String id) { - this(id, "INPUT_OUTPUT", 0, 10); + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } } - public DummyInputOutput(String id, String prefix, int start, int numberOfIOs) { + private final BooleanWriteChannel[] digitalOutputChannels; + + public DummyInputOutput(String id) { super(id, // OpenemsComponent.ChannelId.values(), // DigitalInput.ChannelId.values(), // - DigitalOutput.ChannelId.values() // + DigitalOutput.ChannelId.values(), // + ChannelId.values() // ); - - this.ioChannels = new BooleanWriteChannel[numberOfIOs]; - for (int i = 0; i < numberOfIOs; i++) { - this.ioChannels[i] = (BooleanWriteChannel) this - .addChannel(new ChannelIdImpl(prefix + "_" + (i + start), Doc.of(OpenemsType.BOOLEAN).// - accessMode(AccessMode.READ_WRITE))); - } + this.digitalOutputChannels = Stream.of(ChannelId.values()) // + .filter(channelId -> channelId.doc().getAccessMode() == AccessMode.READ_WRITE) // + .map(this::channel) // + .toArray(BooleanWriteChannel[]::new); } @Override @@ -46,12 +75,12 @@ protected DummyInputOutput self() { @Override public BooleanWriteChannel[] digitalOutputChannels() { - return this.ioChannels; + return this.digitalOutputChannels; } @Override public BooleanReadChannel[] digitalInputChannels() { - return this.ioChannels; + return this.digitalOutputChannels; } } diff --git a/io.openems.edge.io.filipowski/test/io/openems/edge/io/filipowski/analog/mr/IoFilipowskiMrAo1ImplTest.java b/io.openems.edge.io.filipowski/test/io/openems/edge/io/filipowski/analog/mr/IoFilipowskiMrAo1ImplTest.java index 11ef8cd64dc..dabd16527a6 100644 --- a/io.openems.edge.io.filipowski/test/io/openems/edge/io/filipowski/analog/mr/IoFilipowskiMrAo1ImplTest.java +++ b/io.openems.edge.io.filipowski/test/io/openems/edge/io/filipowski/analog/mr/IoFilipowskiMrAo1ImplTest.java @@ -9,17 +9,14 @@ public class IoFilipowskiMrAo1ImplTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new IoFilipowskiMrAo1Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("component0") // + .setModbusId("modbus0") // .setRelayContact(AnalogOutput.OUTPUT_1) // .build()) .next(new TestCase()); diff --git a/io.openems.edge.io.gpio/test/io/openems/edge/io/gpio/ModberryCM4Test.java b/io.openems.edge.io.gpio/test/io/openems/edge/io/gpio/ModberryCM4Test.java index 113c39f7c6a..084079c95f0 100644 --- a/io.openems.edge.io.gpio/test/io/openems/edge/io/gpio/ModberryCM4Test.java +++ b/io.openems.edge.io.gpio/test/io/openems/edge/io/gpio/ModberryCM4Test.java @@ -1,5 +1,6 @@ package io.openems.edge.io.gpio; +import static io.openems.edge.io.gpio.hardware.HardwareType.MODBERRY_X500_M40804_W; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -27,14 +28,11 @@ import io.openems.edge.io.gpio.api.AbstractGpioChannel; import io.openems.edge.io.gpio.api.ReadChannelId; import io.openems.edge.io.gpio.api.WriteChannelId; -import io.openems.edge.io.gpio.hardware.HardwareType; public class ModberryCM4Test { private File root; - private static final String ID = "io0"; - private static final List CHANNEL_IDS = List.of(// new ReadChannelId(18, "DigitalInput1"), // new ReadChannelId(19, "DigitalInput2"), // @@ -93,11 +91,11 @@ private void setGpioFile(File root, int gpioNumber, int value) throws IOExceptio @Test public void testChannelIdsAreCorrect() throws Exception { var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); IoGpio modberryComponent = new IoGpioImpl(); new ComponentTest(modberryComponent).activate(config); @@ -107,11 +105,11 @@ public void testChannelIdsAreCorrect() throws Exception { @Test public void testComponentLoadsSucesfully() throws Exception { var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); new ComponentTest(new IoGpioImpl()) // .activate(config); @@ -120,19 +118,19 @@ public void testComponentLoadsSucesfully() throws Exception { @Test public void testInputValuesAreDefault() throws Exception { var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); new ComponentTest(new IoGpioImpl()) // .activate(config) // .next(new TestCase("Default input values are false") // - .output(new ChannelAddress(ID, "DigitalInput1"), false) // - .output(new ChannelAddress(ID, "DigitalInput2"), false) // - .output(new ChannelAddress(ID, "DigitalInput3"), false) // - .output(new ChannelAddress(ID, "DigitalInput4"), false) // + .output(new ChannelAddress("io0", "DigitalInput1"), false) // + .output(new ChannelAddress("io0", "DigitalInput2"), false) // + .output(new ChannelAddress("io0", "DigitalInput3"), false) // + .output(new ChannelAddress("io0", "DigitalInput4"), false) // ); } @@ -143,19 +141,19 @@ public void testChangeOutputWrittenToFs() throws Exception { assertEquals(this.readGpioFile(this.root, 24), "0"); assertEquals(this.readGpioFile(this.root, 25), "0"); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); new ComponentTest(new IoGpioImpl()) // .activate(config) // .next(new TestCase("Write values are written to fs.") // - .input(new ChannelAddress(ID, "DigitalOutput1"), true) // - .input(new ChannelAddress(ID, "DigitalOutput2"), true) // - .input(new ChannelAddress(ID, "DigitalOutput3"), true) // - .input(new ChannelAddress(ID, "DigitalOutput4"), true) // + .input(new ChannelAddress("io0", "DigitalOutput1"), true) // + .input(new ChannelAddress("io0", "DigitalOutput2"), true) // + .input(new ChannelAddress("io0", "DigitalOutput3"), true) // + .input(new ChannelAddress("io0", "DigitalOutput4"), true) // ); assertEquals(this.readGpioFile(this.root, 22), "1"); assertEquals(this.readGpioFile(this.root, 23), "1"); @@ -171,20 +169,20 @@ public void testChangeInputIsDetected() throws Exception { assertEquals(this.readGpioFile(this.root, 21), "0"); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); new ComponentTest(new IoGpioImpl()) // .activate(config) // .next(new TestCase("Read values are detected by the component.") // - .output(new ChannelAddress(ID, "DigitalInput1"), false) // - .output(new ChannelAddress(ID, "DigitalInput2"), false) // - .output(new ChannelAddress(ID, "DigitalInput3"), false) // - .output(new ChannelAddress(ID, "DigitalInput4"), false) // + .output(new ChannelAddress("io0", "DigitalInput1"), false) // + .output(new ChannelAddress("io0", "DigitalInput2"), false) // + .output(new ChannelAddress("io0", "DigitalInput3"), false) // + .output(new ChannelAddress("io0", "DigitalInput4"), false) // ); this.setGpioFile(this.root, 18, 1); @@ -200,10 +198,10 @@ public void testChangeInputIsDetected() throws Exception { new ComponentTest(new IoGpioImpl()) // .activate(config) // .next(new TestCase("Read values are detected by the component.") // - .output(new ChannelAddress(ID, "DigitalInput1"), true) // - .output(new ChannelAddress(ID, "DigitalInput2"), true) // - .output(new ChannelAddress(ID, "DigitalInput3"), true) // - .output(new ChannelAddress(ID, "DigitalInput4"), true) // + .output(new ChannelAddress("io0", "DigitalInput1"), true) // + .output(new ChannelAddress("io0", "DigitalInput2"), true) // + .output(new ChannelAddress("io0", "DigitalInput3"), true) // + .output(new ChannelAddress("io0", "DigitalInput4"), true) // ); } @@ -211,18 +209,19 @@ public void testChangeInputIsDetected() throws Exception { public void testJavaApi() throws Exception { this.setGpioFile(this.root, 22, 0); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); var componentManager = new DummyComponentManager(); - var componentTest = new ComponentTest(new IoGpioImpl()).activate(config); + var componentTest = new ComponentTest(new IoGpioImpl()) // + .activate(config); componentManager.addComponent(componentTest.getSut()); // Get get component channel value as java reference - WriteChannel writeChannel = componentManager.getChannel(new ChannelAddress(ID, "DigitalOutput1")); + WriteChannel writeChannel = componentManager.getChannel(new ChannelAddress("io0", "DigitalOutput1")); assertFalse(writeChannel.value().isDefined()); writeChannel.setNextValue(true); } @@ -231,11 +230,11 @@ public void testJavaApi() throws Exception { public void testInterfaceDigitalOutputChannels() throws Exception { this.setGpioFile(this.root, 22, 0); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); var componentManager = new DummyComponentManager(); var componentTest = new ComponentTest(new IoGpioImpl()) // @@ -249,11 +248,11 @@ public void testInterfaceDigitalOutputChannels() throws Exception { public void testInterfaceDigitalInputChannels() throws Exception { this.setGpioFile(this.root, 22, 0); var config = MyConfig.create() // - .setId(ID) // - .setAlias(ID) // + .setId("io0") // + .setAlias("io0") // .setEnabled(true) // .setGpioPath(this.folder.getRoot().getAbsolutePath()) // - .setHardwareType(HardwareType.MODBERRY_X500_M40804_W) // + .setHardwareType(MODBERRY_X500_M40804_W) // .build(); var componentManager = new DummyComponentManager(); var componentTest = new ComponentTest(new IoGpioImpl()) // diff --git a/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/eight/IoKmtronicRelay8PortImplTest.java b/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/eight/IoKmtronicRelay8PortImplTest.java index 67b002d2fae..e27c0302a00 100644 --- a/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/eight/IoKmtronicRelay8PortImplTest.java +++ b/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/eight/IoKmtronicRelay8PortImplTest.java @@ -8,17 +8,14 @@ public class IoKmtronicRelay8PortImplTest { - private static final String IO_ID = "io0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new IoKmtronicRelay8PortImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(IO_ID) // - .setModbusId(MODBUS_ID) // + .setId("io0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/four/IoKmtronicRelay4PortImplTest.java b/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/four/IoKmtronicRelay4PortImplTest.java index e7ccd9abf4c..36b4ab28c6f 100644 --- a/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/four/IoKmtronicRelay4PortImplTest.java +++ b/io.openems.edge.io.kmtronic/test/io/openems/edge/io/kmtronic/four/IoKmtronicRelay4PortImplTest.java @@ -8,17 +8,14 @@ public class IoKmtronicRelay4PortImplTest { - private static final String IO_ID = "io0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new IoKmtronicRelay4PortImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(IO_ID) // - .setModbusId(MODBUS_ID) // + .setId("io0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.io.offgridswitch/test/io/openems/edge/iooffgridswitch/IoOffGridSwitchImplTest.java b/io.openems.edge.io.offgridswitch/test/io/openems/edge/iooffgridswitch/IoOffGridSwitchImplTest.java index 9d182edd712..0f2944b67f6 100644 --- a/io.openems.edge.io.offgridswitch/test/io/openems/edge/iooffgridswitch/IoOffGridSwitchImplTest.java +++ b/io.openems.edge.io.offgridswitch/test/io/openems/edge/iooffgridswitch/IoOffGridSwitchImplTest.java @@ -2,7 +2,6 @@ import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -10,29 +9,18 @@ public class IoOffGridSwitchImplTest { - private static final String COMPONENT_ID = "ioOffGridSwitch0"; - - private static final String IO_ID = "io0"; - private static final ChannelAddress INPUT_MAIN_CONTACTOR = new ChannelAddress(IO_ID, "InputOutput0"); - private static final ChannelAddress INPUT_GRID_STATUS = new ChannelAddress(IO_ID, "InputOutput1"); - private static final ChannelAddress INPUT_GROUNDING_CONTACTOR = new ChannelAddress(IO_ID, "InputOutput2"); - private static final ChannelAddress OUTPUT_MAIN_CONTACTOR = new ChannelAddress(IO_ID, "InputOutput3"); - private static final ChannelAddress OUTPUT_GROUNDING_CONTACTOR = new ChannelAddress(IO_ID, "InputOutput4"); - @Test public void test() throws Exception { - var io0 = new DummyInputOutput(IO_ID); - new ComponentTest(new IoOffGridSwitchImpl()) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(io0) // + .addComponent(new DummyInputOutput("io0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setInputMainContactor(INPUT_MAIN_CONTACTOR.toString()) // - .setInputGridStatus(INPUT_GRID_STATUS.toString()) // - .setInputGroundingContactor(INPUT_GROUNDING_CONTACTOR.toString()) // - .setOutputMainContactor(OUTPUT_MAIN_CONTACTOR.toString()) // - .setOutputGroundingContactor(OUTPUT_GROUNDING_CONTACTOR.toString()) // + .setId("ioOffGridSwitch0") // + .setInputMainContactor("io0/InputOutput0") // + .setInputGridStatus("io0/InputOutput1") // + .setInputGroundingContactor("io0/InputOutput2") // + .setOutputMainContactor("io0/InputOutput3") // + .setOutputGroundingContactor("io0/InputOutput4") // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.io.revpi/test/io/openems/edge/io/revpi/IoRevolutionPiDigitalIoImplTest.java b/io.openems.edge.io.revpi/test/io/openems/edge/io/revpi/IoRevolutionPiDigitalIoImplTest.java index db1698ef9a2..7f7285b5b65 100644 --- a/io.openems.edge.io.revpi/test/io/openems/edge/io/revpi/IoRevolutionPiDigitalIoImplTest.java +++ b/io.openems.edge.io.revpi/test/io/openems/edge/io/revpi/IoRevolutionPiDigitalIoImplTest.java @@ -6,8 +6,6 @@ public class IoRevolutionPiDigitalIoImplTest { - // private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new IoRevolutionPiDigitalIoImpl()) // diff --git a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3Em.java b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3Em.java index c5899305afe..e545e5bec63 100644 --- a/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3Em.java +++ b/io.openems.edge.io.shelly/src/io/openems/edge/io/shelly/shellypro3em/IoShellyPro3Em.java @@ -3,8 +3,8 @@ import io.openems.common.channel.Level; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.StateChannel; -import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.component.OpenemsComponent; public interface IoShellyPro3Em extends OpenemsComponent { diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly25/IoShelly25ImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly25/IoShelly25ImplTest.java index 06b3f7975cf..679d091a7de 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly25/IoShelly25ImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly25/IoShelly25ImplTest.java @@ -1,20 +1,19 @@ package io.openems.edge.io.shelly.shelly25; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.ofDummyBridge; + import org.junit.Test; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.common.test.ComponentTest; public class IoShelly25ImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new IoShelly25Impl()) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // + .addReference("httpBridgeFactory", ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // .build()) // ; diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImplTest.java index 1c06c65f443..040323c987f 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shelly3em/IoShelly3EmImplTest.java @@ -1,23 +1,22 @@ package io.openems.edge.io.shelly.shelly3em; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.ofDummyBridge; +import static io.openems.edge.meter.api.MeterType.CONSUMPTION_METERED; + import org.junit.Test; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; public class IoShelly3EmImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new IoShelly3EmImpl()) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // + .addReference("httpBridgeFactory", ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // - .setType(MeterType.CONSUMPTION_METERED) // + .setType(CONSUMPTION_METERED) // .build()) // ; } diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImplTest.java index efef443c6b0..efc52c6e641 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplug/IoShellyPlugImplTest.java @@ -1,25 +1,24 @@ package io.openems.edge.io.shelly.shellyplug; +import static io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory.ofDummyBridge; +import static io.openems.edge.meter.api.MeterType.PRODUCTION; + import org.junit.Test; -import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; public class IoShellyPlugImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new IoShellyPlugImpl()) // - .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // + .addReference("httpBridgeFactory", ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setPhase(SinglePhase.L1) // .setIp("127.0.0.1") // - .setType(MeterType.PRODUCTION) // + .setType(PRODUCTION) // .build()) // ; } diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImplTest.java index e3443f8d6b1..456e5b87ad3 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplus1pm/IoShellyPlus1PmImplTest.java @@ -1,5 +1,12 @@ package io.openems.edge.io.shelly.shellyplus1pm; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.CURRENT; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.VOLTAGE; import static io.openems.edge.meter.api.MeterType.CONSUMPTION_METERED; import static io.openems.edge.meter.api.SinglePhase.L1; import static org.junit.Assert.assertEquals; @@ -7,7 +14,6 @@ import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.bridge.http.api.HttpError; import io.openems.edge.bridge.http.api.HttpResponse; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; @@ -17,17 +23,6 @@ public class IoShellyPlus1PmImplTest { - private static final String COMPONENT_ID = "io0"; - - private static final ChannelAddress ACTIVE_POWER = new ChannelAddress(COMPONENT_ID, "ActivePower"); - private static final ChannelAddress ACTIVE_POWER_L1 = new ChannelAddress(COMPONENT_ID, "ActivePowerL1"); - private static final ChannelAddress ACTIVE_POWER_L2 = new ChannelAddress(COMPONENT_ID, "ActivePowerL2"); - private static final ChannelAddress CURRENT = new ChannelAddress(COMPONENT_ID, "Current"); - private static final ChannelAddress VOLTAGE = new ChannelAddress(COMPONENT_ID, "Voltage"); - private static final ChannelAddress PRODUCTION_ENERGY = new ChannelAddress(COMPONENT_ID, "ActiveProductionEnergy"); - private static final ChannelAddress CONSUMPTION_ENERGY = new ChannelAddress(COMPONENT_ID, - "ActiveConsumptionEnergy"); - @Test public void test() throws Exception { final var httpTestBundle = new DummyBridgeHttpBundle(); @@ -37,7 +32,7 @@ public void test() throws Exception { .addReference("httpBridgeFactory", httpTestBundle.factory()) // .addReference("timedata", new DummyTimedata("timedata0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // .setType(CONSUMPTION_METERED) // .setPhase(L1) // @@ -122,7 +117,6 @@ public void test() throws Exception { .next(new TestCase("Invalid read response") // .onBeforeControllersCallbacks(() -> assertEquals("Off|123 W", sut.debugLog())) - .onBeforeControllersCallbacks(() -> { httpTestBundle.forceNextFailedResult(HttpError.ResponseError.notFound()); httpTestBundle.triggerNextCycle(); @@ -133,24 +127,19 @@ public void test() throws Exception { .output(CURRENT, null) // .output(VOLTAGE, null) // - .output(PRODUCTION_ENERGY, 0L) // - .output(CONSUMPTION_ENERGY, 0L)) // + .output(ACTIVE_PRODUCTION_ENERGY, 0L) // + .output(ACTIVE_CONSUMPTION_ENERGY, 0L)) // .next(new TestCase("Write") // .onBeforeControllersCallbacks(() -> assertEquals("Unknown|UNDEFINED", sut.debugLog())) - .onBeforeControllersCallbacks(() -> { - sut.setRelay(true); - }) // + .onBeforeControllersCallbacks(() -> sut.setRelay(true)) // .also(testCase -> { final var relayTurnedOn = httpTestBundle.expect("http://127.0.0.1/relay/0?turn=on") .toBeCalled(); - testCase.onBeforeControllersCallbacks(() -> { - httpTestBundle.triggerNextCycle(); - }); - testCase.onAfterWriteCallbacks(() -> { - assertTrue("Failed to turn on relay", relayTurnedOn.get()); - }); + testCase.onBeforeControllersCallbacks(() -> httpTestBundle.triggerNextCycle()); + testCase.onAfterWriteCallbacks( + () -> assertTrue("Failed to turn on relay", relayTurnedOn.get())); })) // .deactivate(); diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlugImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlugImplTest.java index c91c4c52720..3b4414e7be2 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlugImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/IoShellyPlugImplTest.java @@ -1,46 +1,40 @@ package io.openems.edge.io.shelly.shellyplusplugs; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_CONSUMPTION_ENERGY; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_PRODUCTION_ENERGY; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.CURRENT; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.VOLTAGE; +import static io.openems.edge.meter.api.MeterType.PRODUCTION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.bridge.http.api.HttpError; import io.openems.edge.bridge.http.api.HttpResponse; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpBundle; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; import io.openems.edge.timedata.test.DummyTimedata; public class IoShellyPlugImplTest { - private static final String COMPONENT_ID = "io0"; - - private static final ChannelAddress ACTIVE_POWER = new ChannelAddress(COMPONENT_ID, "ActivePower"); - private static final ChannelAddress ACTIVE_POWER_L1 = new ChannelAddress(COMPONENT_ID, "ActivePowerL1"); - private static final ChannelAddress ACTIVE_POWER_L2 = new ChannelAddress(COMPONENT_ID, "ActivePowerL2"); - private static final ChannelAddress CURRENT = new ChannelAddress(COMPONENT_ID, "Current"); - private static final ChannelAddress VOLTAGE = new ChannelAddress(COMPONENT_ID, "Voltage"); - private static final ChannelAddress PRODUCTION_ENERGY = new ChannelAddress(COMPONENT_ID, "ActiveProductionEnergy"); - private static final ChannelAddress CONSUMPTION_ENERGY = new ChannelAddress(COMPONENT_ID, - "ActiveConsumptionEnergy"); - @Test public void test() throws Exception { final var httpTestBundle = new DummyBridgeHttpBundle(); - final var sut = new IoShellyPlusPlugsImpl(); new ComponentTest(sut) // .addReference("httpBridgeFactory", httpTestBundle.factory()) // .addReference("timedata", new DummyTimedata("timedata0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setPhase(SinglePhase.L1) // .setIp("127.0.0.1") // - .setType(MeterType.PRODUCTION) // + .setType(PRODUCTION) // .build()) // .next(new TestCase("Successful read response") // @@ -81,8 +75,8 @@ public void test() throws Exception { .output(CURRENT, null) // .output(VOLTAGE, null) // - .output(PRODUCTION_ENERGY, 0L) // - .output(CONSUMPTION_ENERGY, 0L)) // + .output(ACTIVE_PRODUCTION_ENERGY, 0L) // + .output(ACTIVE_CONSUMPTION_ENERGY, 0L)) // .next(new TestCase("Write") // .onBeforeControllersCallbacks(() -> assertEquals("Unknown|UNDEFINED", sut.debugLog())) @@ -93,12 +87,9 @@ public void test() throws Exception { final var relayTurnedOn = httpTestBundle.expect("http://127.0.0.1/relay/0?turn=on") .toBeCalled(); - testCase.onBeforeControllersCallbacks(() -> { - httpTestBundle.triggerNextCycle(); - }); - testCase.onAfterWriteCallbacks(() -> { - assertTrue("Failed to turn on relay", relayTurnedOn.get()); - }); + testCase.onBeforeControllersCallbacks(() -> httpTestBundle.triggerNextCycle()); + testCase.onAfterWriteCallbacks( + () -> assertTrue("Failed to turn on relay", relayTurnedOn.get())); })) // .deactivate(); diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/MyConfig.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/MyConfig.java index 81fe32540f0..c14a9f39c1a 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/MyConfig.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellyplusplugs/MyConfig.java @@ -1,7 +1,6 @@ package io.openems.edge.io.shelly.shellyplusplugs; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.io.shelly.shellyplusplugs.Config; import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.api.SinglePhase; diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3/IoShellyPro3ImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3/IoShellyPro3ImplTest.java index ed4f8d960b6..c7213418efe 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3/IoShellyPro3ImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3/IoShellyPro3ImplTest.java @@ -7,14 +7,12 @@ public class IoShellyPro3ImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new IoShellyPro3Impl()) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // .build()) // ; diff --git a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShelly3EmImplTest.java b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShelly3EmImplTest.java index 29b0136a7a6..f2691bb5e7e 100644 --- a/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShelly3EmImplTest.java +++ b/io.openems.edge.io.shelly/test/io/openems/edge/io/shelly/shellypro3em/IoShelly3EmImplTest.java @@ -8,14 +8,12 @@ public class IoShelly3EmImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new IoShellyPro3EmImpl()) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setIp("127.0.0.1") // .setType(MeterType.CONSUMPTION_METERED) // .build()) // diff --git a/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java b/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java index 012eac53fd3..292deeff3f3 100644 --- a/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java +++ b/io.openems.edge.io.wago/test/io/openems/edge/wago/IoWagoImplTest.java @@ -16,9 +16,6 @@ public class IoWagoImplTest { - private static final String IO_ID = "io0"; - private static final String MODBUS_ID = "modbus0"; - /** * This is an example "ea-config.xml" downloaded from a WAGO Fieldbus coupler. */ @@ -56,14 +53,14 @@ public void test() throws Exception { var sut = new IoWagoImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID).withIpAddress("127.0.0.1")) // + .addReference("setModbus", new DummyModbusBridge("modbus0") // + .withIpAddress("127.0.0.1")) // .activate(MyConfig.create() // - .setId(IO_ID) // - .setModbusId(MODBUS_ID) // + .setId("io0") // + .setModbusId("modbus0") // .setUsername("foo") // .setPassword("bar") // - .build()) // - ; + .build()); InputStream dummyXml = new ByteArrayInputStream(EA_CONFIG.getBytes()); var doc = IoWagoImpl.parseXmlToDocument(dummyXml); diff --git a/io.openems.edge.io.weidmueller/test/io/openems/edge/io/weidmueller/IoWeidmuellerUr20ImplTest.java b/io.openems.edge.io.weidmueller/test/io/openems/edge/io/weidmueller/IoWeidmuellerUr20ImplTest.java index 7a8da19e227..eadbf247bb5 100644 --- a/io.openems.edge.io.weidmueller/test/io/openems/edge/io/weidmueller/IoWeidmuellerUr20ImplTest.java +++ b/io.openems.edge.io.weidmueller/test/io/openems/edge/io/weidmueller/IoWeidmuellerUr20ImplTest.java @@ -9,17 +9,14 @@ public class IoWeidmuellerUr20ImplTest { - private static final String COMPONENT_ID = "io0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new IoWeidmuellerUr20Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("io0") // + .setModbusId("modbus0") // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/core/KacoBlueplanetHybrid10CoreImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/core/KacoBlueplanetHybrid10CoreImplTest.java index e0d6d9bbb72..bf8f9736926 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/core/KacoBlueplanetHybrid10CoreImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/core/KacoBlueplanetHybrid10CoreImplTest.java @@ -6,13 +6,11 @@ public class KacoBlueplanetHybrid10CoreImplTest { - private static final String CORE_ID = "kacoCore0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10CoreImpl()) // .activate(MyConfig.create() // - .setId(CORE_ID) // + .setId("kacoCore0") // .setIdentkey("") // .setIp("192.168.0.1") // .setSerialnumber("123456") // diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/KacoBlueplanetHybrid10EssImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/KacoBlueplanetHybrid10EssImplTest.java index cb4ec7953a9..f06f0bbafb1 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/KacoBlueplanetHybrid10EssImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/KacoBlueplanetHybrid10EssImplTest.java @@ -8,18 +8,14 @@ public class KacoBlueplanetHybrid10EssImplTest { - private static final String ESS_ID = "ess0"; - private static final String CORE_ID = "kacoCore0"; - private static final String TIMEDATA_ID = "timedata0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10EssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("timedata", new DummyTimedata(TIMEDATA_ID)) // + .addReference("timedata", new DummyTimedata("timedata0")) // .activate(MyConfig.create() // - .setId(ESS_ID) // - .setCoreId(CORE_ID) // + .setId("ess0") // + .setCoreId("kacoCore0") // .build()) // ; } diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/charger/KacoBlueplanetHybrid10ChargerImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/charger/KacoBlueplanetHybrid10ChargerImplTest.java index 6d9606bb0b6..e5d00f7b9e2 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/charger/KacoBlueplanetHybrid10ChargerImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/ess/charger/KacoBlueplanetHybrid10ChargerImplTest.java @@ -7,16 +7,13 @@ public class KacoBlueplanetHybrid10ChargerImplTest { - private static final String CHARGER_ID = "charger0"; - private static final String CORE_ID = "kacoCore0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10ChargerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(CHARGER_ID) // - .setCoreId(CORE_ID) // + .setId("charger0") // + .setCoreId("kacoCore0") // .build()) // ; } diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/pvinverter/KacoBlueplanetHybrid10PvInverterImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/pvinverter/KacoBlueplanetHybrid10PvInverterImplTest.java index 2a34e01ddaa..a8c9ee17853 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/pvinverter/KacoBlueplanetHybrid10PvInverterImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/pvinverter/KacoBlueplanetHybrid10PvInverterImplTest.java @@ -7,16 +7,13 @@ public class KacoBlueplanetHybrid10PvInverterImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String CORE_ID = "kacoCore0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10PvInverterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // - .setCoreId(CORE_ID) // + .setId("pvInverter0") // + .setCoreId("kacoCore0") // .build()) // ; } diff --git a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImplTest.java b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImplTest.java index c4d71724b33..cc50bb9d0fd 100644 --- a/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImplTest.java +++ b/io.openems.edge.kaco.blueplanet.hybrid10/test/io/openems/edge/kaco/blueplanet/hybrid10/vectis/KacoBlueplanetHybrid10GridMeterImplTest.java @@ -7,16 +7,13 @@ public class KacoBlueplanetHybrid10GridMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String CORE_ID = "kacoCore0"; - @Test public void test() throws Exception { new ComponentTest(new KacoBlueplanetHybrid10GridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setCoreId(CORE_ID) // + .setId("meter0") // + .setCoreId("kacoCore0") // .build()) // ; } diff --git a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/charger/KostalPikoChargerImplTest.java b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/charger/KostalPikoChargerImplTest.java index ed2b7b7d5cc..96cbaf11cdd 100644 --- a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/charger/KostalPikoChargerImplTest.java +++ b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/charger/KostalPikoChargerImplTest.java @@ -8,15 +8,13 @@ public class KostalPikoChargerImplTest { - private static final String COMPONENT_ID = "charger0"; - @Test public void test() throws Exception { new ComponentTest(new KostalPikoChargerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // # .addReference("setCore", new KostalPikoCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("charger0") // .setCoreId("core0") // .build()) // ; diff --git a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/core/impl/KostalPikoCoreImplTest.java b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/core/impl/KostalPikoCoreImplTest.java index 5dab51e4a65..dff15af4a7e 100644 --- a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/core/impl/KostalPikoCoreImplTest.java +++ b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/core/impl/KostalPikoCoreImplTest.java @@ -6,13 +6,11 @@ public class KostalPikoCoreImplTest { - private static final String COMPONENT_ID = "core0"; - @Test public void test() throws Exception { new ComponentTest(new KostalPikoCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("core0") // .setIp("127.0.0.1") // .setPort(81) // .setUnitID(0xff) // diff --git a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/ess/KostalPikoEssImplTest.java b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/ess/KostalPikoEssImplTest.java index 1c3e78bfc06..aaeaf69fefa 100644 --- a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/ess/KostalPikoEssImplTest.java +++ b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/ess/KostalPikoEssImplTest.java @@ -8,15 +8,13 @@ public class KostalPikoEssImplTest { - private static final String COMPONENT_ID = "ess0"; - @Test public void test() throws Exception { new ComponentTest(new KostalPikoEssImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // # .addReference("setCore", new KostalPikoCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("ess0") // .setCoreId("core0") // .build()) // ; diff --git a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImplTest.java b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImplTest.java index 7895e4c50ed..ee5e9206bcb 100644 --- a/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImplTest.java +++ b/io.openems.edge.kostal.piko/test/io/openems/edge/kostal/piko/gridmeter/KostalPikoGridMeterImplTest.java @@ -8,15 +8,13 @@ public class KostalPikoGridMeterImplTest { - private static final String COMPONENT_ID = "meter0"; - @Test public void test() throws Exception { new ComponentTest(new KostalPikoGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // # .addReference("setCore", new KostalPikoCoreImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("meter0") // .setCoreId("core0") // .build()) // ; diff --git a/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MeterArtemesAM2ImplTest.java b/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MeterArtemesAM2ImplTest.java index cc287731b19..c77fb1b3a1c 100644 --- a/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MeterArtemesAM2ImplTest.java +++ b/io.openems.edge.meter.artemes.am2/test/io/openems/edge/meter/artemes/am2/MeterArtemesAM2ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.artemes.am2; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterArtemesAM2ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterArtemesAM2Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300ImplTest.java b/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300ImplTest.java index 04513e888fc..090b71d524b 100644 --- a/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300ImplTest.java +++ b/io.openems.edge.meter.bcontrol.em300/test/io/openems/edge/meter/bcontrol/em300/MeterBControlEM300ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.bcontrol.em300; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterBControlEM300ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterBControlEM300Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImplTest.java b/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImplTest.java index a6ad0bff683..2ee0d121c5d 100644 --- a/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImplTest.java +++ b/io.openems.edge.meter.camillebauer.aplus/test/io/openems/edge/meter/camillebauer/aplus/MeterCamillebauerAplusImplTest.java @@ -1,27 +1,27 @@ package io.openems.edge.meter.camillebauer.aplus; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterCamillebauerAplusImplTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterCamillebauerAplusImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // - .setMeterType(MeterType.GRID).setInvert(false).build()) + .setId("component0") // + .setModbusId("modbus0") // + .setMeterType(GRID) // + .setInvert(false) // + .build()) .next(new TestCase()); } diff --git a/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300ImplTest.java b/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300ImplTest.java index c628829fba6..529cf4a7a56 100644 --- a/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300ImplTest.java +++ b/io.openems.edge.meter.carlo.gavazzi.em300/test/io/openems/edge/meter/carlo/gavazzi/em300/MeterCarloGavazziEm300ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.carlo.gavazzi.em300; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterCarloGavazziEm300ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterCarloGavazziEm300Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MeterDiscovergyImplTest.java b/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MeterDiscovergyImplTest.java index 5ae02ad71c1..4ab716e0283 100644 --- a/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MeterDiscovergyImplTest.java +++ b/io.openems.edge.meter.discovergy/test/io/openems/edge/meter/discovergy/MeterDiscovergyImplTest.java @@ -1,20 +1,19 @@ package io.openems.edge.meter.discovergy; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.common.test.ComponentTest; -import io.openems.edge.meter.api.MeterType; public class MeterDiscovergyImplTest { - private static final String COMPONENT_ID = "meter0"; - @Test public void test() throws Exception { new ComponentTest(new MeterDiscovergyImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setType(GRID) // .setPassword("xxx") // .setEmail("x@y.z") // .setSerialNumber("12345678") // diff --git a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120ImplTest.java b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120ImplTest.java index 169778c8b20..fd67482a7b2 100644 --- a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120ImplTest.java +++ b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm120/MeterEastronSdm120ImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.meter.eastron.sdm120; +import static io.openems.edge.meter.api.MeterType.GRID; +import static io.openems.edge.meter.api.SinglePhase.L1; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; -import io.openems.edge.meter.api.SinglePhase; public class MeterEastronSdm120ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterEastronSdm120Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // - .setPhase(SinglePhase.L1) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // + .setPhase(L1) // .build()) // ; } diff --git a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630ImplTest.java b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630ImplTest.java index d79ef87a631..a8eca9a1732 100644 --- a/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630ImplTest.java +++ b/io.openems.edge.meter.eastron/test/io/openems/edge/meter/eastron/sdm630/MeterEastronSdm630ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.eastron.sdm630; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterEastronSdm630ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterEastronSdm630Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511ImplTest.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511ImplTest.java index 852686d87d1..801387613e7 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511ImplTest.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg511/MeterJanitzaUmg511ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.janitza.umg511; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterJanitzaUmg511ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterJanitzaUmg511Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604ImplTest.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604ImplTest.java index 22fe623eb16..501f7b39ce7 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604ImplTest.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg604/MeterJanitzaUmg604ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.janitza.umg604; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterJanitzaUmg604ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterJanitzaUmg604Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImplTest.java b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImplTest.java index ea3f2c9ddc2..d956feeda9d 100644 --- a/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImplTest.java +++ b/io.openems.edge.meter.janitza/test/io/openems/edge/meter/janitza/umg96rme/MeterJanitzaUmg96rmeImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.janitza.umg96rme; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterJanitzaUmg96rmeImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterJanitzaUmg96rmeImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImplTest.java b/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImplTest.java index 8cc7f90bdc4..c4602b9fc50 100755 --- a/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImplTest.java +++ b/io.openems.edge.meter.kdk/test/io/openems/edge/meter/kdk/puct2/MeterKdk2puctImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.meter.kdk.puct2; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterKdk2puctImplTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterKdk2puctImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("component0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setMeterType(MeterType.GRID) // + .setMeterType(GRID) // .setInvert(false) // .build()) .next(new TestCase()); diff --git a/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImplTest.java b/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImplTest.java index c6801784fb8..be2a80b1460 100644 --- a/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImplTest.java +++ b/io.openems.edge.meter.phoenixcontact/test/io/openems/edge/meter/phoenixcontact/PhoenixContactMeterImplTest.java @@ -1,27 +1,25 @@ package io.openems.edge.meter.phoenixcontact; +import static io.openems.edge.meter.api.MeterType.PRODUCTION; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class PhoenixContactMeterImplTest { - private static final String COMPONENT_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PhoenixContactMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // - .setMeterType(MeterType.PRODUCTION) // + .setId("meter0") // + .setModbusId("modbus0") // + .setMeterType(PRODUCTION) // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImplTest.java b/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImplTest.java index b5d53bd93e7..f4d70b3cda4 100644 --- a/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImplTest.java +++ b/io.openems.edge.meter.plexlog/test/io/openems/edge/meter/plexlog/MeterPlexlogDataloggerImplTest.java @@ -1,27 +1,25 @@ package io.openems.edge.meter.plexlog; +import static io.openems.edge.meter.api.MeterType.PRODUCTION; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterPlexlogDataloggerImplTest { - private static final String COMPONENT_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterPlexlogDataloggerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setMeterType(MeterType.PRODUCTION) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setMeterType(PRODUCTION) // + .setModbusId("modbus0") // .build()) .next(new TestCase()); } diff --git a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96ImplTest.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96ImplTest.java index 403893ed299..a4ec4ab0e25 100644 --- a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96ImplTest.java +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd96/MeterPqplusUmd96ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.pqplus.umd96; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterPqplusUmd96ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterPqplusUmd96Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97ImplTest.java b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97ImplTest.java index 106baa6ae23..a530ebf98a2 100644 --- a/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97ImplTest.java +++ b/io.openems.edge.meter.pqplus/test/io/openems/edge/meter/pqplus/umd97/MeterPqplusUmd97ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.pqplus.umd97; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterPqplusUmd97ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterPqplusUmd97Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImplTest.java b/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImplTest.java index 31b7ac51617..dd79f31f65a 100644 --- a/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImplTest.java +++ b/io.openems.edge.meter.schneider.acti9.smartlink/test/io/openems/edge/meter/schneider/acti9/smartlink/MeterSchneiderActi9SmartlinkImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.schneider.acti9.smartlink; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSchneiderActi9SmartlinkImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterSchneiderActi9SmartlinkImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .setInvert(false) // .build()) // ; diff --git a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600ImplTest.java b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600ImplTest.java index 98707507163..bfe5929371c 100644 --- a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600ImplTest.java +++ b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac1600/MeterSiemensPac1600ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.siemens.pac1600; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSiemensPac1600ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterSiemensPac1600Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200ImplTest.java b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200ImplTest.java index 51a0989cde4..316d770773a 100644 --- a/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200ImplTest.java +++ b/io.openems.edge.meter.siemens/test/io/openems/edge/meter/siemens/pac2200/MeterSiemensPac2200ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.siemens.pac2200; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSiemensPac2200ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterSiemensPac2200Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .setInvert(false) // .build()) // ; diff --git a/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MeterSmaShm20ImplTest.java b/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MeterSmaShm20ImplTest.java index 2d72d2aa8ee..17f254ed9a0 100644 --- a/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MeterSmaShm20ImplTest.java +++ b/io.openems.edge.meter.sma.shm20/test/io/openems/edge/meter/sma/shm20/MeterSmaShm20ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.sma.shm20; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSmaShm20ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterSmaShm20Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImplTest.java b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImplTest.java index 7b232645b1f..874f97a5cb9 100644 --- a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImplTest.java +++ b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/singlephase/MeterSocomecSinglephaseImplTest.java @@ -1,19 +1,17 @@ package io.openems.edge.meter.socomec.singlephase; +import static io.openems.edge.meter.api.MeterType.GRID; +import static io.openems.edge.meter.api.SinglePhase.L1; + import org.junit.Before; import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; -import io.openems.edge.meter.api.SinglePhase; public class MeterSocomecSinglephaseImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - private static MeterSocomecSinglephaseImpl meter; @Before @@ -21,13 +19,13 @@ public void setup() throws Exception { meter = new MeterSocomecSinglephaseImpl(); new ComponentTest(meter) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .setInvert(false) // - .setPhase(SinglePhase.L1) // + .setPhase(L1) // .build()); // } diff --git a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImplTest.java b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImplTest.java index 399894b186f..9447af3edf4 100644 --- a/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImplTest.java +++ b/io.openems.edge.meter.socomec/test/io/openems/edge/meter/socomec/threephase/MeterSocomecThreephaseImplTest.java @@ -1,18 +1,16 @@ package io.openems.edge.meter.socomec.threephase; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Before; import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterSocomecThreephaseImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - private static MeterSocomecThreephaseImpl meter; @Before @@ -20,11 +18,11 @@ public void setup() throws Exception { meter = new MeterSocomecThreephaseImpl(); new ComponentTest(meter) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .setInvert(false) // .build()); // } diff --git a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MeterVirtualAddImplTest.java b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MeterVirtualAddImplTest.java index 69dd6d1c616..545eee8e712 100644 --- a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MeterVirtualAddImplTest.java +++ b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/add/MeterVirtualAddImplTest.java @@ -1,89 +1,59 @@ package io.openems.edge.meter.virtual.add; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L1; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L2; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.ACTIVE_POWER_L3; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.FREQUENCY; +import static io.openems.edge.meter.api.ElectricityMeter.ChannelId.VOLTAGE; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; import io.openems.edge.meter.test.DummyElectricityMeter; public class MeterVirtualAddImplTest { - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_POWER = new ChannelAddress(METER_ID, "ActivePower"); - private static final ChannelAddress METER_VOLTAGE = new ChannelAddress(METER_ID, "Voltage"); - private static final ChannelAddress METER_FREQ = new ChannelAddress(METER_ID, "Frequency"); - - private static final ChannelAddress METER_POWER_L1 = new ChannelAddress(METER_ID, "ActivePowerL1"); - private static final ChannelAddress METER_POWER_L2 = new ChannelAddress(METER_ID, "ActivePowerL2"); - private static final ChannelAddress METER_POWER_L3 = new ChannelAddress(METER_ID, "ActivePowerL3"); - - private static final String METER_ID_1 = "meter1"; - private static final ChannelAddress METER_ID_1_ACTIVEPOWER = new ChannelAddress(METER_ID_1, "ActivePower"); - private static final ChannelAddress METER_ID_1_VOLTAGE = new ChannelAddress(METER_ID_1, "Voltage"); - private static final ChannelAddress METER_ID_1_FREQUENCY = new ChannelAddress(METER_ID_1, "Frequency"); - - private static final ChannelAddress METER_ID_1_ACTIVEPOWER_L1 = new ChannelAddress(METER_ID_1, "ActivePowerL1"); - private static final ChannelAddress METER_ID_1_ACTIVEPOWER_L2 = new ChannelAddress(METER_ID_1, "ActivePowerL2"); - private static final ChannelAddress METER_ID_1_ACTIVEPOWER_L3 = new ChannelAddress(METER_ID_1, "ActivePowerL3"); - - private static final String METER_ID_2 = "meter2"; - private static final ChannelAddress METER_ID_2_ACTIVEPOWER = new ChannelAddress(METER_ID_2, "ActivePower"); - private static final ChannelAddress METER_ID_2_VOLTAGE = new ChannelAddress(METER_ID_2, "Voltage"); - private static final ChannelAddress METER_ID_2_FREQUENCY = new ChannelAddress(METER_ID_2, "Frequency"); - - private static final ChannelAddress METER_ID_2_ACTIVEPOWER_L1 = new ChannelAddress(METER_ID_2, "ActivePowerL1"); - private static final ChannelAddress METER_ID_2_ACTIVEPOWER_L2 = new ChannelAddress(METER_ID_2, "ActivePowerL2"); - private static final ChannelAddress METER_ID_2_ACTIVEPOWER_L3 = new ChannelAddress(METER_ID_2, "ActivePowerL3"); - - private static final String METER_ID_3 = "meter3"; - private static final ChannelAddress METER_ID_3_ACTIVEPOWER = new ChannelAddress(METER_ID_3, "ActivePower"); - private static final ChannelAddress METER_ID_3_VOLTAGE = new ChannelAddress(METER_ID_3, "Voltage"); - private static final ChannelAddress METER_ID_3_FREQUENCY = new ChannelAddress(METER_ID_3, "Frequency"); - - private static final ChannelAddress METER_ID_3_ACTIVEPOWER_L1 = new ChannelAddress(METER_ID_3, "ActivePowerL1"); - private static final ChannelAddress METER_ID_3_ACTIVEPOWER_L2 = new ChannelAddress(METER_ID_3, "ActivePowerL2"); - private static final ChannelAddress METER_ID_3_ACTIVEPOWER_L3 = new ChannelAddress(METER_ID_3, "ActivePowerL3"); - @Test public void test() throws Exception { new ComponentTest(new MeterVirtualAddImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("addMeter", new DummyElectricityMeter(METER_ID_1)) - .addReference("addMeter", new DummyElectricityMeter(METER_ID_2)) // - .addReference("addMeter", new DummyElectricityMeter(METER_ID_3)) // + .addReference("addMeter", new DummyElectricityMeter("meter1")) + .addReference("addMeter", new DummyElectricityMeter("meter2")) // + .addReference("addMeter", new DummyElectricityMeter("meter3")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setMeterIds(METER_ID_1, METER_ID_2, METER_ID_3) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setMeterIds("meter1", "meter2", "meter3") // + .setType(GRID) // .build()) .next(new TestCase("one") // - .input(METER_ID_1_ACTIVEPOWER, 6_000) // - .input(METER_ID_1_ACTIVEPOWER_L1, 2_000) // - .input(METER_ID_1_ACTIVEPOWER_L2, 2_000) // - .input(METER_ID_1_ACTIVEPOWER_L3, 2_000) // - .input(METER_ID_2_ACTIVEPOWER, 7_500) // - .input(METER_ID_2_ACTIVEPOWER_L1, 2_500) // - .input(METER_ID_2_ACTIVEPOWER_L2, 2_500) // - .input(METER_ID_2_ACTIVEPOWER_L3, 2_500) // - .input(METER_ID_3_ACTIVEPOWER, 9_000) // - .input(METER_ID_3_ACTIVEPOWER_L1, 3_000) // - .input(METER_ID_3_ACTIVEPOWER_L2, 3_000) // - .input(METER_ID_3_ACTIVEPOWER_L3, 3_000) // - .input(METER_ID_1_VOLTAGE, 10) // - .input(METER_ID_2_VOLTAGE, 20) // - .input(METER_ID_3_VOLTAGE, 30) // - .input(METER_ID_1_FREQUENCY, 49) // - .input(METER_ID_2_FREQUENCY, 51) // - .input(METER_ID_3_FREQUENCY, 56)) // + .input("meter1", ACTIVE_POWER, 6_000) // + .input("meter1", ACTIVE_POWER_L1, 2_000) // + .input("meter1", ACTIVE_POWER_L2, 2_000) // + .input("meter1", ACTIVE_POWER_L3, 2_000) // + .input("meter2", ACTIVE_POWER, 7_500) // + .input("meter2", ACTIVE_POWER_L1, 2_500) // + .input("meter2", ACTIVE_POWER_L2, 2_500) // + .input("meter2", ACTIVE_POWER_L3, 2_500) // + .input("meter3", ACTIVE_POWER, 9_000) // + .input("meter3", ACTIVE_POWER_L1, 3_000) // + .input("meter3", ACTIVE_POWER_L2, 3_000) // + .input("meter3", ACTIVE_POWER_L3, 3_000) // + .input("meter1", VOLTAGE, 10) // + .input("meter2", VOLTAGE, 20) // + .input("meter3", VOLTAGE, 30) // + .input("meter1", FREQUENCY, 49) // + .input("meter2", FREQUENCY, 51) // + .input("meter3", FREQUENCY, 56)) // .next(new TestCase("two") // - .output(METER_POWER, 22_500) // - .output(METER_POWER_L1, 7_500) // - .output(METER_POWER_L2, 7_500) // - .output(METER_POWER_L3, 7_500) // - .output(METER_VOLTAGE, 20) // - .output(METER_FREQ, 52)); + .output(ACTIVE_POWER, 22_500) // + .output(ACTIVE_POWER_L1, 7_500) // + .output(ACTIVE_POWER_L2, 7_500) // + .output(ACTIVE_POWER_L3, 7_500) // + .output(VOLTAGE, 20) // + .output(FREQUENCY, 52)); } } diff --git a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImplTest.java b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImplTest.java index d92470e3068..aa560e8dc04 100644 --- a/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImplTest.java +++ b/io.openems.edge.meter.virtual/test/io/openems/edge/meter/virtual/subtract/VirtualSubtractMeterImplTest.java @@ -1,60 +1,50 @@ package io.openems.edge.meter.virtual.subtract; -import org.junit.Test; +import static io.openems.edge.meter.api.MeterType.GRID; + +import java.util.List; -import com.google.common.collect.Lists; +import org.junit.Test; -import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; -import io.openems.edge.meter.api.MeterType; +import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.meter.test.DummyElectricityMeter; public class VirtualSubtractMeterImplTest { - private static final String METER_ID = "meter0"; - private static final ChannelAddress METER_POWER = new ChannelAddress(METER_ID, "ActivePower"); - - private static final String MINUEND_ID = "meter1"; - private static final ChannelAddress MINUEND_POWER = new ChannelAddress(MINUEND_ID, "ActivePower"); - - private static final String SUBTRAHEND1_ID = "meter2"; - private static final ChannelAddress SUBTRAHEND1_POWER = new ChannelAddress(SUBTRAHEND1_ID, "ActivePower"); - - private static final String SUBTRAHEND2_ID = "ess0"; - private static final ChannelAddress SUBTRAHEND2_POWER = new ChannelAddress(SUBTRAHEND2_ID, "ActivePower"); - @Test public void test() throws Exception { new ComponentTest(new VirtualSubtractMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("minuend", new DummyElectricityMeter(MINUEND_ID)) // - .addReference("subtrahends", Lists.newArrayList(// - new DummyElectricityMeter(SUBTRAHEND1_ID), // - new DummyManagedSymmetricEss(SUBTRAHEND2_ID))) // + .addReference("minuend", new DummyElectricityMeter("meter1")) // + .addReference("subtrahends", List.of(// + new DummyElectricityMeter("meter2"), // + new DummyManagedSymmetricEss("ess0"))) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setType(GRID) // .setAddToSum(true) // - .setMinuendId(MINUEND_ID) // - .setSubtrahendsIds(SUBTRAHEND1_ID, SUBTRAHEND2_ID) // + .setMinuendId("meter1") // + .setSubtrahendsIds("meter2", "ess0") // .build()) // .next(new TestCase() // - .input(MINUEND_POWER, 5_000) // - .input(SUBTRAHEND1_POWER, 2_000) // - .input(SUBTRAHEND2_POWER, 4_000) // - .output(METER_POWER, -1000)) // + .input("meter1", ElectricityMeter.ChannelId.ACTIVE_POWER, 5_000) // + .input("meter2", ElectricityMeter.ChannelId.ACTIVE_POWER, 2_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 4_000) // + .output("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, -1000)) // .next(new TestCase() // - .input(MINUEND_POWER, null) // - .input(SUBTRAHEND1_POWER, 2_000) // - .input(SUBTRAHEND2_POWER, 4_000) // - .output(METER_POWER, null)) // + .input("meter1", ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .input("meter2", ElectricityMeter.ChannelId.ACTIVE_POWER, 2_000) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 4_000) // + .output("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, null)) // .next(new TestCase() // - .input(MINUEND_POWER, 5_000) // - .input(SUBTRAHEND1_POWER, null) // - .input(SUBTRAHEND2_POWER, 4_000) // - .output(METER_POWER, 1000)); + .input("meter1", ElectricityMeter.ChannelId.ACTIVE_POWER, 5_000) // + .input("meter2", ElectricityMeter.ChannelId.ACTIVE_POWER, null) // + .input("ess0", SymmetricEss.ChannelId.ACTIVE_POWER, 4_000) // + .output("meter0", ElectricityMeter.ChannelId.ACTIVE_POWER, 1000)); } } \ No newline at end of file diff --git a/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MeterWeidmueller525ImplTest.java b/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MeterWeidmueller525ImplTest.java index cb447a0c407..fdcc894d9de 100644 --- a/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MeterWeidmueller525ImplTest.java +++ b/io.openems.edge.meter.weidmueller/test/io/openems/edge/meter/weidmueller/MeterWeidmueller525ImplTest.java @@ -1,26 +1,24 @@ package io.openems.edge.meter.weidmueller; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterWeidmueller525ImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterWeidmueller525Impl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // - .setType(MeterType.GRID) // + .setId("meter0") // + .setModbusId("modbus0") // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImplTest.java b/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImplTest.java index 11397a94eee..ac18f9e6332 100644 --- a/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImplTest.java +++ b/io.openems.edge.meter.ziehl/test/io/openems/edge/meter/ziehl/efr4001ip/MeterZiehlEfr4001IpImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.meter.ziehl.efr4001ip; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class MeterZiehlEfr4001IpImplTest { - private static final String COMPONENT_ID = "component0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new MeterZiehlEfr4001IpImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setModbusId(MODBUS_ID) // + .setId("component0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setMeterType(MeterType.GRID) // + .setMeterType(GRID) // .setInvert(false) // .build()) .next(new TestCase()); diff --git a/io.openems.edge.predictor.persistencemodel/test/io/openems/edge/predictor/persistencemodel/PredictorPersistenceModelImplTest.java b/io.openems.edge.predictor.persistencemodel/test/io/openems/edge/predictor/persistencemodel/PredictorPersistenceModelImplTest.java index 4cd7b472cdd..d0d95b109eb 100644 --- a/io.openems.edge.predictor.persistencemodel/test/io/openems/edge/predictor/persistencemodel/PredictorPersistenceModelImplTest.java +++ b/io.openems.edge.predictor.persistencemodel/test/io/openems/edge/predictor/persistencemodel/PredictorPersistenceModelImplTest.java @@ -1,34 +1,28 @@ package io.openems.edge.predictor.persistencemodel; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static io.openems.edge.predictor.api.prediction.LogVerbosity.NONE; import static io.openems.edge.predictor.api.prediction.Prediction.EMPTY_PREDICTION; +import static java.time.temporal.ChronoUnit.HOURS; import static org.junit.Assert.assertEquals; -import java.time.Instant; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import org.junit.Test; -import io.openems.common.test.TimeLeapClock; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; -import io.openems.edge.predictor.api.prediction.LogVerbosity; import io.openems.edge.timedata.test.DummyTimedata; public class PredictorPersistenceModelImplTest { - private static final String TIMEDATA_ID = "timedata0"; - private static final String PREDICTOR_ID = "predictor0"; - private static final ChannelAddress METER1_ACTIVE_POWER = new ChannelAddress("meter1", "ActivePower"); @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); + final var clock = createDummyClock(); int[] values = { // Day 1 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 9, 146, 348, 636, 1192, 2092, 2882, 3181, @@ -43,7 +37,7 @@ public void test() throws Exception { 477, 501, 547, 589, 1067, 13304, 17367, 14825, 13654, 12545, 8371, 10468, 9810, 8537, 6228, 3758, 4131, 3572, 1698, 1017, 569, 188, 14, 2, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - var timedata = new DummyTimedata(TIMEDATA_ID); + var timedata = new DummyTimedata("timedata0"); var start = ZonedDateTime.of(2019, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC")); for (var i = 0; i < values.length; i++) { timedata.add(start.plusMinutes(i * 15), METER1_ACTIVE_POWER, values[i]); @@ -55,9 +49,9 @@ public void test() throws Exception { .addReference("timedata", timedata) // .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(PREDICTOR_ID) // + .setId("predictor0") // .setChannelAddresses(METER1_ACTIVE_POWER.toString()) // - .setLogVerbosity(LogVerbosity.NONE) // + .setLogVerbosity(NONE) // .build()); var prediction = sut.getPrediction(METER1_ACTIVE_POWER); @@ -74,7 +68,7 @@ public void test() throws Exception { @Test public void test2() throws Exception { var start = ZonedDateTime.of(2019, 12, 30, 0, 0, 0, 0, ZoneId.of("UTC")); - final var clock = new TimeLeapClock(start.toInstant(), ZoneOffset.UTC); + final var clock = createDummyClock(); int[] values = { // Day 1 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 19, 74, 323, @@ -87,7 +81,7 @@ public void test2() throws Exception { 7320, 5950, 5644, 7157, 6847, 6549, 6498, 6296, 6096, 5895, 5658, 5372, 5011, 4603, 4159, 3831, 3400, 2757, 727, 194, 70, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - var timedata = new DummyTimedata(TIMEDATA_ID); + var timedata = new DummyTimedata("timedata0"); for (var i = 0; i < values.length; i++) { timedata.add(start.plusMinutes(i * 15), METER1_ACTIVE_POWER, values[i]); } @@ -98,30 +92,29 @@ public void test2() throws Exception { .addReference("timedata", timedata) // .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(PREDICTOR_ID) // + .setId("predictor0") // .setChannelAddresses(METER1_ACTIVE_POWER.toString()) // - .setLogVerbosity(LogVerbosity.NONE) // + .setLogVerbosity(NONE) // .build()); - clock.leap(39, ChronoUnit.HOURS); + clock.leap(39, HOURS); sut.getPrediction(METER1_ACTIVE_POWER); } @Test public void testEmpty() throws Exception { - final var clock = new TimeLeapClock(Instant.ofEpochSecond(1577836800) /* starts at 1. January 2020 00:00:00 */, - ZoneOffset.UTC); - var timedata = new DummyTimedata(TIMEDATA_ID); + final var clock = createDummyClock(); + var timedata = new DummyTimedata("timedata0"); var sut = new PredictorPersistenceModelImpl(); new ComponentTest(sut) // .addReference("timedata", timedata) // .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(PREDICTOR_ID) // + .setId("predictor0") // .setChannelAddresses(METER1_ACTIVE_POWER.toString()) // - .setLogVerbosity(LogVerbosity.NONE) // + .setLogVerbosity(NONE) // .build()); assertEquals(EMPTY_PREDICTION, sut.getPrediction(METER1_ACTIVE_POWER)); diff --git a/io.openems.edge.predictor.similardaymodel/test/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImplTest.java b/io.openems.edge.predictor.similardaymodel/test/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImplTest.java index 6374c6a2b55..8f9e3d25fd3 100644 --- a/io.openems.edge.predictor.similardaymodel/test/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImplTest.java +++ b/io.openems.edge.predictor.similardaymodel/test/io/openems/edge/predictor/similardaymodel/PredictorSimilardayModelImplTest.java @@ -18,9 +18,6 @@ public class PredictorSimilardayModelImplTest { - private static final String TIMEDATA_ID = "timedata0"; - private static final String PREDICTOR_ID = "predictor0"; - private static final ChannelAddress METER1_ACTIVE_POWER = new ChannelAddress("meter1", "ActivePower"); @Test @@ -32,7 +29,7 @@ public void test() throws Exception { var values = Data.data; var predictedValues = Data.predictedData; - var timedata = new DummyTimedata(TIMEDATA_ID); + var timedata = new DummyTimedata("timedata0"); var start = ZonedDateTime.of(2019, 12, 1, 0, 0, 0, 0, ZoneId.of("UTC")); for (var i = 0; i < values.length; i++) { @@ -45,7 +42,7 @@ public void test() throws Exception { .addReference("timedata", timedata) // .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(PREDICTOR_ID) // + .setId("predictor0") // .setNumOfWeeks(4) // .setChannelAddresses(METER1_ACTIVE_POWER.toString()) // .setLogVerbosity(LogVerbosity.NONE) // diff --git a/io.openems.edge.pvinverter.cluster/test/io/openems/edge/pvinverter/cluster/PvInverterClusterImplTest.java b/io.openems.edge.pvinverter.cluster/test/io/openems/edge/pvinverter/cluster/PvInverterClusterImplTest.java index 8ea8a4efda1..c5d1d1c31a6 100644 --- a/io.openems.edge.pvinverter.cluster/test/io/openems/edge/pvinverter/cluster/PvInverterClusterImplTest.java +++ b/io.openems.edge.pvinverter.cluster/test/io/openems/edge/pvinverter/cluster/PvInverterClusterImplTest.java @@ -8,14 +8,12 @@ public class PvInverterClusterImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterClusterImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setPvInverterIds() // .build()) // .next(new TestCase()) // diff --git a/io.openems.edge.pvinverter.fronius/test/io/openems/edge/pvinverter/fronius/PvInverterFroniusImplTest.java b/io.openems.edge.pvinverter.fronius/test/io/openems/edge/pvinverter/fronius/PvInverterFroniusImplTest.java index 04e6f406c5f..db5e7b321d7 100644 --- a/io.openems.edge.pvinverter.fronius/test/io/openems/edge/pvinverter/fronius/PvInverterFroniusImplTest.java +++ b/io.openems.edge.pvinverter.fronius/test/io/openems/edge/pvinverter/fronius/PvInverterFroniusImplTest.java @@ -8,18 +8,15 @@ public class PvInverterFroniusImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterFroniusImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // .build()) // ; diff --git a/io.openems.edge.pvinverter.kaco.blueplanet/test/io/openems/edge/pvinverter/kaco/blueplanet/PvInverterKacoBlueplanetImplTest.java b/io.openems.edge.pvinverter.kaco.blueplanet/test/io/openems/edge/pvinverter/kaco/blueplanet/PvInverterKacoBlueplanetImplTest.java index 80811823b8e..af619a2deb4 100644 --- a/io.openems.edge.pvinverter.kaco.blueplanet/test/io/openems/edge/pvinverter/kaco/blueplanet/PvInverterKacoBlueplanetImplTest.java +++ b/io.openems.edge.pvinverter.kaco.blueplanet/test/io/openems/edge/pvinverter/kaco/blueplanet/PvInverterKacoBlueplanetImplTest.java @@ -8,18 +8,15 @@ public class PvInverterKacoBlueplanetImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterKacoBlueplanetImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // .build()) // ; diff --git a/io.openems.edge.pvinverter.kostal/test/io/openems/edge/pvinverter/kostal/PvInverterKostalImplTest.java b/io.openems.edge.pvinverter.kostal/test/io/openems/edge/pvinverter/kostal/PvInverterKostalImplTest.java index d9b74670758..c9f44649eb9 100644 --- a/io.openems.edge.pvinverter.kostal/test/io/openems/edge/pvinverter/kostal/PvInverterKostalImplTest.java +++ b/io.openems.edge.pvinverter.kostal/test/io/openems/edge/pvinverter/kostal/PvInverterKostalImplTest.java @@ -8,18 +8,15 @@ public class PvInverterKostalImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterKostalImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // .build()) // ; diff --git a/io.openems.edge.pvinverter.sma/test/io/openems/edge/pvinverter/sma/PvInverterSmaSunnyTripowerImplTest.java b/io.openems.edge.pvinverter.sma/test/io/openems/edge/pvinverter/sma/PvInverterSmaSunnyTripowerImplTest.java index 27b7a2d9d03..6e1b94cf7b0 100644 --- a/io.openems.edge.pvinverter.sma/test/io/openems/edge/pvinverter/sma/PvInverterSmaSunnyTripowerImplTest.java +++ b/io.openems.edge.pvinverter.sma/test/io/openems/edge/pvinverter/sma/PvInverterSmaSunnyTripowerImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.pvinverter.sma; +import static io.openems.edge.pvinverter.sunspec.Phase.ALL; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.pvinverter.sunspec.Phase; public class PvInverterSmaSunnyTripowerImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterSmaSunnyTripowerImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setPhase(Phase.ALL) // + .setPhase(ALL) // .build()) // ; } diff --git a/io.openems.edge.pvinverter.solarlog/test/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImplTest.java b/io.openems.edge.pvinverter.solarlog/test/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImplTest.java index 7d1f87a7b69..778f9f85f63 100644 --- a/io.openems.edge.pvinverter.solarlog/test/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImplTest.java +++ b/io.openems.edge.pvinverter.solarlog/test/io/openems/edge/pvinverter/solarlog/PvInverterSolarlogImplTest.java @@ -8,17 +8,14 @@ public class PvInverterSolarlogImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new PvInverterSolarlogImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .build()) // ; } diff --git a/io.openems.edge.scheduler.allalphabetically/test/io/openems/edge/scheduler/allalphabetically/SchedulerAllAlphabeticallyImplTest.java b/io.openems.edge.scheduler.allalphabetically/test/io/openems/edge/scheduler/allalphabetically/SchedulerAllAlphabeticallyImplTest.java index 204e4e48109..0b4ca7c84d5 100644 --- a/io.openems.edge.scheduler.allalphabetically/test/io/openems/edge/scheduler/allalphabetically/SchedulerAllAlphabeticallyImplTest.java +++ b/io.openems.edge.scheduler.allalphabetically/test/io/openems/edge/scheduler/allalphabetically/SchedulerAllAlphabeticallyImplTest.java @@ -18,33 +18,25 @@ public class SchedulerAllAlphabeticallyImplTest { - private static final String SCHEDULER_ID = "scheduler0"; - - private static final String CTRL0_ID = "ctrl0"; - private static final String CTRL1_ID = "ctrl1"; - private static final String CTRL2_ID = "ctrl2"; - private static final String CTRL3_ID = "ctrl3"; - private static final String CTRL4_ID = "ctrl4"; - @Test public void testWithFixedPriorities() throws Exception { final SchedulerAllAlphabetically sut = new SchedulerAllAlphabeticallyImpl(); new ComponentTest(sut) // .addReference("componentManager", new DummyComponentManager()) // - .addComponent(new DummyController(CTRL0_ID)) // - .addComponent(new DummyController(CTRL1_ID)) // - .addComponent(new DummyController(CTRL2_ID)) // - .addComponent(new DummyController(CTRL3_ID)) // - .addComponent(new DummyController(CTRL4_ID)) // + .addComponent(new DummyController("ctrl0")) // + .addComponent(new DummyController("ctrl1")) // + .addComponent(new DummyController("ctrl2")) // + .addComponent(new DummyController("ctrl3")) // + .addComponent(new DummyController("ctrl4")) // .activate(MyConfig.create() // - .setId(SCHEDULER_ID) // - .setControllersIds(CTRL2_ID, CTRL1_ID, "") // + .setId("scheduler0") // + .setControllersIds("ctrl2", "ctrl1", "") // .build()) .next(new TestCase()) // .deactivate(); assertEquals(// - Arrays.asList(CTRL2_ID, CTRL1_ID, CTRL0_ID, CTRL3_ID, CTRL4_ID), // + Arrays.asList("ctrl2", "ctrl1", "ctrl0", "ctrl3", "ctrl4"), // getControllerIds(sut)); } @@ -77,7 +69,7 @@ public void testOnlyAlphabeticalOrdering() throws Exception { test // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(SCHEDULER_ID) // + .setId("scheduler0") // .setControllersIds() // .build()) // .next(new TestCase()); diff --git a/io.openems.edge.scheduler.daily/test/io/openems/edge/scheduler/daily/SchedulerDailyImplTest.java b/io.openems.edge.scheduler.daily/test/io/openems/edge/scheduler/daily/SchedulerDailyImplTest.java index 9f0d5e94cb1..f992c074e95 100644 --- a/io.openems.edge.scheduler.daily/test/io/openems/edge/scheduler/daily/SchedulerDailyImplTest.java +++ b/io.openems.edge.scheduler.daily/test/io/openems/edge/scheduler/daily/SchedulerDailyImplTest.java @@ -1,18 +1,16 @@ package io.openems.edge.scheduler.daily; +import static io.openems.common.utils.JsonUtils.buildJsonArray; +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.edge.common.test.TestUtils.createDummyClock; +import static java.time.temporal.ChronoUnit.HOURS; import static org.junit.Assert.assertEquals; -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; import java.util.List; import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; -import io.openems.common.utils.JsonUtils; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -21,56 +19,49 @@ public class SchedulerDailyImplTest { - private static final String SCHEDULER_ID = "scheduler0"; - - private static final String CTRL0_ID = "ctrl0"; - private static final String CTRL1_ID = "ctrl1"; - private static final String CTRL2_ID = "ctrl2"; - private static final String CTRL3_ID = "ctrl3"; - private static final String CTRL4_ID = "ctrl4"; - @Test public void test() throws Exception { - final var clock = new TimeLeapClock(Instant.parse("2020-01-01T00:00:00.00Z"), ZoneOffset.UTC); + final var clock = createDummyClock(); final SchedulerDaily sut = new SchedulerDailyImpl(); new ComponentTest(sut) // .addReference("componentManager", new DummyComponentManager(clock)) // - .addComponent(new DummyController(CTRL0_ID)) // - .addComponent(new DummyController(CTRL1_ID)) // - .addComponent(new DummyController(CTRL2_ID)) // - .addComponent(new DummyController(CTRL3_ID)) // - .addComponent(new DummyController(CTRL4_ID)) // + .addComponent(new DummyController("ctrl0")) // + .addComponent(new DummyController("ctrl1")) // + .addComponent(new DummyController("ctrl2")) // + .addComponent(new DummyController("ctrl3")) // + .addComponent(new DummyController("ctrl4")) // .activate(MyConfig.create() // - .setId(SCHEDULER_ID) // - .setAlwaysRunBeforeControllerIds(CTRL2_ID).setControllerScheduleJson(JsonUtils.buildJsonArray() // - .add(JsonUtils.buildJsonObject() // + .setId("scheduler0") // + .setAlwaysRunBeforeControllerIds("ctrl2") // + .setControllerScheduleJson(buildJsonArray() // + .add(buildJsonObject() // .addProperty("time", "08:00:00") // - .add("controllers", JsonUtils.buildJsonArray() // - .add(CTRL0_ID) // + .add("controllers", buildJsonArray() // + .add("ctrl0") // .build()) // .build()) // - .add(JsonUtils.buildJsonObject() // + .add(buildJsonObject() // .addProperty("time", "13:45:00") // - .add("controllers", JsonUtils.buildJsonArray() // - .add(CTRL4_ID) // + .add("controllers", buildJsonArray() // + .add("ctrl4") // .build()) // .build()) // .build().toString()) - .setAlwaysRunAfterControllerIds(CTRL3_ID, CTRL1_ID) // + .setAlwaysRunAfterControllerIds("ctrl3", "ctrl1") // .build()) // .next(new TestCase("00:00") // .onBeforeControllersCallbacks(() -> assertEquals(// - Arrays.asList(CTRL2_ID, CTRL4_ID, CTRL3_ID, CTRL1_ID), // + List.of("ctrl2", "ctrl4", "ctrl3", "ctrl1"), // getControllerIds(sut)))) // .next(new TestCase("12:00") // - .timeleap(clock, 12, ChronoUnit.HOURS) // + .timeleap(clock, 12, HOURS) // .onBeforeControllersCallbacks(() -> assertEquals(// - Arrays.asList(CTRL2_ID, CTRL0_ID, CTRL3_ID, CTRL1_ID), // + List.of("ctrl2", "ctrl0", "ctrl3", "ctrl1"), // getControllerIds(sut)))) .next(new TestCase("14:00") // - .timeleap(clock, 12, ChronoUnit.HOURS) // + .timeleap(clock, 12, HOURS) // .onBeforeControllersCallbacks(() -> assertEquals(// - Arrays.asList(CTRL2_ID, CTRL4_ID, CTRL3_ID, CTRL1_ID), // + List.of("ctrl2", "ctrl4", "ctrl3", "ctrl1"), // getControllerIds(sut)))); } diff --git a/io.openems.edge.scheduler.fixedorder/test/io/openems/edge/scheduler/fixedorder/SchedulerFixedOrderImplTest.java b/io.openems.edge.scheduler.fixedorder/test/io/openems/edge/scheduler/fixedorder/SchedulerFixedOrderImplTest.java index 47bc0a88a65..710c1adddc0 100644 --- a/io.openems.edge.scheduler.fixedorder/test/io/openems/edge/scheduler/fixedorder/SchedulerFixedOrderImplTest.java +++ b/io.openems.edge.scheduler.fixedorder/test/io/openems/edge/scheduler/fixedorder/SchedulerFixedOrderImplTest.java @@ -2,7 +2,6 @@ import static org.junit.Assert.assertEquals; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -40,7 +39,7 @@ public void test() throws Exception { test.next(new TestCase()); // assertEquals(// - Arrays.asList(CTRL3_ID, CTRL1_ID), // + List.of(CTRL3_ID, CTRL1_ID), // getControllerIds(sut)); } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/datasource/api/DummyDatasource.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/datasource/api/DummyDatasource.java new file mode 100644 index 00000000000..0f1a58189f5 --- /dev/null +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/datasource/api/DummyDatasource.java @@ -0,0 +1,38 @@ +package io.openems.edge.simulator.datasource.api; + +import java.util.List; +import java.util.Set; + +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.OpenemsType; + +public class DummyDatasource implements SimulatorDatasource { + + public final Object value; + + public DummyDatasource(Object value) { + this.value = value; + } + + @Override + public Set getKeys() { + return Set.of(); + } + + @Override + public int getTimeDelta() { + return 0; + } + + @SuppressWarnings("unchecked") + @Override + public List getValues(OpenemsType type, ChannelAddress channelAddress) { + return (List) List.of(this.value); + } + + @SuppressWarnings("unchecked") + @Override + public T getValue(OpenemsType type, ChannelAddress channelAddress) { + return (T) this.value; + } +} diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcs.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcs.java index 59c4acf3eef..bd93a48548f 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcs.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcs.java @@ -5,13 +5,12 @@ import io.openems.common.channel.Unit; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; -import io.openems.edge.common.channel.LongReadChannel; -import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.ManagedEvcs; +import io.openems.edge.meter.api.ElectricityMeter; -public interface SimulatorEvcs extends ManagedEvcs, Evcs, OpenemsComponent, EventHandler { +public interface SimulatorEvcs extends ManagedEvcs, Evcs, ElectricityMeter, OpenemsComponent, EventHandler { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { SIMULATED_CHARGE_POWER(Doc.of(OpenemsType.INTEGER).unit(Unit.WATT)); @@ -27,24 +26,4 @@ public Doc doc() { return this.doc; } } - - @Override - public default Value getActiveConsumptionEnergy() { - return this.getActiveConsumptionEnergyChannel().getNextValue(); - } - - @Override - public default void _setActiveConsumptionEnergy(long value) { - this.channel(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY).setNextValue(value); - } - - @Override - public default void _setActiveConsumptionEnergy(Long value) { - this.channel(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY).setNextValue(value); - } - - @Override - public default LongReadChannel getActiveConsumptionEnergyChannel() { - return this.channel(Evcs.ChannelId.ACTIVE_CONSUMPTION_ENERGY); - } } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcsImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcsImpl.java index 371c2ed8619..96f489d6ce9 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcsImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/evcs/SimulatorEvcsImpl.java @@ -24,6 +24,7 @@ import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; import io.openems.edge.evcs.api.Status; +import io.openems.edge.meter.api.ElectricityMeter; @Designate(ocd = Config.class, factory = true) @Component(// @@ -36,7 +37,7 @@ EdgeEventConstants.TOPIC_CYCLE_EXECUTE_WRITE, // }) public class SimulatorEvcsImpl extends AbstractManagedEvcsComponent - implements SimulatorEvcs, ManagedEvcs, Evcs, OpenemsComponent, EventHandler { + implements SimulatorEvcs, ManagedEvcs, Evcs, ElectricityMeter, OpenemsComponent, EventHandler { @Reference private EvcsPower evcsPower; @@ -49,6 +50,7 @@ public class SimulatorEvcsImpl extends AbstractManagedEvcsComponent public SimulatorEvcsImpl() { super(// OpenemsComponent.ChannelId.values(), // + ElectricityMeter.ChannelId.values(), // ManagedEvcs.ChannelId.values(), // Evcs.ChannelId.values(), // SimulatorEvcs.ChannelId.values() // @@ -91,18 +93,18 @@ public void handleEvent(Event event) { private void updateChannels() { int chargePowerLimit = this.getSetChargePowerLimit().orElse(0); - this._setChargePower(chargePowerLimit); + this._setActivePower(chargePowerLimit); /* * Set Simulated "meter" Active Power */ - this._setChargePower(chargePowerLimit); + this._setActivePower(chargePowerLimit); /* * Set calculated energy */ var timeDiff = ChronoUnit.MILLIS.between(this.lastUpdate, LocalDateTime.now()); - var energyTransfered = timeDiff / 1000.0 / 60.0 / 60.0 * this.getChargePower().orElse(0); + var energyTransfered = timeDiff / 1000.0 / 60.0 / 60.0 * this.getActivePower().orElse(0); this.exactEnergySession = this.exactEnergySession + energyTransfered; this._setEnergySession((int) this.exactEnergySession); @@ -112,7 +114,7 @@ private void updateChannels() { @Override public String debugLog() { - return this.getChargePower().asString(); + return this.getActivePower().asString(); } @Override @@ -138,7 +140,7 @@ public boolean getConfiguredDebugMode() { @Override public boolean applyChargePowerLimit(int power) throws OpenemsException { this._setSetChargePowerLimit(power); - this._setChargePower(power); + this._setActivePower(power); this._setStatus(power > 0 ? Status.CHARGING : Status.CHARGING_REJECTED); return true; } diff --git a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java index 215d25ac505..c290e16d03b 100644 --- a/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java +++ b/io.openems.edge.simulator/src/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImpl.java @@ -23,6 +23,8 @@ import org.osgi.service.event.EventHandler; import org.osgi.service.event.propertytypes.EventTopics; import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.openems.common.types.ChannelAddress; import io.openems.common.types.OpenemsType; @@ -39,8 +41,6 @@ import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateEnergyFromPower; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Designate(ocd = Config.class, factory = true) @Component(// diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/battery/SimulatorBatteryImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/battery/SimulatorBatteryImplTest.java index 0762e332f6d..d2f19787009 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/battery/SimulatorBatteryImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/battery/SimulatorBatteryImplTest.java @@ -7,13 +7,11 @@ public class SimulatorBatteryImplTest { - private static final String COMPONENT_ID = "battery0"; - @Test public void test() throws Exception { new ComponentTest(new SimulatorBatteryImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("battery0") // .setCapacityKWh(20) // .setChargeMaxCurrent(40) // .setChargeMaxVoltage(700) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/csv/predefined/SimulatorDatasourceCsvPredefinedImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/csv/predefined/SimulatorDatasourceCsvPredefinedImplTest.java index 1ba5d54f0ca..c10d2e08e70 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/csv/predefined/SimulatorDatasourceCsvPredefinedImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/csv/predefined/SimulatorDatasourceCsvPredefinedImplTest.java @@ -9,14 +9,12 @@ public class SimulatorDatasourceCsvPredefinedImplTest { - private static final String COMPONENT_ID = "datasource0"; - @Test public void test() throws Exception { new ComponentTest(new SimulatorDatasourceCsvPredefinedImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("datasource0") // .setFactor(1) // .setFormat(CsvFormat.ENGLISH) // .setSource(Source.H0_HOUSEHOLD_SUMMER_WEEKDAY_NON_REGULATED_CONSUMPTION) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/single/direct/SimulatorDatasourceSingleDirectImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/single/direct/SimulatorDatasourceSingleDirectImplTest.java index 7ac397de8c1..2328e523a45 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/single/direct/SimulatorDatasourceSingleDirectImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/datasource/single/direct/SimulatorDatasourceSingleDirectImplTest.java @@ -8,14 +8,12 @@ public class SimulatorDatasourceSingleDirectImplTest { - private static final String COMPONENT_ID = "datasource0"; - @Test public void test() throws Exception { new ComponentTest(new SimulatorDatasourceSingleDirectImpl()) // .addReference("componentManager", new DummyComponentManager()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("datasource0") // .setTimeDelta(0) // .setValues() // .build()) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/evcs/SimulatorEvcsImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/evcs/SimulatorEvcsImplTest.java index 5090dd90d7d..4aed1982e28 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/evcs/SimulatorEvcsImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/evcs/SimulatorEvcsImplTest.java @@ -10,15 +10,13 @@ public class SimulatorEvcsImplTest { - private static final String ESS_ID = "evcs0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorEvcsImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("evcsPower", new DummyEvcsPower()) // .activate(MyConfig.create() // - .setId(ESS_ID) // + .setId("evcs0") // .setMinHwPower(1000) // .setMaxHwPower(10000) // .build()) // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/io/SimulatorIoDigitalInputOutputImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/io/SimulatorIoDigitalInputOutputImplTest.java index c147bc6a0b1..bd83f9838c9 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/io/SimulatorIoDigitalInputOutputImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/io/SimulatorIoDigitalInputOutputImplTest.java @@ -8,13 +8,11 @@ public class SimulatorIoDigitalInputOutputImplTest { - private static final String COMPONENT_ID = "io0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorIoDigitalInputOutputImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("io0") // .setNumberOfOutputs(3) // .build()) // .next(new TestCase()); // diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java index 59d29433d83..dd2d9614318 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/acting/SimulatorGridMeterActingImplTest.java @@ -7,28 +7,27 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.test.TimeLeapClock; +import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.simulator.datasource.csv.direct.SimulatorDatasourceCsvDirectImpl; +import io.openems.edge.simulator.datasource.api.DummyDatasource; public class SimulatorGridMeterActingImplTest { - private static final String COMPONENT_ID = "meter0"; - private static final String DATASOURCE_ID = "datasource0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorGridMeterActingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // + .addReference("datasource", new DummyDatasource(123)) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("meter0") // .setStartTime("")// .needFrequencyStepResponse(false)// - .setDatasourceId(DATASOURCE_ID) // - .build()); // - // .next(new TestCase()); // TODO requires DummyDatasource + .setDatasourceId("datasource0") // + .build()) // + .next(new TestCase()) // + .deactivate(); } @Test @@ -37,13 +36,13 @@ public void test1() throws OpenemsException, Exception { new ComponentTest(new SimulatorGridMeterActingImpl()) // .addReference("componentManager", new DummyComponentManager(clock)) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // + .addReference("datasource", new DummyDatasource(123)) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("meter0") // .setStartTime("")// .needFrequencyStepResponse(true)// - .setDatasourceId(DATASOURCE_ID) // - .build()); // - // .next(new TestCase()); // TODO requires DummyDatasource + .setDatasourceId("datasource0") // + .build()) // + .deactivate(); } } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImplTest.java index 96222001f3e..dedc66db72d 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/meter/grid/reacting/SimulatorGridMeterReactingImplTest.java @@ -9,14 +9,12 @@ public class SimulatorGridMeterReactingImplTest { - private static final String COMPONENT_ID = "meter0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorGridMeterReactingImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("meter0") // .build()) // .next(new TestCase()); } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/modbus/SimulatorModbusImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/modbus/SimulatorModbusImplTest.java index 3a60a20f877..6f059aa6a80 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/modbus/SimulatorModbusImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/modbus/SimulatorModbusImplTest.java @@ -8,13 +8,11 @@ public class SimulatorModbusImplTest { - private static final String COMPONENT_ID = "modbus0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorModbusImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("modbus0") // .build()) // .next(new TestCase()); } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImplTest.java index 5310e732d61..9294bd195a1 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/pvinverter/SimulatorPvInverterImplTest.java @@ -9,17 +9,14 @@ public class SimulatorPvInverterImplTest { - private static final String COMPONENT_ID = "pvInverter0"; - private static final String DATASOURCE_ID = "datasource0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorPvInverterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("datasource", new SimulatorDatasourceCsvDirectImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setDatasourceId(DATASOURCE_ID) // + .setId("pvInverter0") // + .setDatasourceId("datasource0") // .build()); // // .next(new TestCase()); // TODO requires DummyDatasource } diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/thermometer/SimulatorThermometerImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/thermometer/SimulatorThermometerImplTest.java index c07899add9f..a6620e2fd54 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/thermometer/SimulatorThermometerImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/thermometer/SimulatorThermometerImplTest.java @@ -8,13 +8,11 @@ public class SimulatorThermometerImplTest { - private static final String COMPONENT_ID = "thermometer0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorThermometerImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("thermometer0") // .setTemperature(20) // .build()) // .next(new TestCase()); diff --git a/io.openems.edge.simulator/test/io/openems/edge/simulator/timedata/SimulatorTimedataImplTest.java b/io.openems.edge.simulator/test/io/openems/edge/simulator/timedata/SimulatorTimedataImplTest.java index bd1d9358847..13d83a24d12 100644 --- a/io.openems.edge.simulator/test/io/openems/edge/simulator/timedata/SimulatorTimedataImplTest.java +++ b/io.openems.edge.simulator/test/io/openems/edge/simulator/timedata/SimulatorTimedataImplTest.java @@ -9,13 +9,11 @@ public class SimulatorTimedataImplTest { - private static final String COMPONENT_ID = "thermometer0"; - @Test public void test() throws OpenemsException, Exception { new ComponentTest(new SimulatorTimedataImpl()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("thermometer0") // .setFilename("") // .setFormat(CsvFormat.ENGLISH) // .build()) // diff --git a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImplTest.java b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImplTest.java index 6c4ccbc5b0d..92b45ac9baa 100644 --- a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImplTest.java +++ b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/gridmeter/SolarEdgeGridMeterImplTest.java @@ -1,27 +1,25 @@ package io.openems.edge.solaredge.gridmeter; +import static io.openems.edge.meter.api.MeterType.GRID; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.meter.api.MeterType; public class SolarEdgeGridMeterImplTest { - private static final String METER_ID = "meter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new SolarEdgeGridMeterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(METER_ID) // - .setModbusId(MODBUS_ID) // + .setId("meter0") // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setType(MeterType.GRID) // + .setType(GRID) // .build()) // ; } diff --git a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/pvinverter/SolarEdgePvInverterImplTest.java b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/pvinverter/SolarEdgePvInverterImplTest.java index c90b0a9e978..438399b1243 100644 --- a/io.openems.edge.solaredge/test/io/openems/edge/solaredge/pvinverter/SolarEdgePvInverterImplTest.java +++ b/io.openems.edge.solaredge/test/io/openems/edge/solaredge/pvinverter/SolarEdgePvInverterImplTest.java @@ -1,28 +1,26 @@ package io.openems.edge.solaredge.pvinverter; +import static io.openems.edge.pvinverter.sunspec.Phase.L1; + import org.junit.Test; import io.openems.edge.bridge.modbus.test.DummyModbusBridge; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyConfigurationAdmin; -import io.openems.edge.pvinverter.sunspec.Phase; public class SolarEdgePvInverterImplTest { - private static final String PV_INVERTER_ID = "pvInverter0"; - private static final String MODBUS_ID = "modbus0"; - @Test public void test() throws Exception { new ComponentTest(new SolarEdgePvInverterImpl()) // .addReference("cm", new DummyConfigurationAdmin()) // - .addReference("setModbus", new DummyModbusBridge(MODBUS_ID)) // + .addReference("setModbus", new DummyModbusBridge("modbus0")) // .activate(MyConfig.create() // - .setId(PV_INVERTER_ID) // + .setId("pvInverter0") // .setReadOnly(true) // - .setModbusId(MODBUS_ID) // + .setModbusId("modbus0") // .setModbusUnitId(1) // - .setPhase(Phase.L1) // + .setPhase(L1) // .build()) // ; } diff --git a/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java b/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java index 4c097c21c69..05890b4b34c 100644 --- a/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java +++ b/io.openems.edge.timedata.influxdb/test/io/openems/edge/timedata/influxdb/TimedataInfluxDbImplTest.java @@ -1,19 +1,18 @@ package io.openems.edge.timedata.influxdb; +import static io.openems.common.channel.PersistencePriority.MEDIUM; +import static io.openems.shared.influxdb.QueryLanguageConfig.INFLUX_QL; + import org.junit.Test; -import io.openems.common.channel.PersistencePriority; import io.openems.common.oem.DummyOpenemsEdgeOem; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyCycle; -import io.openems.shared.influxdb.QueryLanguageConfig; public class TimedataInfluxDbImplTest { - private static final String COMPONENT_ID = "influx0"; - @Test public void test() throws Exception { new ComponentTest(new TimedataInfluxDbImpl()) // @@ -21,8 +20,8 @@ public void test() throws Exception { .addReference("cycle", new DummyCycle(1000)) // .addReference("oem", new DummyOpenemsEdgeOem()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setQueryLanguage(QueryLanguageConfig.INFLUX_QL) // + .setId("influx0") // + .setQueryLanguage(INFLUX_QL) // .setUrl("http://localhost:8086") // .setOrg("-") // .setApiKey("username:password") // @@ -31,7 +30,7 @@ public void test() throws Exception { .setNoOfCycles(1) // .setMaxQueueSize(5000) // .setReadOnly(false) // - .setPersistencePriority(PersistencePriority.MEDIUM) + .setPersistencePriority(MEDIUM) // .build()) // .next(new TestCase()) // ; diff --git a/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/TimedataRrd4jImplTest.java b/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/TimedataRrd4jImplTest.java index 980994dfb48..2b0590abc1b 100644 --- a/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/TimedataRrd4jImplTest.java +++ b/io.openems.edge.timedata.rrd4j/test/io/openems/edge/timedata/rrd4j/TimedataRrd4jImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.timedata.rrd4j; +import static io.openems.common.channel.PersistencePriority.MEDIUM; import static org.junit.Assert.assertEquals; import java.io.IOException; @@ -15,15 +16,12 @@ import org.rrd4j.core.RrdDef; import org.rrd4j.core.RrdMemoryBackendFactory; -import io.openems.common.channel.PersistencePriority; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; public class TimedataRrd4jImplTest { - private static final String COMPONENT_ID = "rrd4j0"; - @Test public void test() throws Exception { final var componentManager = new DummyComponentManager(); @@ -31,8 +29,8 @@ public void test() throws Exception { .addReference("workerFactory", new DummyRecordWorkerFactory(componentManager)) // .addReference("readHandler", new Rrd4jReadHandler()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // - .setPersistencePriority(PersistencePriority.MEDIUM) // + .setId("rrd4j0") // + .setPersistencePriority(MEDIUM) // .build()) // .next(new TestCase()) // ; diff --git a/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImplTest.java b/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImplTest.java index eaef278769f..dd329cfe509 100644 --- a/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImplTest.java +++ b/io.openems.edge.timeofusetariff.awattar/test/io/openems/edge/timeofusetariff/awattar/TimeOfUseTariffAwattarImplTest.java @@ -1,5 +1,6 @@ package io.openems.edge.timeofusetariff.awattar; +import static io.openems.edge.timeofusetariff.awattar.Zone.GERMANY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; @@ -11,15 +12,13 @@ public class TimeOfUseTariffAwattarImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { var awattar = new TimeOfUseTariffAwattarImpl(); new ComponentTest(awattar) // .activate(MyConfig.create() // - .setId(CTRL_ID) // - .setZone(Zone.GERMANY) // + .setId("ctrl0") // + .setZone(GERMANY) // .build()) // ; } diff --git a/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImplTest.java b/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImplTest.java index 3516d5f8378..83c69c1e2eb 100644 --- a/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImplTest.java +++ b/io.openems.edge.timeofusetariff.corrently/test/io/openems/edge/timeofusetariff/corrently/TimeOfUseTariffCorrentlyImplTest.java @@ -12,14 +12,12 @@ public class TimeOfUseTariffCorrentlyImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { var corrently = new TimeOfUseTariffCorrentlyImpl(); new ComponentTest(corrently) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setZipcode("94469" /* Deggendorf, Germany */) // .build()) // ; diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java index 2c3098097d2..8bcea20e8ce 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java @@ -10,8 +10,6 @@ public class TouEntsoeTest { - private static final String COMPONENT_ID = "tou0"; - @Test public void test() throws Exception { var entsoe = new TouEntsoeImpl(); @@ -21,7 +19,7 @@ public void test() throws Exception { .addReference("meta", dummyMeta) // .addReference("oem", new DummyOpenemsEdgeOem()) // .activate(MyConfig.create() // - .setId(COMPONENT_ID) // + .setId("tou0") // .setSecurityToken("") // .setExchangerateAccesskey("") // .setBiddingZone(BiddingZone.GERMANY) // diff --git a/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImplTest.java b/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImplTest.java index 8bdb01e4e1a..76e1e7fda13 100644 --- a/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImplTest.java +++ b/io.openems.edge.timeofusetariff.groupe/test/io/openems/edge/timeofusetariff/groupe/TimeOfUseTariffGroupeImplTest.java @@ -1,19 +1,16 @@ package io.openems.edge.timeofusetariff.groupe; import static io.openems.edge.common.currency.Currency.CHF; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static io.openems.edge.timeofusetariff.groupe.TimeOfUseTariffGroupeImpl.parsePrices; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.oem.DummyOpenemsEdgeOem; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; @@ -21,7 +18,6 @@ public class TimeOfUseTariffGroupeImplTest { - private static final String CTRL_ID = "ctrl0"; private static final double GROUPE_E_EXCHANGE_RATE = 1; private static final String PRICE_RESULT_STRING = """ @@ -407,8 +403,7 @@ public class TimeOfUseTariffGroupeImplTest { @Test public void test() throws Exception { - final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); - final DummyComponentManager cm = new DummyComponentManager(clock); + final var clock = createDummyClock(); var groupe = new TimeOfUseTariffGroupeImpl(); var dummyMeta = new DummyMeta("foo0") // .withCurrency(CHF); @@ -416,9 +411,9 @@ public void test() throws Exception { .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // .addReference("meta", dummyMeta) // .addReference("oem", new DummyOpenemsEdgeOem()) // - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setExchangerateAccesskey("") // .build()) // ; diff --git a/io.openems.edge.timeofusetariff.hassfurt/test/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImplTest.java b/io.openems.edge.timeofusetariff.hassfurt/test/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImplTest.java index 2f363b27876..041b9e6ae0e 100644 --- a/io.openems.edge.timeofusetariff.hassfurt/test/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImplTest.java +++ b/io.openems.edge.timeofusetariff.hassfurt/test/io/openems/edge/timeofusetariff/hassfurt/TimeOfUseTariffHassfurtImplTest.java @@ -1,24 +1,20 @@ package io.openems.edge.timeofusetariff.hassfurt; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static io.openems.edge.timeofusetariff.hassfurt.TimeOfUseTariffHassfurtImpl.parsePrices; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; public class TimeOfUseTariffHassfurtImplTest { - private static final String CTRL_ID = "ctrl0"; private static final String STROM_FLEX_PRO_STRING = """ { "object": "list", @@ -192,14 +188,12 @@ public class TimeOfUseTariffHassfurtImplTest { @Test public void test() throws Exception { - final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); - final DummyComponentManager cm = new DummyComponentManager(clock); - var hassfurt = new TimeOfUseTariffHassfurtImpl(); - new ComponentTest(hassfurt) // + final var clock = createDummyClock(); + new ComponentTest(new TimeOfUseTariffHassfurtImpl()) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setTariffType(TariffType.STROM_FLEX) // .build()) // ; diff --git a/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImplTest.java b/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImplTest.java index 46c41dec126..4852a20c2d0 100644 --- a/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImplTest.java +++ b/io.openems.edge.timeofusetariff.rabotcharge/test/io/openems/edge/timeofusetariff/rabotcharge/TimeOfUseTariffRabotChargeImplTest.java @@ -1,35 +1,28 @@ package io.openems.edge.timeofusetariff.rabotcharge; +import static io.openems.edge.common.test.TestUtils.createDummyClock; import static io.openems.edge.timeofusetariff.rabotcharge.TimeOfUseTariffRabotChargeImpl.parsePrices; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; -import java.time.Instant; -import java.time.ZoneOffset; - import org.junit.Test; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.test.TimeLeapClock; import io.openems.edge.bridge.http.dummy.DummyBridgeHttpFactory; import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; public class TimeOfUseTariffRabotChargeImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { - final TimeLeapClock clock = new TimeLeapClock(Instant.parse("2020-01-01T01:00:00.00Z"), ZoneOffset.UTC); - final DummyComponentManager cm = new DummyComponentManager(clock); - var rabotCharge = new TimeOfUseTariffRabotChargeImpl(); - new ComponentTest(rabotCharge) // + final var clock = createDummyClock(); + new ComponentTest(new TimeOfUseTariffRabotChargeImpl()) // .addReference("httpBridgeFactory", DummyBridgeHttpFactory.ofDummyBridge()) // - .addReference("componentManager", cm) // + .addReference("componentManager", new DummyComponentManager(clock)) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setAccessToken("foo-bar") // .build()) // ; diff --git a/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TimeOfUseTariffTibberImplTest.java b/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TimeOfUseTariffTibberImplTest.java index 59f2b6a3558..7190b8a7795 100644 --- a/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TimeOfUseTariffTibberImplTest.java +++ b/io.openems.edge.timeofusetariff.tibber/test/io/openems/edge/timeofusetariff/tibber/TimeOfUseTariffTibberImplTest.java @@ -6,14 +6,11 @@ public class TimeOfUseTariffTibberImplTest { - private static final String CTRL_ID = "ctrl0"; - @Test public void test() throws Exception { - var tibber = new TimeOfUseTariffTibberImpl(); - new ComponentTest(tibber) // + new ComponentTest(new TimeOfUseTariffTibberImpl()) // .activate(MyConfig.create() // - .setId(CTRL_ID) // + .setId("ctrl0") // .setAccessToken("foo-bar") // .setFilter("") // .build()) // diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..8196d1ddf2e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "openems", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/ui/.project b/ui/.project new file mode 100644 index 00000000000..4f185983a47 --- /dev/null +++ b/ui/.project @@ -0,0 +1,11 @@ + + + fems-ui + + + + + + + + diff --git a/ui/README.md b/ui/README.md index 96ef2800acb..e5a4c386f2d 100644 --- a/ui/README.md +++ b/ui/README.md @@ -65,7 +65,9 @@ This project was generated with [angular-cli](https://github.com/angular/angular > [!IMPORTANT] > Crucial information necessary for users to succeed. Only provide /resources/logo-dark.png and logo.png * Move the files from res(except values and xml) to ```/android/app/src/$theme/``` (```/main``` acts as default) -* Build apps: `gradlew bundle{$theme}Release` +* Build apps (execute in order): + - `NODE_ENV="{$theme}" ./node_modules/.bin/ionic cap build android -c "$theme,$theme-backend-deploy-app" --no-open;` + - `THEME="{$theme}" gradlew bundleThemeRelease` Important (if not generated, can be copied and adjusted from existing theme): - `ui\android\app\src\{$theme}\res\xml\file_paths.xml` @@ -77,6 +79,7 @@ Use `gradlew install{$theme}Release to install it on any device` - Available Tasks: `gradlew tasks` - list available devices + emulators: `$npx native-run android --list --json` +- use Android Studio for Debugging: `$ionic cap open android` ## i18n - internationalization diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index a6324b0d68b..9aa984ea31d 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -16,9 +16,9 @@ import { OverviewComponent as GridOptimizedChargeChartOverviewComponent } from " import { OverviewComponent as TimeOfUseTariffOverviewComponent } from "./edge/history/Controller/Ess/TimeOfUseTariff/overview/overview"; import { DetailsOverviewComponent as DigitalOutputDetailsOverviewComponent } from "./edge/history/Controller/Io/DigitalOutput/details/details.overview"; import { OverviewComponent as DigitalOutputChartOverviewComponent } from "./edge/history/Controller/Io/DigitalOutput/overview/overview"; +import { OverviewComponent as HeatingelementChartOverviewComponent } from "./edge/history/Controller/Io/heatingelement/overview/overview"; import { OverviewComponent as ModbusTcpApiOverviewComponent } from "./edge/history/Controller/ModbusTcpApi/overview/overview"; import { DelayedSellToGridChartOverviewComponent } from "./edge/history/delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component"; -import { HeatingelementChartOverviewComponent } from "./edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component"; import { HeatPumpChartOverviewComponent } from "./edge/history/heatpump/heatpumpchartoverview/heatpumpchartoverview.component"; import { HistoryComponent as EdgeHistoryComponent } from "./edge/history/history.component"; import { HistoryDataService } from "./edge/history/historydataservice"; diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 80c7aa7d2a0..9b1e3b8fc02 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -1,4 +1,46 @@ + + + + + + + + Menu.menu + + + + + + + + + +

    {{ user.name }}

    + +

    {{ user.id }}

    +
    + + + + + + + + Menu.overview + + + + + + + + @@ -35,47 +77,7 @@

    Index.connectionFailed

    - - - - - - - Menu.menu - - - - - - - - -

    {{ user.name }}

    - -

    {{ user.id }}

    -
    -
    -
    - - - - - - Menu.overview - - - - - -
    -
    -
    diff --git a/ui/src/app/changelog/view/view.html b/ui/src/app/changelog/view/view.html index 2746d025db1..10f23e85d60 100644 --- a/ui/src/app/changelog/view/view.html +++ b/ui/src/app/changelog/view/view.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/history/Controller/Io/Io.module.ts b/ui/src/app/edge/history/Controller/Io/Io.module.ts index caeda2b8417..eae8ce33c7d 100644 --- a/ui/src/app/edge/history/Controller/Io/Io.module.ts +++ b/ui/src/app/edge/history/Controller/Io/Io.module.ts @@ -1,12 +1,15 @@ import { NgModule } from "@angular/core"; import { DigitalOutput } from "./DigitalOutput/digitalOutput.module"; +import { HeatingElement } from "./heatingelement/heatingelement.module"; @NgModule({ imports: [ DigitalOutput, + HeatingElement, ], exports: [ DigitalOutput, + HeatingElement, ], }) export class ControllerIo { } diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/chart/chart.ts b/ui/src/app/edge/history/Controller/Io/heatingelement/chart/chart.ts new file mode 100644 index 00000000000..4af9f3d9c88 --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/chart/chart.ts @@ -0,0 +1,82 @@ +// @ts-strict-ignore +import { Component } from "@angular/core"; +import { AbstractHistoryChart } from "src/app/shared/components/chart/abstracthistorychart"; +import { ChartConstants } from "src/app/shared/components/chart/chart.constants"; +import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; +import { ChartAxis, HistoryUtils, Utils, YAxisType } from "src/app/shared/service/utils"; +import { ChannelAddress, EdgeConfig } from "src/app/shared/shared"; + +@Component({ + selector: "controller-io-heatingelement-chart", + templateUrl: "../../../../../../shared/components/chart/abstracthistorychart.html", +}) +export class ChartComponent extends AbstractHistoryChart { + public static getChartData(component: EdgeConfig.Component, phaseColors: string[], chartType: "line" | "bar"): HistoryUtils.ChartData { + + const input: HistoryUtils.InputChannel[] = [ + { name: component.id, powerChannel: new ChannelAddress(component.id, "Level") }, + ]; + + for (const level of [1, 2, 3]) { + input.push({ + name: component.id + level, + powerChannel: new ChannelAddress(component.id, "Level"), + energyChannel: new ChannelAddress(component.id, "Level" + level + "CumulatedTime"), + }); + } + + return { + input: input, + output: (data: HistoryUtils.ChannelData) => { + + const output: HistoryUtils.DisplayValue[] = []; + + if (chartType === "line") { + output.push({ + name: "Level", + converter: () => data[component.id].map(val => Utils.multiplySafely(val, 1000)), + color: ChartConstants.Colors.RED, + stack: 0, + }); + } + + if (chartType === "bar") { + for (const level of [1, 2, 3]) { + output.push({ + name: "Level " + level, + nameSuffix: (energyQueryResponse: QueryHistoricTimeseriesEnergyResponse) => + energyQueryResponse?.result.data[component.id + "/Level" + level + "CumulatedTime"] ?? null, + converter: () => data[component.id + level] + // TODO add logic to not have to adjust non power data manually + .map(val => Utils.multiplySafely(val, 1000)), + color: phaseColors[level % phaseColors.length], + stack: 0, + }); + } + } + + return output; + }, + tooltip: { + formatNumber: ChartConstants.NumberFormat.NO_DECIMALS, + }, + yAxes: [ + chartType === "line" + ? { + unit: YAxisType.LEVEL, + position: "left", + yAxisId: ChartAxis.LEFT, + } + : { + unit: YAxisType.TIME, + position: "left", + yAxisId: ChartAxis.LEFT, + }, + ], + }; + } + + protected override getChartData(): HistoryUtils.ChartData { + return ChartComponent.getChartData(this.component, AbstractHistoryChart.phaseColors, this.chartType); + } +} diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.html b/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.html new file mode 100644 index 00000000000..923a442cb04 --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.ts b/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.ts new file mode 100644 index 00000000000..677d32f0737 --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/flat/flat.ts @@ -0,0 +1,10 @@ +import { Component } from "@angular/core"; +import { AbstractFlatWidget } from "src/app/shared/components/flat/abstract-flat-widget"; + +@Component({ + selector: "controller-io-heatingelement-widget", + templateUrl: "./flat.html", +}) +export class FlatComponent extends AbstractFlatWidget { + protected FORMAT_SECONDS_TO_DURATION = this.Converter.FORMAT_SECONDS_TO_DURATION(this.translate.currentLang); +} diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/heatingelement.module.ts b/ui/src/app/edge/history/Controller/Io/heatingelement/heatingelement.module.ts new file mode 100644 index 00000000000..99310facb87 --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/heatingelement.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from "@angular/core"; +import { BrowserModule } from "@angular/platform-browser"; +import { SharedModule } from "src/app/shared/shared.module"; +import { ChartComponent } from "./chart/chart"; +import { FlatComponent } from "./flat/flat"; +import { OverviewComponent } from "./overview/overview"; + +@NgModule({ + imports: [ + BrowserModule, + SharedModule, + ], + declarations: [ + ChartComponent, + FlatComponent, + OverviewComponent, + ], + exports: [ + ChartComponent, + FlatComponent, + OverviewComponent, + ], +}) +export class HeatingElement { } diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.html b/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.html new file mode 100644 index 00000000000..d834e111f28 --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.html @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.ts b/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.ts new file mode 100644 index 00000000000..2e52219327a --- /dev/null +++ b/ui/src/app/edge/history/Controller/Io/heatingelement/overview/overview.ts @@ -0,0 +1,8 @@ +import { Component } from "@angular/core"; +import { AbstractHistoryChartOverview } from "src/app/shared/components/chart/abstractHistoryChartOverview"; + +@Component({ + selector: "controller-io-heatingelement-overview", + templateUrl: "./overview.html", +}) +export class OverviewComponent extends AbstractHistoryChartOverview { } diff --git a/ui/src/app/edge/history/common/consumption/chart/chart.ts b/ui/src/app/edge/history/common/consumption/chart/chart.ts index bc5474bde75..001e004b59e 100644 --- a/ui/src/app/edge/history/common/consumption/chart/chart.ts +++ b/ui/src/app/edge/history/common/consumption/chart/chart.ts @@ -26,9 +26,7 @@ export class ChartComponent extends AbstractHistoryChart { component.factoryId == "Evcs.Cluster.PeakShaving" || component.factoryId == "Evcs.Cluster.SelfConsumption")); - const consumptionMeters: EdgeConfig.Component[] = config.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") - .filter(component => component.isEnabled && config.isTypeConsumptionMetered(component)); - + // TODO Since 2024.11.0 EVCS implements EletricityMeter; use DeprecatedEvcs as filter evcsComponents.forEach(component => { inputChannel.push({ name: component.id + "/ChargePower", @@ -37,6 +35,10 @@ export class ChartComponent extends AbstractHistoryChart { }); }); + const consumptionMeters: EdgeConfig.Component[] = config.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") + .filter(component => component.isEnabled && config.isTypeConsumptionMetered(component) + && !config.getNatureIdsByFactoryId(component.factoryId).includes("io.openems.edge.evcs.api.Evcs")); + consumptionMeters.forEach(meter => { inputChannel.push({ name: meter.id + "/ActivePower", diff --git a/ui/src/app/edge/history/common/consumption/flat/flat.ts b/ui/src/app/edge/history/common/consumption/flat/flat.ts index 149521db911..a862b6ad406 100644 --- a/ui/src/app/edge/history/common/consumption/flat/flat.ts +++ b/ui/src/app/edge/history/common/consumption/flat/flat.ts @@ -14,27 +14,24 @@ export class FlatComponent extends AbstractFlatWidget { protected totalOtherEnergy: number; protected override getChannelAddresses(): ChannelAddress[] { + const channels: ChannelAddress[] = [new ChannelAddress("_sum", "ConsumptionActiveEnergy")]; this.evcsComponents = this.config?.getComponentsImplementingNature("io.openems.edge.evcs.api.Evcs") .filter(component => !(component.factoryId === "Evcs.Cluster.SelfConsumption") && !(component.factoryId === "Evcs.Cluster.PeakShaving") && !component.isEnabled === false); - - this.consumptionMeterComponents = this.config?.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") - .filter(component => component.isEnabled && this.config.isTypeConsumptionMetered(component)); - - const channels: ChannelAddress[] = [new ChannelAddress("_sum", "ConsumptionActiveEnergy")]; - this.evcsComponents.forEach((component) => { channels.push(new ChannelAddress(component.id, "ActiveConsumptionEnergy")); }); + this.consumptionMeterComponents = this.config?.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") + .filter(component => component.isEnabled && this.config.isTypeConsumptionMetered(component) + && !this.config.getNatureIdsByFactoryId(component.factoryId).includes("io.openems.edge.evcs.api.Evcs")); this.consumptionMeterComponents.forEach((component) => { channels.push(new ChannelAddress(component.id, "ActiveProductionEnergy")); }); - return channels; } diff --git a/ui/src/app/edge/history/common/consumption/overview/overview.ts b/ui/src/app/edge/history/common/consumption/overview/overview.ts index cf75130de35..7ba779ea15c 100644 --- a/ui/src/app/edge/history/common/consumption/overview/overview.ts +++ b/ui/src/app/edge/history/common/consumption/overview/overview.ts @@ -34,7 +34,8 @@ export class OverviewComponent extends AbstractHistoryChartOverview { !component.isEnabled === false); this.consumptionMeterComponents = this.config?.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") - .filter(component => component.isEnabled && this.config.isTypeConsumptionMetered(component)); + .filter(component => component.isEnabled && this.config.isTypeConsumptionMetered(component) + && !this.config.getNatureIdsByFactoryId(component.factoryId).includes("io.openems.edge.evcs.api.Evcs")); const sum: EdgeConfig.Component = this.config.getComponent("_sum"); sum.alias = this.translate.instant("General.TOTAL"); diff --git a/ui/src/app/edge/history/common/energy/chart/channels.spec.ts b/ui/src/app/edge/history/common/energy/chart/channels.spec.ts index fe2dbeb853b..c5c12a0f2fa 100644 --- a/ui/src/app/edge/history/common/energy/chart/channels.spec.ts +++ b/ui/src/app/edge/history/common/energy/chart/channels.spec.ts @@ -8,7 +8,7 @@ import { QueryHistoricTimeseriesEnergyResponse } from "src/app/shared/jsonrpc/re export namespace History { - export const LINE_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min: number, max: number; } | null, ticks: { stepSize: number }; }; }): OeChartTester.Dataset.Option => ({ + export const LINE_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min: number, max: number; } | null, ticks?: { stepSize: number }; }; }): OeChartTester.Dataset.Option => ({ type: "option", options: { "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": {}, "line": {} }, @@ -30,11 +30,14 @@ export namespace History { }, "scales": { "x": { "stacked": true, "offset": false, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { - ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kW", "display": true, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + "stacked": false, + ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, + "title": { "text": "kW", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, "color": "", "padding": 5, "maxTicksLimit": ChartConstants.NUMBER_OF_Y_AXIS_TICKS }, }, "right": { - ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "max": 100, "min": 0, "type": "linear", "title": { "text": "%", "display": true, "font": { "size": 11 }, "padding": 5 }, "position": "right", "grid": { "display": false }, + "stacked": false, + ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "type": "linear", "title": { "text": "%", "display": false, "font": { "size": 11 }, "padding": 5 }, "position": "right", "grid": { "display": false }, "ticks": { ...options["right"]?.ticks, "color": "", @@ -67,7 +70,8 @@ export namespace History { }, "scales": { "x": { "stacked": true, "offset": true, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { - ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": true, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + "stacked": true, + ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, "color": "", diff --git a/ui/src/app/edge/history/common/energy/chart/chart.spec.ts b/ui/src/app/edge/history/common/energy/chart/chart.spec.ts index cd1695a6c81..ecf8e88a444 100644 --- a/ui/src/app/edge/history/common/energy/chart/chart.spec.ts +++ b/ui/src/app/edge/history/common/energy/chart/chart.spec.ts @@ -34,7 +34,7 @@ describe("History EnergyMonitor", () => { ], labels: LABELS(History.DAY.dataChannelWithValues.result.timestamps), options: History.LINE_CHART_OPTIONS("hour", "line", { - ["right"]: { ticks: { stepSize: 20 }, scale: null }, + ["right"]: { scale: null }, }), }, }); @@ -55,7 +55,7 @@ describe("History EnergyMonitor", () => { DATA("Ladezustand", History.WEEK.dataChannelWithValues.result.data["_sum/EssSoc"]), ], labels: LABELS(History.WEEK.dataChannelWithValues.result.timestamps), - options: History.LINE_CHART_OPTIONS("day", "line", { ["right"]: { ticks: { stepSize: 20 }, scale: null } }), + options: History.LINE_CHART_OPTIONS("day", "line", { ["right"]: { scale: null } }), }, }); } diff --git a/ui/src/app/edge/history/common/grid/chart/chart.spec.ts b/ui/src/app/edge/history/common/grid/chart/chart.spec.ts index 64c0455cdf2..a52ed73cafc 100644 --- a/ui/src/app/edge/history/common/grid/chart/chart.spec.ts +++ b/ui/src/app/edge/history/common/grid/chart/chart.spec.ts @@ -51,9 +51,8 @@ describe("History Grid", () => { DATA("Bezug: 0,9 kWh", [null, null, null, 0.031, 0.018, 0, 0.02, 0.016, 0.015, 0.014, 0.009, 0.02, 0.025, 0.025, 0.025, 0.021, 0.012, 0.009, 0.01, 0.011, 0.005, 0.003, 0, 0.015, 0.018, 0.023, 0, 0, 0, 0.002, 0.002, 0.003, 0.015, 0.008, 0.022, 0.027, 0.016, 0.003, 0.002, 0, 0.028, 0.027, 0.017, 0.001, 0, 0, 0, null, null, null, null, 0.011, 0.01, 0.004, 0.006, 0.007, 0.018, 0.008, 0.012, 0.009, 0.004, 0.013, 0.015, 0.012, 0, 0, 0, 0.002, 0, 0.005, 0.001, 0.03, 0.062, 0, 0, 0, 0, 0, 0, 0, 0, 0.015, 0.005, 0.004, 0.007, 0, 0, 0, 0, 0, 0, 0, 0.005, 0, 0, 0, 0, 0, 0, 0.021, 0, 0, 0, 0, 0, 0.003, 0, 0.004, 0, 0, 0.032, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]), ], labels: LABELS(History.DAY.dataChannelWithValues.result.timestamps), - options: OeTester.ChartOptions.MULTI_LINE_OPTIONS("hour", "line", { - ["right"]: { scale: { max: 1, min: 0 }, ticks: { stepSize: 1 } }, - }), + options: OeTester.ChartOptions.MULTI_LINE_OPTIONS("hour", "line", {}, + ), }, }, false); } @@ -67,9 +66,7 @@ describe("History Grid", () => { DATA("Bezug: 2,4 kWh", [0, 0.011916666666666666, 0.01633333333333333, 0.00609090909090909, 0.015333333333333334, 0.011666666666666665, 0.0024166666666666664, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.02425, 0.004416666666666667, 0.0035833333333333333, 0, 0, 0, 0.04441666666666667, 0, 0.013111111111111112, 0.001, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0011666666666666668, 0, 0, 0, 0.0015833333333333333, 0.013333333333333334, 0.020416666666666666, 0.01125, 0.019727272727272725, 0.012444444444444445, 0.009583333333333334, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.007666666666666667, 0, 0.0023333333333333335, 0.0125, 0.01609090909090909, 0.02016666666666667, 0.014083333333333333, 0.006363636363636363, 0.01955555555555556, 0.04841666666666666, 0.011166666666666667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.014222222222222221, 0.00225, 0, 0.0036666666666666666, 0.032916666666666664, 0.014666666666666666, 0.0135, 0.017363636363636362, 0.013333333333333334, 0.022083333333333333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0009166666666666666, 0, 0.0021666666666666666, 0, 0, 0, 0.0005, 0.04841666666666666, 0, 0.005555555555555556, 0.02716666666666667, 0.017333333333333333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0023333333333333335, 0.008333333333333333, 0.003, 0.015916666666666666, 0.00325, 0, 0.004333333333333333, 0.001, 0, 0, 0.019545454545454546, 0.0017777777777777776, 0.006416666666666667, 0.017666666666666667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0058, 0.005625, 0, 0]), ], labels: LABELS(History.WEEK.dataChannelWithValues.result.timestamps), - options: OeTester.ChartOptions.MULTI_LINE_OPTIONS("day", "line", { - ["right"]: { scale: { max: 1, min: 0 }, ticks: { stepSize: 1 } }, - }), + options: OeTester.ChartOptions.MULTI_LINE_OPTIONS("day", "line", {}), }, }, false); } @@ -83,9 +80,7 @@ describe("History Grid", () => { DATA("Bezug: 773 kWh", [16, 6, 3, 3, 5, 48, 4, null, 5, 26, 17, 62, 8, 66, 13, 21, 4, 3, 18, 27, 29, null, 118, 85, 2, null, 72, 28, 84, null]), ], labels: LABELS(History.MONTH.energyPerPeriodChannelWithValues.result.timestamps), - options: OeTester.ChartOptions.MULTI_BAR_OPTIONS("day", "bar", { - ["right"]: { scale: {}, ticks: { stepSize: 1 } }, - }), + options: OeTester.ChartOptions.MULTI_BAR_OPTIONS("day", "bar", {}), }, }, false); @@ -100,9 +95,7 @@ describe("History Grid", () => { DATA("Bezug: 23.209 kWh", [9829, 4812, 2915, 2036, 2712, 773, 94, null, null, null, null, null]), ], labels: LABELS(History.YEAR.energyPerPeriodChannelWithValues.result.timestamps), - options: OeTester.ChartOptions.MULTI_BAR_OPTIONS("month", "bar", { - ["right"]: { scale: {}, ticks: { stepSize: 1 } }, - }), + options: OeTester.ChartOptions.MULTI_BAR_OPTIONS("month", "bar", {}), }, }, false); } diff --git a/ui/src/app/edge/history/common/production/details/chart/productionMeter.spec.ts b/ui/src/app/edge/history/common/production/details/chart/productionMeter.spec.ts index d3cc8490cc1..6a47313a6ca 100644 --- a/ui/src/app/edge/history/common/production/details/chart/productionMeter.spec.ts +++ b/ui/src/app/edge/history/common/production/details/chart/productionMeter.spec.ts @@ -52,13 +52,6 @@ describe("History Production Details - productionMeters", () => { }); export function expectView(config: EdgeConfig, testContext: TestContext & { route: ActivatedRoute }, chartType: "line" | "bar", channels: OeTester.Types.Channels, view: OeChartTester.View): void { - sessionStorage.setItem("mapping to int", JSON.stringify(History.MONTH.energyPerPeriodChannelWithValues.result.data["meter0/ActiveProductionEnergy"].map(el => el != null ? Math.round(el) : null))); - sessionStorage.setItem("phase", JSON.stringify(OeChartTester - .apply(ProductionMeterChartDetailsComponent - .getChartData( - DummyConfig.convertDummyEdgeConfigToRealEdgeConfig(config), testContext.route, - testContext.translate), chartType, channels, testContext, config))); - expect(removeFunctions(OeChartTester .apply(ProductionMeterChartDetailsComponent .getChartData( diff --git a/ui/src/app/edge/history/heatingelement/chart.component.ts b/ui/src/app/edge/history/heatingelement/chart.component.ts deleted file mode 100644 index 49dd46bf02c..00000000000 --- a/ui/src/app/edge/history/heatingelement/chart.component.ts +++ /dev/null @@ -1,126 +0,0 @@ -// @ts-strict-ignore -import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { TranslateService } from "@ngx-translate/core"; - -import type { ChartOptions } from "chart.js"; -import { DefaultTypes } from "src/app/shared/service/defaulttypes"; -import { ChartAxis, YAxisType } from "src/app/shared/service/utils"; - -import { QueryHistoricTimeseriesDataResponse } from "../../../shared/jsonrpc/response/queryHistoricTimeseriesDataResponse"; -import { ChannelAddress, Edge, EdgeConfig, Service } from "../../../shared/shared"; -import { AbstractHistoryChart } from "../abstracthistorychart"; - -@Component({ - selector: "heatingelementChart", - templateUrl: "../abstracthistorychart.html", -}) -export class HeatingelementChartComponent extends AbstractHistoryChart implements OnInit, OnChanges, OnDestroy { - - @Input({ required: true }) public period!: DefaultTypes.HistoryPeriod; - @Input({ required: true }) public component!: EdgeConfig.Component; - - constructor( - protected override service: Service, - protected override translate: TranslateService, - private route: ActivatedRoute, - ) { - super("heatingelement-chart", service, translate); - } - - ngOnChanges() { - this.updateChart(); - } - - ngOnInit() { - this.startSpinner(); - this.setLabel(); - } - - ngOnDestroy() { - this.unsubscribeChartRefresh(); - } - - public getChartHeight(): number { - return window.innerHeight / 1.3; - } - - protected updateChart() { - this.autoSubscribeChartRefresh(); - this.startSpinner(); - this.colors = []; - this.loading = true; - this.queryHistoricTimeseriesData(this.period.from, this.period.to).then(response => { - this.service.getCurrentEdge().then(() => { - const result = (response as QueryHistoricTimeseriesDataResponse).result; - // convert labels - const labels: Date[] = []; - for (const timestamp of result.timestamps) { - labels.push(new Date(timestamp)); - } - this.labels = labels; - - // convert datasets - const datasets = []; - const level = this.component.id + "/Level"; - - if (level in result.data) { - const levelData = result.data[level].map(value => { - if (value == null) { - return null; - } else { - return value; - } - }); - datasets.push({ - label: "Level", - data: levelData, - }); - this.colors.push({ - backgroundColor: "rgba(200,0,0,0.05)", - borderColor: "rgba(200,0,0,1)", - }); - } - this.datasets = datasets; - this.loading = false; - this.stopSpinner(); - - }).catch(reason => { - console.error(reason); // TODO error message - this.initializeChart(); - return; - }); - - }).catch(reason => { - console.error(reason); // TODO error message - this.initializeChart(); - return; - }).finally(async () => { - this.formatNumber = "1.0-1"; - this.unit = YAxisType.NONE; - await this.setOptions(this.options); - this.applyControllerSpecificOptions(this.options); - }); - } - - protected getChannelAddresses(edge: Edge, config: EdgeConfig): Promise { - return new Promise((resolve) => { - const levels = new ChannelAddress(this.component.id, "Level"); - const channeladdresses = [levels]; - resolve(channeladdresses); - }); - } - - protected applyControllerSpecificOptions(options: ChartOptions) { - options.scales[ChartAxis.LEFT]["title"].text = "Level"; - options.scales[ChartAxis.LEFT]["beginAtZero"] = true; - options.scales[ChartAxis.LEFT].max = 3; - options.scales[ChartAxis.LEFT].ticks["stepSize"] = 1; - this.options = options; - } - - protected setLabel() { - this.options = this.createDefaultChartOptions(); - } - -} diff --git a/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.html b/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.html deleted file mode 100644 index 9b116e552fb..00000000000 --- a/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - {{ component.alias }} - - - - - - - - - - - - - - - - - - - diff --git a/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.ts b/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.ts deleted file mode 100644 index f615818d7c3..00000000000 --- a/ui/src/app/edge/history/heatingelement/heatingelementchartoverview/heatingelementchartoverview.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { Edge, EdgeConfig, Service } from "../../../../shared/shared"; - -@Component({ - selector: HeatingelementChartOverviewComponent.SELECTOR, - templateUrl: "./heatingelementchartoverview.component.html", -}) -export class HeatingelementChartOverviewComponent implements OnInit { - - private static readonly SELECTOR = "heatingelement-chart-overview"; - public edge: Edge | null = null; - public component: EdgeConfig.Component | null = null; - - constructor( - public service: Service, - private route: ActivatedRoute, - ) { } - - ngOnInit() { - this.service.getCurrentEdge().then(edge => { - this.service.getConfig().then(config => { - this.component = config.getComponent(this.route.snapshot.params.componentId); - this.service.getConfig().then(config => { - this.edge = edge; - this.component = config.getComponent(this.route.snapshot.params.componentId); - }); - }); - }); - } -} diff --git a/ui/src/app/edge/history/heatingelement/widget.component.html b/ui/src/app/edge/history/heatingelement/widget.component.html deleted file mode 100644 index 49b50bff319..00000000000 --- a/ui/src/app/edge/history/heatingelement/widget.component.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - {{ component.alias }} - - - - -
  • + -   {{ displayValue }} + {{displayName ? displayName + ": " + displayValue: '  ' + + displayValue}}
    - {{ name }} + {{ displayName }} + {{ displayValue }}
    - - - - - - - - - - - - -
    - Edge.History.activeDuration Level 1 - - {{ activeTimeOverPeriodLevel1 | formatSecondsToDuration }} -
    - Edge.History.activeDuration Level 2 - - {{ activeTimeOverPeriodLevel2 | formatSecondsToDuration }} -
    - Edge.History.activeDuration Level 3 - {{ activeTimeOverPeriodLevel3 | formatSecondsToDuration }} -
    - -
    -
    - diff --git a/ui/src/app/edge/history/heatingelement/widget.component.ts b/ui/src/app/edge/history/heatingelement/widget.component.ts deleted file mode 100644 index 262c025d23c..00000000000 --- a/ui/src/app/edge/history/heatingelement/widget.component.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { QueryHistoricTimeseriesDataResponse } from "src/app/shared/jsonrpc/response/queryHistoricTimeseriesDataResponse"; -import { DefaultTypes } from "src/app/shared/service/defaulttypes"; - -import { ChannelAddress, Edge, EdgeConfig, Service } from "../../../shared/shared"; -import { AbstractHistoryWidget } from "../abstracthistorywidget"; - -@Component({ - selector: HeatingelementWidgetComponent.SELECTOR, - templateUrl: "./widget.component.html", -}) -export class HeatingelementWidgetComponent extends AbstractHistoryWidget implements OnInit, OnChanges, OnDestroy { - - private static readonly SELECTOR = "heatingelementWidget"; - @Input({ required: true }) public period!: DefaultTypes.HistoryPeriod; - @Input({ required: true }) public componentId!: string; - - - public component: EdgeConfig.Component | null = null; - - public activeTimeOverPeriodLevel1: number | null = null; - public activeTimeOverPeriodLevel2: number | null = null; - public activeTimeOverPeriodLevel3: number | null = null; - - public edge: Edge | null = null; - - constructor( - public override service: Service, - private route: ActivatedRoute, - ) { - super(service); - } - - ngOnInit() { - this.service.getCurrentEdge().then(edge => { - this.edge = edge; - this.service.getConfig().then(config => { - this.component = config.getComponent(this.componentId); - }); - }); - } - - ngOnDestroy() { - this.unsubscribeWidgetRefresh(); - } - - ngOnChanges() { - this.updateValues(); - } - - public getCumulativeValue(channeladdress: string, response: QueryHistoricTimeseriesDataResponse) { - const array = response.result.data[channeladdress]; - const firstValue = array.find(el => el != null) ?? 0; - const lastValue = array.slice().reverse().find(el => el != null) ?? 0; - return lastValue - firstValue; - } - - protected updateValues() { - this.queryHistoricTimeseriesData(this.service.historyPeriod.value.from, this.service.historyPeriod.value.to).then(response => { - this.activeTimeOverPeriodLevel1 = this.getCumulativeValue(this.componentId + "/Level1CumulatedTime", response); - this.activeTimeOverPeriodLevel2 = this.getCumulativeValue(this.componentId + "/Level2CumulatedTime", response); - this.activeTimeOverPeriodLevel3 = this.getCumulativeValue(this.componentId + "/Level3CumulatedTime", response); - }); - } - - protected getChannelAddresses(edge: Edge, config: EdgeConfig): Promise { - return new Promise((resolve) => { - const channeladdresses = [ - new ChannelAddress(this.componentId, "Level1CumulatedTime"), - new ChannelAddress(this.componentId, "Level2CumulatedTime"), - new ChannelAddress(this.componentId, "Level3CumulatedTime"), - ]; - resolve(channeladdresses); - }); - } -} diff --git a/ui/src/app/edge/history/history.component.html b/ui/src/app/edge/history/history.component.html index 0b7e40f54f2..45655fbc05e 100644 --- a/ui/src/app/edge/history/history.component.html +++ b/ui/src/app/edge/history/history.component.html @@ -1,4 +1,3 @@ -
    @@ -85,8 +84,8 @@ - - + + diff --git a/ui/src/app/edge/history/history.component.ts b/ui/src/app/edge/history/history.component.ts index e2349530d0d..1e872a6bca5 100644 --- a/ui/src/app/edge/history/history.component.ts +++ b/ui/src/app/edge/history/history.component.ts @@ -1,9 +1,8 @@ // @ts-strict-ignore -import { Component, OnInit, ViewChild } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { AppService } from "src/app/app.service"; -import { HeaderComponent } from "src/app/shared/components/header/header.component"; import { JsonrpcResponseError } from "src/app/shared/jsonrpc/base"; import { Edge, EdgeConfig, EdgePermission, Service, Widgets } from "src/app/shared/shared"; import { environment } from "src/environments"; @@ -14,8 +13,6 @@ import { environment } from "src/environments"; }) export class HistoryComponent implements OnInit { - @ViewChild(HeaderComponent, { static: false }) public HeaderComponent: HeaderComponent; - // is a Timedata service available, i.e. can historic data be queried. public isTimedataAvailable: boolean = true; @@ -65,12 +62,6 @@ export class HistoryComponent implements OnInit { }); } - // checks arrows when ChartPage is closed - // double viewchild is used to prevent undefined state of PickDateComponent - ionViewDidEnter() { - this.HeaderComponent.PickDateComponent.checkArrowAutomaticForwarding(); - } - updateOnWindowResize() { const ref = /* fix proportions */ Math.min(window.innerHeight - 150, /* handle grid breakpoints */(window.innerWidth < 768 ? window.innerWidth - 150 : window.innerWidth - 400)); diff --git a/ui/src/app/edge/history/history.module.ts b/ui/src/app/edge/history/history.module.ts index 9d45b5ba2df..0c0fd1d6c06 100644 --- a/ui/src/app/edge/history/history.module.ts +++ b/ui/src/app/edge/history/history.module.ts @@ -1,6 +1,5 @@ import { NgModule } from "@angular/core"; import { HistoryDataErrorModule } from "src/app/shared/components/history-data-error/history-data-error.module"; - import { SharedModule } from "../../shared/shared.module"; import { ChpSocChartComponent } from "./chpsoc/chart.component"; import { ChpSocWidgetComponent } from "./chpsoc/widget.component"; @@ -9,9 +8,6 @@ import { Controller } from "./Controller/controller.module"; import { DelayedSellToGridChartComponent } from "./delayedselltogrid/chart.component"; import { DelayedSellToGridChartOverviewComponent } from "./delayedselltogrid/symmetricpeakshavingchartoverview/delayedselltogridchartoverview.component"; import { DelayedSellToGridWidgetComponent } from "./delayedselltogrid/widget.component"; -import { HeatingelementChartComponent } from "./heatingelement/chart.component"; -import { HeatingelementChartOverviewComponent } from "./heatingelement/heatingelementchartoverview/heatingelementchartoverview.component"; -import { HeatingelementWidgetComponent } from "./heatingelement/widget.component"; import { HeatPumpChartComponent } from "./heatpump/chart.component"; import { HeatPumpChartOverviewComponent } from "./heatpump/heatpumpchartoverview/heatpumpchartoverview.component"; import { HeatpumpWidgetComponent } from "./heatpump/widget.component"; @@ -36,10 +32,10 @@ import { StorageComponent } from "./storage/widget.component"; @NgModule({ imports: [ - SharedModule, Common, Controller, HistoryDataErrorModule, + SharedModule, ], declarations: [ AsymmetricPeakshavingChartComponent, @@ -50,13 +46,11 @@ import { StorageComponent } from "./storage/widget.component"; DelayedSellToGridChartComponent, DelayedSellToGridChartOverviewComponent, DelayedSellToGridWidgetComponent, - HeatingelementChartComponent, - HeatingelementChartOverviewComponent, - HeatingelementWidgetComponent, HeatPumpChartComponent, HeatPumpChartOverviewComponent, HeatpumpWidgetComponent, HistoryComponent, + HistoryParentComponent, SocStorageChartComponent, StorageChargerChartComponent, StorageChartOverviewComponent, @@ -70,7 +64,6 @@ import { StorageComponent } from "./storage/widget.component"; TimeslotPeakshavingChartComponent, TimeslotPeakshavingChartOverviewComponent, TimeslotPeakshavingWidgetComponent, - HistoryParentComponent, ], }) export class HistoryModule { } diff --git a/ui/src/app/edge/live/common/consumption/flat/flat.ts b/ui/src/app/edge/live/common/consumption/flat/flat.ts index 85b0069083f..58c13f96316 100644 --- a/ui/src/app/edge/live/common/consumption/flat/flat.ts +++ b/ui/src/app/edge/live/common/consumption/flat/flat.ts @@ -50,8 +50,11 @@ export class FlatComponent extends AbstractFlatWidget { // Get EVCSs this.evcss = this.config.getComponentsImplementingNature("io.openems.edge.evcs.api.Evcs") - .filter(component => !(component.factoryId == "Evcs.Cluster.SelfConsumption") && - !(component.factoryId == "Evcs.Cluster.PeakShaving") && !component.isEnabled == false); + .filter(component => + !(component.factoryId == "Evcs.Cluster.SelfConsumption") && + !(component.factoryId == "Evcs.Cluster.PeakShaving") && + !(this.config.factories[component.factoryId].natureIds.includes("io.openems.edge.meter.api.ElectricityMeter")) && + !component.isEnabled == false); for (const component of this.evcss) { channelAddresses.push( diff --git a/ui/src/app/edge/live/common/consumption/modal/modal.ts b/ui/src/app/edge/live/common/consumption/modal/modal.ts index ccdcd9af819..a228215317f 100644 --- a/ui/src/app/edge/live/common/consumption/modal/modal.ts +++ b/ui/src/app/edge/live/common/consumption/modal/modal.ts @@ -16,8 +16,11 @@ export class ModalComponent extends AbstractFormlyComponent { public static generateView(config: EdgeConfig, translate: TranslateService): OeFormlyView { const evcss: EdgeConfig.Component[] | null = config.getComponentsImplementingNature("io.openems.edge.evcs.api.Evcs") - .filter(component => !(component.factoryId == "Evcs.Cluster.SelfConsumption") && - !(component.factoryId == "Evcs.Cluster.PeakShaving") && !component.isEnabled == false); + .filter(component => + !(component.factoryId == "Evcs.Cluster.SelfConsumption") && + !(component.factoryId == "Evcs.Cluster.PeakShaving") && + !(config.factories[component.factoryId].natureIds.includes("io.openems.edge.meter.api.ElectricityMeter")) && + !component.isEnabled == false); const consumptionMeters: EdgeConfig.Component[] | null = config.getComponentsImplementingNature("io.openems.edge.meter.api.ElectricityMeter") .filter(component => component.isEnabled && config.isTypeConsumptionMetered(component)); diff --git a/ui/src/app/edge/live/live.component.html b/ui/src/app/edge/live/live.component.html index 88a68dd2777..9d4cf55d172 100644 --- a/ui/src/app/edge/live/live.component.html +++ b/ui/src/app/edge/live/live.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/alerting/alerting.component.html b/ui/src/app/edge/settings/alerting/alerting.component.html index 0177269976c..f674bd30749 100644 --- a/ui/src/app/edge/settings/alerting/alerting.component.html +++ b/ui/src/app/edge/settings/alerting/alerting.component.html @@ -6,7 +6,7 @@ vertical-align: middle; } -
    + diff --git a/ui/src/app/edge/settings/app/index.component.html b/ui/src/app/edge/settings/app/index.component.html index 48926c47bdf..fd010aeac6a 100644 --- a/ui/src/app/edge/settings/app/index.component.html +++ b/ui/src/app/edge/settings/app/index.component.html @@ -61,7 +61,7 @@

    -
    + diff --git a/ui/src/app/edge/settings/app/install.component.html b/ui/src/app/edge/settings/app/install.component.html index 2418d652e3a..b9fe99c5215 100644 --- a/ui/src/app/edge/settings/app/install.component.html +++ b/ui/src/app/edge/settings/app/install.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/app/single.component.html b/ui/src/app/edge/settings/app/single.component.html index 465413bd056..3896e3aa39c 100644 --- a/ui/src/app/edge/settings/app/single.component.html +++ b/ui/src/app/edge/settings/app/single.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/app/update.component.html b/ui/src/app/edge/settings/app/update.component.html index 039d79b1826..e336e7124de 100644 --- a/ui/src/app/edge/settings/app/update.component.html +++ b/ui/src/app/edge/settings/app/update.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/channels/channels.component.html b/ui/src/app/edge/settings/channels/channels.component.html index ecef95c38f1..c0931184525 100644 --- a/ui/src/app/edge/settings/channels/channels.component.html +++ b/ui/src/app/edge/settings/channels/channels.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/component/install/index.component.html b/ui/src/app/edge/settings/component/install/index.component.html index c918b508485..bf72bc8faba 100644 --- a/ui/src/app/edge/settings/component/install/index.component.html +++ b/ui/src/app/edge/settings/component/install/index.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/component/install/install.component.html b/ui/src/app/edge/settings/component/install/install.component.html index ae3916b1def..39bd2dfbb1a 100644 --- a/ui/src/app/edge/settings/component/install/install.component.html +++ b/ui/src/app/edge/settings/component/install/install.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/component/update/index.component.html b/ui/src/app/edge/settings/component/update/index.component.html index 30373db23e2..2cd3b5e7223 100644 --- a/ui/src/app/edge/settings/component/update/index.component.html +++ b/ui/src/app/edge/settings/component/update/index.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/component/update/update.component.html b/ui/src/app/edge/settings/component/update/update.component.html index aeaee6f0114..793ad16cfed 100644 --- a/ui/src/app/edge/settings/component/update/update.component.html +++ b/ui/src/app/edge/settings/component/update/update.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.html b/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.html index 0468ec0c4d5..9359068039c 100644 --- a/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.html +++ b/ui/src/app/edge/settings/jsonrpctest/jsonrpctest.html @@ -31,7 +31,6 @@
    -
    diff --git a/ui/src/app/edge/settings/network/network.component.html b/ui/src/app/edge/settings/network/network.component.html index aaae39ad4ae..1526c04e99c 100644 --- a/ui/src/app/edge/settings/network/network.component.html +++ b/ui/src/app/edge/settings/network/network.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/powerassistant/powerassistant.html b/ui/src/app/edge/settings/powerassistant/powerassistant.html index 8fdb3666116..e428d16d831 100644 --- a/ui/src/app/edge/settings/powerassistant/powerassistant.html +++ b/ui/src/app/edge/settings/powerassistant/powerassistant.html @@ -7,7 +7,7 @@ -
    + diff --git a/ui/src/app/edge/settings/profile/aliasupdate.component.html b/ui/src/app/edge/settings/profile/aliasupdate.component.html index 1e63df550f6..47efa868cca 100644 --- a/ui/src/app/edge/settings/profile/aliasupdate.component.html +++ b/ui/src/app/edge/settings/profile/aliasupdate.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/profile/profile.component.html b/ui/src/app/edge/settings/profile/profile.component.html index a5adddc50c3..2e9b3884104 100644 --- a/ui/src/app/edge/settings/profile/profile.component.html +++ b/ui/src/app/edge/settings/profile/profile.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/settings.component.html b/ui/src/app/edge/settings/settings.component.html index abfdf9a2a04..4c22db2268f 100644 --- a/ui/src/app/edge/settings/settings.component.html +++ b/ui/src/app/edge/settings/settings.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/settings.module.ts b/ui/src/app/edge/settings/settings.module.ts index 3e12f05e109..0f252dc9277 100644 --- a/ui/src/app/edge/settings/settings.module.ts +++ b/ui/src/app/edge/settings/settings.module.ts @@ -1,6 +1,5 @@ import { NgModule } from "@angular/core"; import { ChangelogModule } from "src/app/changelog/changelog.module"; - import { SharedModule } from "./../../shared/shared.module"; import { AlertingComponent } from "./alerting/alerting.component"; import { AppModule } from "./app/app.module"; diff --git a/ui/src/app/edge/settings/system/system.component.html b/ui/src/app/edge/settings/system/system.component.html index fb00b72fee6..154e66467b4 100644 --- a/ui/src/app/edge/settings/system/system.component.html +++ b/ui/src/app/edge/settings/system/system.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/systemexecute/systemexecute.component.html b/ui/src/app/edge/settings/systemexecute/systemexecute.component.html index c22504d89c5..82cf2b8c0f7 100644 --- a/ui/src/app/edge/settings/systemexecute/systemexecute.component.html +++ b/ui/src/app/edge/settings/systemexecute/systemexecute.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/edge/settings/systemlog/systemlog.component.html b/ui/src/app/edge/settings/systemlog/systemlog.component.html index 37919bc7547..9acbac2f1da 100644 --- a/ui/src/app/edge/settings/systemlog/systemlog.component.html +++ b/ui/src/app/edge/settings/systemlog/systemlog.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/index/login.component.ts b/ui/src/app/index/login.component.ts index 7b2f876f623..63b7077c7e7 100644 --- a/ui/src/app/index/login.component.ts +++ b/ui/src/app/index/login.component.ts @@ -3,6 +3,7 @@ import { AfterContentChecked, ChangeDetectorRef, Component, OnDestroy, OnInit } import { FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { Capacitor } from "@capacitor/core"; +import { ViewWillEnter } from "@ionic/angular"; import { Subject } from "rxjs"; import { environment } from "src/environments"; @@ -15,7 +16,7 @@ import { Edge, Service, Utils, Websocket } from "../shared/shared"; selector: "login", templateUrl: "./login.component.html", }) -export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { +export class LoginComponent implements ViewWillEnter, AfterContentChecked, OnDestroy, OnInit { public environment = environment; public form: FormGroup; protected formIsDisabled: boolean = false; @@ -53,18 +54,16 @@ export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { } ngOnInit() { - - // TODO add websocket status observable const interval = setInterval(() => { - if (this.websocket.status === "online") { + if (this.websocket.status === "online" && !this.router.url.split("/").includes("live")) { this.router.navigate(["/overview"]); clearInterval(interval); } }, 1000); } - async ionViewWillEnter() { + async ionViewWillEnter() { // Execute Login-Request if url path matches 'demo' if (this.route.snapshot.routeConfig.path == "demo") { @@ -107,7 +106,7 @@ export class LoginComponent implements OnInit, AfterContentChecked, OnDestroy { .finally(() => { // Unclean - this.ngOnInit(); + this.ionViewWillEnter(); this.formIsDisabled = false; }); } diff --git a/ui/src/app/index/overview/overview.component.html b/ui/src/app/index/overview/overview.component.html index e1a0fa4e05d..e4c411d6833 100644 --- a/ui/src/app/index/overview/overview.component.html +++ b/ui/src/app/index/overview/overview.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/app/index/overview/overview.component.ts b/ui/src/app/index/overview/overview.component.ts index f8a129eaa3a..8f885578816 100644 --- a/ui/src/app/index/overview/overview.component.ts +++ b/ui/src/app/index/overview/overview.component.ts @@ -1,8 +1,8 @@ // @ts-strict-ignore -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnDestroy } from "@angular/core"; import { FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { InfiniteScrollCustomEvent } from "@ionic/angular"; +import { InfiniteScrollCustomEvent, ViewWillEnter } from "@ionic/angular"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; import { filter, take } from "rxjs/operators"; @@ -10,14 +10,13 @@ import { Pagination } from "src/app/shared/service/pagination"; import { Edge, Service, Utils, Websocket } from "src/app/shared/shared"; import { Role } from "src/app/shared/type/role"; import { environment } from "src/environments"; - import { ChosenFilter } from "../filter/filter.component"; @Component({ selector: "overview", templateUrl: "./overview.component.html", }) -export class OverViewComponent implements OnInit, OnDestroy { +export class OverViewComponent implements ViewWillEnter, OnDestroy { public environment = environment; /** True, if there is no access to any Edge. */ public noEdges: boolean = false; @@ -50,7 +49,7 @@ export class OverViewComponent implements OnInit, OnDestroy { public pagination: Pagination, ) { } - ngOnInit() { + ionViewWillEnter() { this.page = 0; this.filteredEdges = []; this.limitReached = false; @@ -139,7 +138,6 @@ export class OverViewComponent implements OnInit, OnDestroy { take(1), ) .subscribe(metadata => { - const edgeIds = Object.keys(metadata.edges); this.noEdges = edgeIds.length === 0; this.loggedInUserCanInstall = Role.isAtLeast(metadata.user.globalRole, "installer"); diff --git a/ui/src/app/shared/components/chart/abstracthistorychart.ts b/ui/src/app/shared/components/chart/abstracthistorychart.ts index 3fdc02b231b..b7610c4f5ea 100644 --- a/ui/src/app/shared/components/chart/abstracthistorychart.ts +++ b/ui/src/app/shared/components/chart/abstracthistorychart.ts @@ -405,6 +405,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { }; }; + options.plugins.legend.labels.generateLabels = function (chart: Chart.Chart) { const chartLegendLabelItems: Chart.LegendItem[] = []; @@ -467,9 +468,16 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { options.plugins.tooltip.enabled = chartObject.tooltip.enabled ?? true; // Remove duplicates from legend, if legendItem with two or more occurrences in legend, use one legendItem to trigger them both - options.plugins.legend.onClick = function (event: Chart.ChartEvent, legendItem: Chart.LegendItem, legend) { + options.plugins.legend.onClick = function (event: Chart.ChartEvent, legendItem: Chart.LegendItem, legend: Chart.LegendElement) { const chart: Chart.Chart = this.chart; + function rebuildScales(chart: Chart.Chart) { + let options = chart.options; + chartObject.yAxes.forEach((element) => { + options = AbstractHistoryChart.getYAxisOptions(options, element, translate, chartType, locale, _dataSets, showYAxisType); + }); + } + const legendItems = chart.data.datasets.reduce((arr, ds, i) => { if (ds.label == legendItem.text) { arr.push({ label: ds.label, index: i }); @@ -485,10 +493,19 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { meta.hidden = meta.hidden === null ? !chart.data.datasets[item.index].hidden : null; }); - // We hid a dataset ... rerender the chart + /** needs to be set, cause property async set */ + const _dataSets: Chart.ChartDataset[] = datasets.map((v, k) => { + if (k === legendItem.datasetIndex) { + v.hidden = !v.hidden; + } + return v; + }); + + rebuildScales(chart); chart.update(); }; + options.scales.x.ticks["source"] = "auto"; options.scales.x.ticks.maxTicksLimit = 31; options.scales.x["bounds"] = "ticks"; @@ -511,9 +528,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { public static getYAxisOptions(options: Chart.ChartOptions, element: HistoryUtils.yAxes, translate: TranslateService, chartType: "line" | "bar", locale: string, datasets: Chart.ChartDataset[], showYAxisType?: boolean): Chart.ChartOptions { const baseConfig = ChartConstants.DEFAULT_Y_SCALE_OPTIONS(element, translate, chartType, datasets, showYAxisType); - switch (element.unit) { - case YAxisType.RELAY: options.scales[element.yAxisId] = { ...baseConfig, @@ -524,7 +539,7 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { ...baseConfig.ticks, stepSize: 1, // Two states are possible - callback: function (value, index, ticks) { + callback: function (value, index, ticks: Chart.Tick[]) { return Converter.ON_OFF(translate)(value); }, padding: 5, @@ -562,6 +577,18 @@ export abstract class AbstractHistoryChart implements OnInit, OnDestroy { }, }; break; + case YAxisType.LEVEL: + options.scales[element.yAxisId] = { + ...baseConfig, + min: 0, + max: 3, + beginAtZero: true, + ticks: { + ...baseConfig.ticks, + stepSize: 1, + }, + }; + break; case YAxisType.VOLTAGE: case YAxisType.CURRENT: options.scales[element.yAxisId] = { diff --git a/ui/src/app/shared/components/chart/chart.constants.spec.ts b/ui/src/app/shared/components/chart/chart.constants.spec.ts index 2e38cb4cbe7..3687e1d71fb 100644 --- a/ui/src/app/shared/components/chart/chart.constants.spec.ts +++ b/ui/src/app/shared/components/chart/chart.constants.spec.ts @@ -7,9 +7,9 @@ import { ChartConstants } from "./chart.constants"; describe("Chart constants", () => { it("#calculateStepSize", () => { - expect(ChartConstants.calculateStepSize(0, 10)).toEqual(2.5); + expect(ChartConstants.calculateStepSize(0, 10)).toEqual(2); expect(ChartConstants.calculateStepSize(0, null)).toEqual(null); - expect(ChartConstants.calculateStepSize(-10, 0)).toEqual(2.5); + expect(ChartConstants.calculateStepSize(-10, 0)).toEqual(2); expect(ChartConstants.calculateStepSize(undefined, 0)).toEqual(null); // min higher than max @@ -26,9 +26,9 @@ describe("Chart constants", () => { }, ]; - expect(ChartConstants.getScaleOptions([], yAxis)).toEqual({ min: null, max: null, stepSize: null }); - expect(ChartConstants.getScaleOptions(datasets, yAxis)).toEqual({ min: 0, max: 1892, stepSize: 473 }); - expect(ChartConstants.getScaleOptions(null, yAxis)).toEqual(null); - expect(ChartConstants.getScaleOptions(null, null)).toEqual(null); + expect(ChartConstants.getScaleOptions([], yAxis, "line")).toEqual({ min: null, max: null, stepSize: null }); + expect(ChartConstants.getScaleOptions(datasets, yAxis, "line")).toEqual({ min: 0, max: 1892, stepSize: 378.4 }); + expect(ChartConstants.getScaleOptions(null, yAxis, "line")).toEqual({ min: null, max: null, stepSize: null }); + expect(ChartConstants.getScaleOptions(null, null, "line")).toEqual({ min: null, max: null, stepSize: null }); }); }); diff --git a/ui/src/app/shared/components/chart/chart.constants.ts b/ui/src/app/shared/components/chart/chart.constants.ts index 2adc960c541..acae3146c98 100644 --- a/ui/src/app/shared/components/chart/chart.constants.ts +++ b/ui/src/app/shared/components/chart/chart.constants.ts @@ -1,16 +1,16 @@ // @ts-strict-ignore - import { formatNumber } from "@angular/common"; import { TranslateService } from "@ngx-translate/core"; import { ChartComponentLike, ChartDataset } from "chart.js"; import ChartDataLabels from "chartjs-plugin-datalabels"; +import { RGBColor } from "../../service/defaulttypes"; import { HistoryUtils, Utils } from "../../service/utils"; import { Language } from "../../type/language"; import { ArrayUtils } from "../../utils/array/array.utils"; import { AbstractHistoryChart } from "./abstracthistorychart"; export class ChartConstants { - public static readonly NUMBER_OF_Y_AXIS_TICKS: number = 6; + public static readonly NUMBER_OF_Y_AXIS_TICKS: number = 7; public static readonly EMPTY_DATASETS: ChartDataset[] = []; public static readonly REQUEST_TIMEOUT = 500; @@ -43,7 +43,7 @@ export class ChartConstants { ...ChartDataLabels, formatter: (value, ctx) => { const locale: string = (Language.getByKey(localStorage.LANGUAGE) ?? Language.DEFAULT).i18nLocaleKey; - return formatNumber(value, locale, "1.0-0") + "\xa0" + unit ?? null; + return formatNumber(value, locale, "1.0-0") + "\xa0" + unit; }, ...{ anchor: "end", offset: -18, align: "start", clip: false, clamp: true, @@ -53,6 +53,14 @@ export class ChartConstants { }); }; + public static Colors = class { + public static RED: string = new RGBColor(200, 0, 0).toString(); + }; + + public static readonly NumberFormat = class { + public static NO_DECIMALS: string = "1.0-0"; + }; + /** * Default yScale CartesianScaleTypeRegistry.linear * @@ -64,25 +72,36 @@ export class ChartConstants { */ public static DEFAULT_Y_SCALE_OPTIONS = (element: HistoryUtils.yAxes, translate: TranslateService, chartType: "line" | "bar", datasets: ChartDataset[], showYAxisTitle?: boolean) => { const beginAtZero: boolean = ChartConstants.isDataSeriesPositive(datasets); + const scaleOptions: ReturnType = this.getScaleOptions(datasets, element, chartType); return { title: { text: element.customTitle ?? AbstractHistoryChart.getYAxisType(element.unit, translate, chartType), - display: showYAxisTitle, + display: false, padding: 5, font: { size: 11, }, }, + stacked: chartType === "line" ? false : true, beginAtZero: beginAtZero, position: element.position, grid: { display: element.displayGrid ?? true, }, + ...(scaleOptions?.min !== null && { min: scaleOptions.min }), + ...(scaleOptions?.max !== null && { max: scaleOptions.max }), ticks: { color: getComputedStyle(document.documentElement).getPropertyValue("--ion-color-text"), padding: 5, maxTicksLimit: ChartConstants.NUMBER_OF_Y_AXIS_TICKS, + ...(scaleOptions?.stepSize && { stepSize: scaleOptions.stepSize }), + callback: function (value, index, ticks) { + if (index == (ticks.length - 1) && showYAxisTitle) { + return element.customTitle ?? AbstractHistoryChart.getYAxisType(element.unit, translate, chartType); + } + return value; + }, }, }; }; @@ -94,10 +113,35 @@ export class ChartConstants { * @param yAxis the yAxis * @returns min, max and stepsize for datasets belonging to this yAxis */ - public static getScaleOptions(datasets: ChartDataset[], yAxis: HistoryUtils.yAxes): { min: number; max: number; stepSize: number; } | null { + public static getScaleOptions(datasets: ChartDataset[], yAxis: HistoryUtils.yAxes, chartType: "line" | "bar"): { min: number; max: number; stepSize: number; } | null { + + const stackMap: { [index: string]: ChartDataset } = {}; + datasets?.filter(el => el["yAxisID"] === yAxis.yAxisId).forEach((dataset, index) => { + const stackId = dataset.stack || "default"; // If no stack is defined, use "default" + + if (dataset.hidden) { + return; + } + + if (chartType === "line") { + stackMap[index] = dataset; + return; + } + + if (!(stackId in stackMap)) { + // If the stack doesn"t exist yet, create an entry + stackMap[stackId] = { ...dataset, data: [...dataset.data] }; + } else { + // If the stack already exists, merge the data arrays + stackMap[stackId].data = stackMap[stackId].data.map((value, index) => { + return Utils.addSafely(value as number, (dataset.data[index] as number || 0)); // Sum data points or handle missing values + }); + } + }); + + return Object.values(stackMap) + .reduce((arr: { min: number, max: number, stepSize: number }, dataset: ChartDataset) => { - return datasets?.filter(el => el["yAxisID"] === yAxis.yAxisId) - .reduce((arr, dataset) => { const min = Math.floor(Math.min(arr.min, ArrayUtils.findSmallestNumber(dataset.data as number[]))) ?? null; const max = Math.ceil(Math.max(arr.max, ArrayUtils.findBiggestNumber(dataset.data as number[]))) ?? null; @@ -108,7 +152,7 @@ export class ChartConstants { arr = { min: min, max: max, - stepSize: Math.max(arr.stepSize, ChartConstants.calculateStepSize(min, max)), + stepSize: Math.max(arr?.stepSize ?? 0, ChartConstants.calculateStepSize(min, max)), }; return arr; diff --git a/ui/src/app/shared/components/edge/edge.ts b/ui/src/app/shared/components/edge/edge.ts index babbb91da9e..64cb0fe962a 100644 --- a/ui/src/app/shared/components/edge/edge.ts +++ b/ui/src/app/shared/components/edge/edge.ts @@ -22,9 +22,9 @@ import { GetChannelResponse } from "../../jsonrpc/response/getChannelResponse"; import { Channel, GetChannelsOfComponentResponse } from "../../jsonrpc/response/getChannelsOfComponentResponse"; import { GetEdgeConfigResponse } from "../../jsonrpc/response/getEdgeConfigResponse"; import { GetPropertiesOfFactoryResponse } from "../../jsonrpc/response/getPropertiesOfFactoryResponse"; -import { ArrayUtils } from "../../service/arrayutils"; import { ChannelAddress, EdgePermission, SystemLog, Websocket } from "../../shared"; import { Role } from "../../type/role"; +import { ArrayUtils } from "../../utils/array/array.utils"; import { CurrentData } from "./currentdata"; import { EdgeConfig } from "./edgeconfig"; diff --git a/ui/src/app/shared/components/edge/edgeconfig.ts b/ui/src/app/shared/components/edge/edgeconfig.ts index 4b12b73aed2..907ebb27fb9 100644 --- a/ui/src/app/shared/components/edge/edgeconfig.ts +++ b/ui/src/app/shared/components/edge/edgeconfig.ts @@ -115,7 +115,7 @@ export class EdgeConfig { category: { title: "Zähler", icon: "speedometer-outline" }, factories: [ EdgeConfig.getFactoriesByNature(factories, "io.openems.edge.meter.api.SymmetricMeter"), // TODO replaced by ElectricityMeter - EdgeConfig.getFactoriesByNature(factories, "io.openems.edge.meter.api.ElectricityMeter"), + EdgeConfig.getFactoriesByNature(factories, "io.openems.edge.meter.api.ElectricityMeter", "io.openems.edge.evcs.api.Evcs"), EdgeConfig.getFactoriesByNature(factories, "io.openems.edge.ess.dccharger.api.EssDcCharger"), ].flat(2), }, @@ -258,13 +258,20 @@ export class EdgeConfig { /** * Get Factories of Nature. * - * @param natureId the given Nature. + * @param factories the given EdgeConfig.Factory + * @param includeNature the name of the Nature to be included + * @param excludeNature an optional name of a Nature to be excluded */ - public static getFactoriesByNature(factories: { [id: string]: EdgeConfig.Factory }, natureId: string): EdgeConfig.Factory[] { + public static getFactoriesByNature(factories: { [id: string]: EdgeConfig.Factory }, includeNature: string, excludeNature?: string): EdgeConfig.Factory[] { const result = []; - const nature = EdgeConfig.getNaturesOfFactories(factories)[natureId]; - if (nature) { - for (const factoryId of nature.factoryIds) { + const natures = EdgeConfig.getNaturesOfFactories(factories); + const include = natures[includeNature]; + const excludes = excludeNature != null && excludeNature in natures ? natures[excludeNature].factoryIds : []; + if (include) { + for (const factoryId of include.factoryIds) { + if (excludes.includes(factoryId)) { + continue; + } if (factoryId in factories) { result.push(factories[factoryId]); } @@ -509,25 +516,19 @@ export class EdgeConfig { if (component.properties["type"] == "PRODUCTION") { return true; } + const natureIds = this.getNatureIdsByFactoryId(component.factoryId); + if (natureIds.includes("io.openems.edge.pvinverter.api.ManagedSymmetricPvInverter") + || natureIds.includes("io.openems.edge.ess.dccharger.api.EssDcCharger")) { + return true; + } // TODO properties in OSGi Component annotations are not transmitted correctly with Apache Felix SCR switch (component.factoryId) { case "Fenecon.Dess.PvMeter": case "Fenecon.Mini.PvMeter": case "Fenecon.Pro.PvMeter": - case "Kaco.BlueplanetHybrid10.PvInverter": - case "Kostal.Piko.Charger": - case "PV-Inverter.Fronius": - case "PV-Inverter.KACO.blueplanet": - case "PV-Inverter.Kostal": - case "PV-Inverter.SMA.SunnyTripower": - case "PV-Inverter.Solarlog": - case "PV-Inverter.SunSpec": case "Simulator.ProductionMeter.Acting": - case "Simulator.PvInverter": - case "SolarEdge.PV-Inverter": return true; } - return false; } @@ -540,11 +541,14 @@ export class EdgeConfig { public isTypeConsumptionMetered(component: EdgeConfig.Component) { if (component.properties["type"] == "CONSUMPTION_METERED") { return true; - } else { - switch (component.factoryId) { - case "GoodWe.EmergencyPowerMeter": - return true; - } + } + switch (component.factoryId) { + case "GoodWe.EmergencyPowerMeter": + return true; + } + const natures = this.getNatureIdsByFactoryId(component.factoryId); + if (natures.includes("io.openems.edge.evcs.api.Evcs") && !natures.includes("io.openems.edge.evcs.api.MetaEvcs")) { + return true; } return false; } diff --git a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts index 3faf1948a9f..439c8cbd220 100644 --- a/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts +++ b/ui/src/app/shared/components/flat/flat-widget-percentagebar/flat-widget-percentagebar.ts @@ -6,7 +6,10 @@ import { AbstractFlatWidgetLine } from "../abstract-flat-widget-line"; templateUrl: "./flat-widget-percentagebar.html", }) export class FlatWidgetPercentagebarComponent extends AbstractFlatWidgetLine { - protected get displayPercent(): number { - return Math.round(Number.parseFloat(this.displayValue)); + + protected get displayPercent(): number | null { + return this.displayValue === null + ? null + : Math.round(Number.parseFloat(this.displayValue)); } } diff --git a/ui/src/app/shared/components/header/app-header.ts b/ui/src/app/shared/components/header/app-header.ts new file mode 100644 index 00000000000..1a3a2596fbc --- /dev/null +++ b/ui/src/app/shared/components/header/app-header.ts @@ -0,0 +1,222 @@ +// @ts-strict-ignore +import { AfterViewChecked, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { NavigationEnd, Router } from "@angular/router"; +import { MenuController, ModalController } from "@ionic/angular"; +import { Subject } from "rxjs"; +import { filter, takeUntil } from "rxjs/operators"; +import { environment } from "src/environments"; + +import { Edge, Service, Websocket } from "../../shared"; +import { PickDateComponent } from "../pickdate/pickdate.component"; +import { StatusSingleComponent } from "../status/single/status.component"; + +@Component({ + selector: "app-header", + templateUrl: "./header.component.html", +}) +export class AppHeaderComponent implements OnInit, OnDestroy, AfterViewChecked { + + @ViewChild(PickDateComponent, { static: false }) public PickDateComponent: PickDateComponent; + + public environment = environment; + public backUrl: string | boolean = "/"; + public enableSideMenu: boolean; + public currentPage: "EdgeSettings" | "Other" | "IndexLive" | "IndexHistory" = "Other"; + public isSystemLogEnabled: boolean = false; + + protected isHeaderAllowed: boolean = false; + + private ngUnsubscribe: Subject = new Subject(); + private _customBackUrl: string | null = null; + + constructor( + private cdRef: ChangeDetectorRef, + public menu: MenuController, + public modalCtrl: ModalController, + public router: Router, + public service: Service, + public websocket: Websocket, + ) { } + + @Input() public set customBackUrl(url: string | null) { + if (!url) { + return; + } + this._customBackUrl = url; + this.updateBackUrl(url); + } + + ngOnInit() { + // set inital URL + this.updateUrl(this.router.routerState.snapshot.url); + // update backUrl on navigation events + this.router.events.pipe( + takeUntil(this.ngUnsubscribe), + filter(event => event instanceof NavigationEnd), + ).subscribe(event => { + window.scrollTo(0, 0); + this.updateUrl((event).urlAfterRedirects); + }); + + } + + // used to prevent 'Expression has changed after it was checked' error + ngAfterViewChecked() { + this.cdRef.detectChanges(); + } + + updateUrl(url: string) { + this.updateBackUrl(url); + this.updateEnableSideMenu(url); + this.updateCurrentPage(url); + this.isHeaderAllowed = this.isAllowedForView(url); + } + + updateEnableSideMenu(url: string) { + const urlArray = url.split("/"); + const file = urlArray.pop(); + + if (file == "user" || file == "settings" || file == "changelog" || file == "login" || file == "index" || urlArray.length > 3) { + // disable side-menu; show back-button instead + this.enableSideMenu = false; + } else { + // enable side-menu if back-button is not needed + this.enableSideMenu = true; + } + } + + updateBackUrl(url: string) { + + if (this._customBackUrl) { + this.backUrl = this._customBackUrl; + return; + } + + // disable backUrl & Segment Navigation on initial 'login' page + if (url === "/login" || url === "/overview" || url === "/index") { + this.backUrl = false; + return; + } + + + // set backUrl for user when an Edge had been selected before + const currentEdge: Edge = this.service.currentEdge.value; + if (url === "/user" && currentEdge != null) { + this.backUrl = "/device/" + currentEdge.id + "/live"; + return; + } + + // set backUrl for user if no edge had been selected + if (url === "/user") { + this.backUrl = "/overview"; + return; + } + + if (url === "/changelog" && currentEdge != null) { + // TODO this does not work if Changelog was opened from /user + this.backUrl = "/device/" + currentEdge.id + "/settings/profile"; + return; + } + + const urlArray = url.split("/"); + let backUrl: string | boolean = "/"; + const file = urlArray.pop(); + + // disable backUrl for History & EdgeIndex Component ++ Enable Segment Navigation + if ((file == "history" || file == "live") && urlArray.length == 3) { + this.backUrl = false; + return; + } + + // disable backUrl to first 'index' page from Edge index if there is only one Edge in the system + if (file === "live" && urlArray.length == 3 && this.environment.backend === "OpenEMS Edge") { + this.backUrl = false; + return; + } + + // remove one part of the url for 'index' + if (file === "live") { + urlArray.pop(); + } + + // fix url for App "settings/app/install" and "settings/app/update" + if (urlArray.slice(-3, -1).join("/") === "settings/app") { + urlArray.pop(); + } + + // re-join the url + backUrl = urlArray.join("/") || "/"; + + // correct path for '/device/[edgeId]/index' + if (backUrl === "/device") { + backUrl = "/"; + } + this.backUrl = backUrl; + } + + updateCurrentPage(url: string) { + const urlArray = url.split("/"); + let file = urlArray.pop(); + if (urlArray.length >= 4) { + file = urlArray[3]; + } + // Enable Segment Navigation for Edge-Index-Page + if ((file == "history" || file == "live") && urlArray.length == 3) { + if (file == "history") { + this.currentPage = "IndexHistory"; + } else { + this.currentPage = "IndexLive"; + } + } else if (file == "settings" && urlArray.length > 1) { + this.currentPage = "EdgeSettings"; + } + else { + this.currentPage = "Other"; + } + } + + public segmentChanged(event) { + if (event.detail.value == "IndexLive") { + this.router.navigate(["/device/" + this.service.currentEdge.value.id + "/live"], { replaceUrl: true }); + this.cdRef.detectChanges(); + } + if (event.detail.value == "IndexHistory") { + + /** Creates bug of being infinite forwarded betweeen live and history, if not relatively routed */ + // this.router.navigate(["../history"], { relativeTo: this.route }); + this.router.navigate(["/device/" + this.service.currentEdge.value.id + "/history"]); + this.cdRef.detectChanges(); + } + } + + async presentSingleStatusModal() { + const modal = await this.modalCtrl.create({ + component: StatusSingleComponent, + }); + return await modal.present(); + } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + + private isAllowedForView(url: string): boolean { + + // Strip queryParams + const cleanUrl = url.split("?")[0]; + + if (url.includes("/history/")) { + return false; + } + + switch (cleanUrl) { + case "/login": + case "/index": + case "/demo": + return false; + default: + return true; + } + } +} diff --git a/ui/src/app/shared/components/header/header.component.html b/ui/src/app/shared/components/header/header.component.html index b1e4714c831..0ef3fa7a05e 100644 --- a/ui/src/app/shared/components/header/header.component.html +++ b/ui/src/app/shared/components/header/header.component.html @@ -1,4 +1,4 @@ - + @@ -36,16 +36,17 @@ - + + [name]="edge.roleIsAtLeast('admin') ? 'information-outline' : 'checkmark-circle-outline'" + size="medium"> diff --git a/ui/src/app/shared/components/header/header.component.ts b/ui/src/app/shared/components/header/header.component.ts index 27c51f4732d..1a4fe4dbf74 100644 --- a/ui/src/app/shared/components/header/header.component.ts +++ b/ui/src/app/shared/components/header/header.component.ts @@ -1,6 +1,6 @@ // @ts-strict-ignore import { AfterViewChecked, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; +import { NavigationEnd, Router } from "@angular/router"; import { MenuController, ModalController } from "@ionic/angular"; import { Subject } from "rxjs"; import { filter, takeUntil } from "rxjs/operators"; @@ -23,10 +23,12 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { public enableSideMenu: boolean; public currentPage: "EdgeSettings" | "Other" | "IndexLive" | "IndexHistory" = "Other"; public isSystemLogEnabled: boolean = false; + + protected isHeaderAllowed: boolean = true; + private ngUnsubscribe: Subject = new Subject(); private _customBackUrl: string | null = null; - constructor( private cdRef: ChangeDetectorRef, public menu: MenuController, @@ -34,7 +36,6 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { public router: Router, public service: Service, public websocket: Websocket, - private route: ActivatedRoute, ) { } @Input() public set customBackUrl(url: string | null) { @@ -56,6 +57,7 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { window.scrollTo(0, 0); this.updateUrl((event).urlAfterRedirects); }); + } // used to prevent 'Expression has changed after it was checked' error @@ -180,7 +182,8 @@ export class HeaderComponent implements OnInit, OnDestroy, AfterViewChecked { if (event.detail.value == "IndexHistory") { /** Creates bug of being infinite forwarded betweeen live and history, if not relatively routed */ - this.router.navigate(["../history"], { relativeTo: this.route }); + // this.router.navigate(["../history"], { relativeTo: this.route }); + this.router.navigate(["/device/" + this.service.currentEdge.value.id + "/history"]); this.cdRef.detectChanges(); } } diff --git a/ui/src/app/shared/components/shared/testing/common.ts b/ui/src/app/shared/components/shared/testing/common.ts index 59e22834a1c..ea9af4c8948 100644 --- a/ui/src/app/shared/components/shared/testing/common.ts +++ b/ui/src/app/shared/components/shared/testing/common.ts @@ -22,7 +22,7 @@ export namespace OeTester { } export namespace ChartOptions { - export const LINE_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min?: number, max?: number, beginAtZero?: boolean }, ticks?: { stepSize: number; }; }; }, title?: string): OeChartTester.Dataset.Option => ({ + export const LINE_CHART_OPTIONS = (period: string, chartType: "line" | "bar", options: { [key: string]: { scale: { min?: number, max?: number, beginAtZero?: boolean }, ticks?: { stepSize: number; min?: number, max?: number }; }; }, title?: string): OeChartTester.Dataset.Option => ({ type: "option", options: { "responsive": true, "maintainAspectRatio": false, "elements": { "point": { "radius": 0, "hitRadius": 0, "hoverRadius": 0 }, "line": { "stepped": false, "fill": true } }, "datasets": { "bar": {}, "line": {} }, "plugins": { @@ -31,6 +31,7 @@ export namespace OeTester { }, }, "scales": { "x": { "stacked": true, "offset": false, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { + "stacked": false, "beginAtZero": false, ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "title": { "text": "kW", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, @@ -52,7 +53,9 @@ export namespace OeTester { }, "scales": { "x": { "stacked": true, "offset": true, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { - ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + "stacked": true, + ...options["left"]?.scale, + ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, "color": "", @@ -85,8 +88,9 @@ export namespace OeTester { }, }, "scales": { "x": { "stacked": true, "offset": false, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { + "stacked": false, ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, - "title": { "text": "kW", "display": true, "padding": 5, "font": { "size": 11 } }, + "title": { "text": "kW", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, @@ -96,8 +100,9 @@ export namespace OeTester { }, }, "right": { + "stacked": false, ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, - "title": { "text": "Zustand", "display": true, "padding": 5, "font": { "size": 11 } }, + "title": { "text": "Zustand", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "right", "grid": { "display": false }, "ticks": { ...options["right"]?.ticks, @@ -119,7 +124,8 @@ export namespace OeTester { }, "scales": { "x": { "stacked": true, "offset": true, "type": "time", "ticks": { "source": "auto", "maxTicksLimit": 31 }, "bounds": "ticks", "adapters": { "date": { "locale": { "code": "de", "formatLong": {}, "localize": {}, "match": {}, "options": { "weekStartsOn": 1, "firstWeekContainsDate": 4 } } } }, "time": { "unit": period as TimeUnit, "displayFormats": { "datetime": "yyyy-MM-dd HH:mm:ss", "millisecond": "SSS [ms]", "second": "HH:mm:ss a", "minute": "HH:mm", "hour": "HH:00", "day": "dd", "week": "ll", "month": "MM", "quarter": "[Q]Q - YYYY", "year": "yyyy" } } }, "left": { - ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": true, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, + "stacked": true, + ...options["left"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, "title": { "text": "kWh", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "left", "grid": { "display": true }, "ticks": { ...options["left"]?.ticks, "color": "", @@ -128,8 +134,9 @@ export namespace OeTester { }, }, "right": { - ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : { min: 0 }), "beginAtZero": true, - "title": { "text": "Aktive Zeit", "display": true, "padding": 5, "font": { "size": 11 } }, + "stacked": true, + ...options["right"]?.scale, ...(chartType === "line" ? { stacked: false } : {}), "beginAtZero": true, + "title": { "text": "Aktive Zeit", "display": false, "padding": 5, "font": { "size": 11 } }, "position": "right", "grid": { "display": false }, "ticks": { "color": "", diff --git a/ui/src/app/shared/components/shared/testing/tester.ts b/ui/src/app/shared/components/shared/testing/tester.ts index 7b2e9c7c662..9a4084f6bb5 100644 --- a/ui/src/app/shared/components/shared/testing/tester.ts +++ b/ui/src/app/shared/components/shared/testing/tester.ts @@ -6,6 +6,7 @@ import { QueryHistoricTimeseriesEnergyPerPeriodResponse } from "src/app/shared/j import { HistoryUtils } from "src/app/shared/service/utils"; import { CurrentData, EdgeConfig } from "src/app/shared/shared"; +import { ObjectUtils } from "src/app/shared/utils/object/object.utils"; import { AbstractHistoryChart } from "../../chart/abstracthistorychart"; import { XAxisType } from "../../chart/chart.constants"; import { TextIndentation } from "../../modal/modal-line/modal-line"; @@ -307,9 +308,17 @@ export class OeChartTester { legendOptions.push(AbstractHistoryChart.getLegendOptions(label, displayValue)); }); + const options = AbstractHistoryChart.getOptions(chartData, chartType, testContext.service, testContext.translate, legendOptions, channelData.result, locale, config, datasets, xAxisType, labels); + + chartData.yAxes.filter(axis => axis.unit != null).forEach(axis => { + // Remove custom scale calculations from unittest, seperate unittest existing + options.scales[axis.yAxisId] = ObjectUtils.excludeProperties(options.scales[axis.yAxisId], ["min", "max"]) as Chart.ScaleOptionsByType<"radialLinear" | keyof Chart.CartesianScaleTypeRegistry>; + options.scales[axis.yAxisId].ticks = ObjectUtils.excludeProperties(options.scales[axis.yAxisId].ticks as Chart.RadialTickOptions, ["stepSize"]); + }); + return { type: "option", - options: AbstractHistoryChart.getOptions(chartData, chartType, testContext.service, testContext.translate, legendOptions, channelData.result, locale, config, datasets, xAxisType, labels), + options: options, }; } diff --git a/ui/src/app/shared/ngrx-store/states.ts b/ui/src/app/shared/ngrx-store/states.ts index 33d8f0aeb0d..c957daee5e3 100644 --- a/ui/src/app/shared/ngrx-store/states.ts +++ b/ui/src/app/shared/ngrx-store/states.ts @@ -35,7 +35,7 @@ export class AppStateTracker { protected router: Router, protected pagination: Pagination, private websocket: Websocket, - private previousRouteService: PreviousRouteService, + private routeService: PreviousRouteService, ) { if (!localStorage.getItem("AppState")) { console.log(`${AppStateTracker.LOG_PREFIX} Log deactivated`); @@ -55,15 +55,18 @@ export class AppStateTracker { * Handles navigation after authentication */ public navigateAfterAuthentication() { - const segments = this.router.routerState.snapshot.url.split("/"); - const previousUrl: string = this.previousRouteService.getPreviousUrl(); - if ((previousUrl === segments[segments.length - 1]) || previousUrl === "/") { - this.router.navigate(["./overview"]); - return; - } + this.router.navigate(["overview"]); + return; + // const segments = this.router.routerState.snapshot.url.split("/"); + // const previousUrl: string = this.routeService.getPreviousUrl(); + + // if ((previousUrl === segments[segments.length - 1]) || previousUrl === "/") { + // this.router.navigate(["./overview"]); + // return; + // } - this.router.navigate(previousUrl.split("/")); + // this.router.navigate(previousUrl.split("/")); } private startStateHandler(state: States): void { diff --git a/ui/src/app/shared/service/arrayutils.ts b/ui/src/app/shared/service/arrayutils.ts deleted file mode 100644 index 5fc4e31b654..00000000000 --- a/ui/src/app/shared/service/arrayutils.ts +++ /dev/null @@ -1,27 +0,0 @@ -export namespace ArrayUtils { - export function equalsCheck(a: T[], b: T[]) { - return a.length === b.length && - a.every((v, i) => v === b[i]); - } - - /** - * Sort arrays alphabetically, according to the string returned by fn. - * Elements for which fn returns null or undefined are sorted to the end in an undefined order. - * - * @param array to sort - * @param fn to get a string to sort by - * @returns sorted array - */ - export function sortedAlphabetically(array: Type[], fn: (arg: Type) => string): Type[] { - return array.sort((a: Type, b: Type) => { - const aVal = fn(a); - const bVal = fn(b); - if (!aVal) { - return !bVal ? 0 : 1; - } else if (!bVal) { - return -1; - } - return aVal.localeCompare(bVal, undefined, { sensitivity: "accent" }); - }); - } -} diff --git a/ui/src/app/shared/service/defaulttypes.spec.ts b/ui/src/app/shared/service/defaulttypes.spec.ts new file mode 100644 index 00000000000..b5a01f05370 --- /dev/null +++ b/ui/src/app/shared/service/defaulttypes.spec.ts @@ -0,0 +1,17 @@ +// @ts-strict-ignore +import { RGBColor } from "./defaulttypes"; + +describe("Defaulttypes", () => { + + it("#RgbColor.toString()", () => { + const black = new RGBColor(0, 0, 0); + expect(black.toString()).toEqual("rgb(0,0,0)"); + }); + + it("#RgbColor.toString() - invalid values", () => { + const allInvalid = new RGBColor(null, null, null); + expect(() => allInvalid.toString()).toThrow(Error("All values need to be valid")); + const oneInvalid = new RGBColor(0, 0, null); + expect(() => oneInvalid.toString()).toThrow(Error("All values need to be valid")); + }); +}); diff --git a/ui/src/app/shared/service/defaulttypes.ts b/ui/src/app/shared/service/defaulttypes.ts index 08b2419b983..f9652fb6dbe 100644 --- a/ui/src/app/shared/service/defaulttypes.ts +++ b/ui/src/app/shared/service/defaulttypes.ts @@ -256,3 +256,28 @@ export type TKeyValue = { }; /** */ export type PropType = TObj[TProp]; + +type Range = Acc["length"] extends N + ? Acc[number] + : Range; + +export type RGBValue = Range<256>; // 0 to 255 + +export class RGBColor { + private readonly red: T; + private readonly green: T; + private readonly blue: T; + + constructor(red: T, green: T, blue: T) { + this.red = red; + this.green = green; + this.blue = blue; + } + + public toString(): string { + if (this.red == null || this.green == null || this.blue == null) { + throw new Error("All values need to be valid"); + } + return `rgb(${this.red},${this.green},${this.blue})`; + } +} diff --git a/ui/src/app/shared/service/utils.ts b/ui/src/app/shared/service/utils.ts index 0d5f686b21a..837e37acc54 100644 --- a/ui/src/app/shared/service/utils.ts +++ b/ui/src/app/shared/service/utils.ts @@ -4,7 +4,6 @@ import { TranslateService } from "@ngx-translate/core"; import { ChartDataset } from "chart.js"; import { saveAs } from "file-saver-es"; import { DefaultTypes } from "src/app/shared/service/defaulttypes"; - import { JsonrpcResponseSuccess } from "../jsonrpc/base"; import { Base64PayloadResponse } from "../jsonrpc/response/base64PayloadResponse"; import { QueryHistoricTimeseriesEnergyResponse } from "../jsonrpc/response/queryHistoricTimeseriesEnergyResponse"; @@ -626,16 +625,17 @@ export class Utils { } export enum YAxisType { + CURRENCY, + CURRENT, + ENERGY, + LEVEL, NONE, - POWER, PERCENTAGE, - RELAY, - ENERGY, - VOLTAGE, + POWER, REACTIVE, - CURRENT, + RELAY, TIME, - CURRENCY, + VOLTAGE, } export enum ChartAxis { diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index d85cf25b0e5..75cca829719 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -27,6 +27,7 @@ import { InputTypeComponent } from "./components/formly/input"; import { FormlyInputSerialNumberWrapperComponent as FormlyWrapperInputSerialNumber } from "./components/formly/input-serial-number-wrapper"; import { PanelWrapperComponent } from "./components/formly/panel-wrapper.component"; import { RepeatTypeComponent } from "./components/formly/repeat"; +import { AppHeaderComponent } from "./components/header/app-header"; import { HeaderComponent } from "./components/header/header.component"; import { HistoryDataErrorModule } from "./components/history-data-error/history-data-error.module"; import { PercentageBarComponent } from "./components/percentagebar/percentagebar.component"; @@ -56,20 +57,12 @@ export function SubnetmaskValidatorMessage(err, field: FormlyFieldConfig) { return `"${field.formControl.value}" is not a valid Subnetmask`; } - @NgModule({ imports: [ BrowserAnimationsModule, - NgChartsModule, CommonModule, + ComponentsModule, DirectiveModule, - FormsModule, - IonicModule, - NgxSpinnerModule.forRoot({ - type: "ball-clip-rotate-multiple", - }), - ReactiveFormsModule, - RouterModule, FormlyModule.forRoot({ wrappers: [ { name: "form-field", component: FormlyWrapperFormFieldComponent }, @@ -96,56 +89,61 @@ export function SubnetmaskValidatorMessage(err, field: FormlyFieldConfig) { { name: "subnetmask", message: SubnetmaskValidatorMessage }, ], }), - PipeModule, - ComponentsModule, - TranslateModule, + FormsModule, HistoryDataErrorModule, + IonicModule, MeterModule, + NgChartsModule, + NgxSpinnerModule.forRoot({ + type: "ball-clip-rotate-multiple", + }), + PipeModule, + ReactiveFormsModule, + RouterModule, + TranslateModule, ], declarations: [ - // components + AppHeaderComponent, ChartOptionsComponent, - HeaderComponent, - PercentageBarComponent, - // formly - InputTypeComponent, - FormlyWrapperFormFieldComponent, - RepeatTypeComponent, - FormlyWrapperInputSerialNumber, + FormlyCheckBoxHyperlinkWrapperComponent, + FormlyFieldCheckboxWithImageComponent, + FormlyFieldModalComponent, + FormlyFieldMultiStepComponent, + FormlyFieldRadioWithImageComponent, + FormlyFieldWithLoadingAnimationComponent, FormlySelectFieldExtendedWrapperComponent, FormlySelectFieldModalComponent, - FormlyFieldRadioWithImageComponent, - FormlyCheckBoxHyperlinkWrapperComponent, FormlyWrapperDefaultValueWithCasesComponent, - FormlyFieldModalComponent, + FormlyWrapperFormFieldComponent, + FormlyWrapperInputSerialNumber, + HeaderComponent, + InputTypeComponent, PanelWrapperComponent, - FormlyFieldWithLoadingAnimationComponent, - FormlyFieldCheckboxWithImageComponent, - FormlyFieldMultiStepComponent, + PercentageBarComponent, + RepeatTypeComponent, ], exports: [ - // modules + AppHeaderComponent, BrowserAnimationsModule, - NgChartsModule, + ChartOptionsComponent, CommonModule, + ComponentsModule, DirectiveModule, + FormlyFieldWithLoadingAnimationComponent, FormlyIonicModule, FormlyModule, FormsModule, + HeaderComponent, + HistoryDataErrorModule, IonicModule, + MeterModule, + NgChartsModule, NgxSpinnerModule, + PercentageBarComponent, + PipeModule, ReactiveFormsModule, RouterModule, TranslateModule, - PipeModule, - ComponentsModule, - MeterModule, - HistoryDataErrorModule, - // components - ChartOptionsComponent, - HeaderComponent, - PercentageBarComponent, - FormlyFieldWithLoadingAnimationComponent, ], providers: [ AppStateTracker, diff --git a/ui/src/app/shared/utils/array/array.utils.ts b/ui/src/app/shared/utils/array/array.utils.ts index 6026afa4579..037c4c4c4f6 100644 --- a/ui/src/app/shared/utils/array/array.utils.ts +++ b/ui/src/app/shared/utils/array/array.utils.ts @@ -19,10 +19,10 @@ export namespace ArrayUtils { /** * Finds the biggest number in a array. * null, undefined, NaN, +-Infinity are ignored in this method. - * - * @param arr the arr - * @returns a number if arr not empty, else null - */ + * + * @param arr the arr + * @returns a number if arr not empty, else null + */ export function findBiggestNumber(arr: (number | null | undefined)[]): number | null { const filteredArr = arr.filter((el): el is number => Number.isFinite(el)); return filteredArr.length > 0 ? Math.max(...filteredArr) : null; @@ -48,4 +48,15 @@ export namespace ArrayUtils { return aVal.localeCompare(bVal, undefined, { sensitivity: "accent" }); }); } + + /** + * Checks if array contains at least one of the passed strings + * + * @param strings the strings + * @param arr the array + * @returns true if arr contains at least one of the strings + */ + export function containsStrings(strings: (number | string | null)[], arr: (number | string | null)[]): boolean { + return arr.filter(el => strings.includes(el)).length > 0; + } } diff --git a/ui/src/app/shared/utils/object/object.utils.ts b/ui/src/app/shared/utils/object/object.utils.ts new file mode 100644 index 00000000000..043a0fdb465 --- /dev/null +++ b/ui/src/app/shared/utils/object/object.utils.ts @@ -0,0 +1,8 @@ +export class ObjectUtils { + + public static excludeProperties, K extends keyof T>(obj: T, keys: K[]): Omit { + const result = { ...obj }; + keys.forEach(key => delete result[key]); + return result; + } +} diff --git a/ui/src/app/user/user.component.html b/ui/src/app/user/user.component.html index 08f52cc3ffc..69df7a384f5 100644 --- a/ui/src/app/user/user.component.html +++ b/ui/src/app/user/user.component.html @@ -1,4 +1,3 @@ -
    diff --git a/ui/src/assets/i18n/de.json b/ui/src/assets/i18n/de.json index e105762966d..954c76a43ce 100644 --- a/ui/src/assets/i18n/de.json +++ b/ui/src/assets/i18n/de.json @@ -237,14 +237,14 @@ "CONTROL_MODE_DESCRIPTION": { "CHARGE_CONSUMPTION": "" }, - "CHARGE_FROM_GRID_ACTIVATE": "Aktive Beladung aus dem Netz aktivieren (BETA-Test)", + "CHARGE_FROM_GRID_ACTIVATE": "Aktive Beladung aus dem Netz aktivieren", "PRICE": "Aktueller Bezugsstrompreis", "STATE": { "DELAY_DISCHARGE": "Entladung verzögert", "BALANCING": "Eigenverbrauchsoptimierung", "CHARGE_GRID": "Beladung aus dem Netz freigegeben" }, - "CHART_TITLE": "Aktueller Fahrplan (BETA-Test)", + "CHART_TITLE": "Aktueller Fahrplan", "CHART_WARNING_NOTE": "Die Grafik zeigt die vergangenen drei Stunden, sowie die zukünftig geplante Betriebsweise für den Zeitraum, für den die dynamischen Netzbezugspreise zur Verfügung stehen. Bitte beachten Sie, dass der Fahrplan kontinuierlich neu berechnet wird und sich somit im Tagesverlauf ändern kann.", "POWER_SOC_CHART_TITLE": "Vorhersagen (Nur für Admins)" }, @@ -315,6 +315,7 @@ "nov": "Nov", "dec": "Dez", "activeDuration": "Einschaltdauer", + "ACTIVE_DURATION_WITH_LEVEL": "Einschaltdauer Level {{level}}", "CURRENT": "Strom", "VOLTAGE": "Spannung" }, diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json index 58ca563831c..0ce4b7372fb 100644 --- a/ui/src/assets/i18n/en.json +++ b/ui/src/assets/i18n/en.json @@ -238,14 +238,14 @@ "CONTROL_MODE_DESCRIPTION": { "CHARGE_CONSUMPTION": "" }, - "CHARGE_FROM_GRID_ACTIVATE": "Activate charge from the grid (BETA test)", + "CHARGE_FROM_GRID_ACTIVATE": "Activate charge from the grid", "PRICE": "Current price", "STATE": { "DELAY_DISCHARGE": "Delayed discharge", "BALANCING": "Self-Consumption optmization", "CHARGE_GRID": "Charge from grid allowed" }, - "CHART_TITLE": "Planned Schedule (BETA test)", + "CHART_TITLE": "Planned Schedule", "CHART_WARNING_NOTE": "The graphic shows the past three hours as well as the future planned operating mode for the period for which the dynamic grid purchase prices are available. Please note that the planned schedule is subject to continuous recalculation and may change throughout the day.", "POWER_SOC_CHART_TITLE": "Forecasts (Only for Admins)" }, @@ -313,7 +313,8 @@ "oct": "Oxt", "nov": "Nov", "dec": "Dec", - "activeDuration": "active duration", + "activeDuration": "Active duration", + "ACTIVE_DURATION_WITH_LEVEL": "Active duration level {{level}}", "CURRENT_AND_VOLTAGE": "Current & Voltage", "CURRENT": "Current", "VOLTAGE": "Voltage", From 25febfa4dd10d028fc86a4acc83f17e6e430ca5a Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sat, 19 Oct 2024 23:20:11 +0200 Subject: [PATCH 07/19] Update Checkstyle to 10.18.2 (#2847) - Update checkstyle to 10.18.2 - See https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.18.2 for details. - Unfortunately Checkstyle by default requires no newline after

    in JavaDoc, whereas Eclipse IDE automatically adds one by default. This is PR disables this check. - Change `JavadocParagraph` to `allowNewlineParagraph` - Fix ControllerEssGridOptimizedChargeImplTest - Fixes Checkstyle `Checks that overloaded methods are grouped together. Overloaded methods have the samename but different signatures where the signature can differ by the number ofinput parameters or type of input parameters or both. ` --- build.gradle | 2 +- cnf/checkstyle.xml | 18 +-- ...trollerEssGridOptimizedChargeImplTest.java | 148 +++++++++--------- 3 files changed, 83 insertions(+), 85 deletions(-) diff --git a/build.gradle b/build.gradle index 82249fb14f2..ad224209fd9 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ subprojects { } checkstyle { - toolVersion = '10.11.0' + toolVersion = '10.18.2' configFile = file("${rootDir}/cnf/checkstyle.xml") maxWarnings = 0 ignoreFailures = false diff --git a/cnf/checkstyle.xml b/cnf/checkstyle.xml index ad22318becf..6ed4ad0febb 100644 --- a/cnf/checkstyle.xml +++ b/cnf/checkstyle.xml @@ -23,8 +23,8 @@ - + @@ -47,9 +47,9 @@ + - @@ -107,8 +107,8 @@ - + @@ -144,9 +144,9 @@ + - @@ -179,12 +179,10 @@ - - - + - + @@ -206,15 +204,15 @@ - + - + diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java index 7ac9440a6ba..862b0955d56 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImplTest.java @@ -1444,80 +1444,6 @@ private void testLogic(String description, Integer[] productionPrediction, Integ }); } - private DelayChargeResultState testOneDay(String testDescription, Integer[] productionPrediction, - Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity, - int maxApparentPower, int allowedChargePower, DelayChargeRiskLevel riskLevel, Integer[] productionActual, - Integer[] consumptionActual, float resultBuffer) { - DelayChargeResultState resultState; - DelayChargeResult newLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription, - productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower, - allowedChargePower, riskLevel, productionActual, consumptionActual, false); - - DelayChargeResult oldLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription, - productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower, - allowedChargePower, riskLevel, productionActual, consumptionActual, true); - - if (newLogic.getFinalSoc() + resultBuffer < oldLogic.getFinalSoc()) { - resultState = DelayChargeResultState.WARNING; - } else if (newLogic.getFinalSoc() - resultBuffer > oldLogic.getFinalSoc()) { - resultState = DelayChargeResultState.PERFECT; - } else { - resultState = DelayChargeResultState.OK; - } - - float unefficientEnergy = Math - .round(newLogic.getChargedEnergyWithLowPower() / newLogic.getChargedEnergy() * 1000) / 10.0f; - float unefficientEnergyOld = Math - .round(oldLogic.getChargedEnergyWithLowPower() / oldLogic.getChargedEnergy() * 1000) / 10.0f; - System.out.println(resultState.text + "\t" + testDescription + " \t(New: " - + Math.round(newLogic.getFinalSoc() * 100) / 100.0 + " | Old: " - + Math.round(oldLogic.getFinalSoc() * 100) / 100.0 + ") \t Energy: (New: " - + newLogic.getChargedEnergy() + "[" + newLogic.getChargedEnergyWithLowPower() + " -> " - + unefficientEnergy + "%] | Old: " + oldLogic.getChargedEnergy() + "[" - + oldLogic.getChargedEnergyWithLowPower() + " -> " + unefficientEnergyOld + "%])"); - - // fail("New logic results in a lower SoC (New: " + newLogic.getFinalSoc() + " | - // Old: "+ oldLogic.getFinalSoc() + ") - " + testDescription); - return resultState; - } - - private static class DelayChargeResult { - - private float finalSoc; - private float chargedEnergy; - private float chargedEnergyWithLowPower; - - public DelayChargeResult(float finalSoc, float chargedEnergy, float chargedEnergyWithLowPower) { - this.finalSoc = finalSoc; - this.chargedEnergy = chargedEnergy; - this.chargedEnergyWithLowPower = chargedEnergyWithLowPower; - } - - public float getFinalSoc() { - return this.finalSoc; - } - - public float getChargedEnergy() { - return this.chargedEnergy; - } - - public float getChargedEnergyWithLowPower() { - return this.chargedEnergyWithLowPower; - } - } - - private static enum DelayChargeResultState { - OK("OK - SoC as bevore"), // - WARNING("WARNING - Lower SoC"), // - PERFECT("PERFECT - Higher SoC"); - - private String text; - - DelayChargeResultState(String text) { - this.text = text; - } - } - @SuppressWarnings("deprecation") private static DelayChargeResult testOneDay(String testDescription, Integer[] productionPrediction, Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity, @@ -1658,6 +1584,80 @@ private static DelayChargeResult testOneDay(String testDescription, Integer[] pr return new DelayChargeResult(socFloat, totoalActivePower * 0.25f, totoalActivePowerLessEfficiency * 0.25f); } + private DelayChargeResultState testOneDay(String testDescription, Integer[] productionPrediction, + Integer[] consumptionPrediction, int soc, Optional targetMinuteOpt, int capacity, + int maxApparentPower, int allowedChargePower, DelayChargeRiskLevel riskLevel, Integer[] productionActual, + Integer[] consumptionActual, float resultBuffer) { + DelayChargeResultState resultState; + DelayChargeResult newLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription, + productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower, + allowedChargePower, riskLevel, productionActual, consumptionActual, false); + + DelayChargeResult oldLogic = ControllerEssGridOptimizedChargeImplTest.testOneDay(testDescription, + productionPrediction, consumptionPrediction, soc, targetMinuteOpt, capacity, maxApparentPower, + allowedChargePower, riskLevel, productionActual, consumptionActual, true); + + if (newLogic.getFinalSoc() + resultBuffer < oldLogic.getFinalSoc()) { + resultState = DelayChargeResultState.WARNING; + } else if (newLogic.getFinalSoc() - resultBuffer > oldLogic.getFinalSoc()) { + resultState = DelayChargeResultState.PERFECT; + } else { + resultState = DelayChargeResultState.OK; + } + + float unefficientEnergy = Math + .round(newLogic.getChargedEnergyWithLowPower() / newLogic.getChargedEnergy() * 1000) / 10.0f; + float unefficientEnergyOld = Math + .round(oldLogic.getChargedEnergyWithLowPower() / oldLogic.getChargedEnergy() * 1000) / 10.0f; + System.out.println(resultState.text + "\t" + testDescription + " \t(New: " + + Math.round(newLogic.getFinalSoc() * 100) / 100.0 + " | Old: " + + Math.round(oldLogic.getFinalSoc() * 100) / 100.0 + ") \t Energy: (New: " + + newLogic.getChargedEnergy() + "[" + newLogic.getChargedEnergyWithLowPower() + " -> " + + unefficientEnergy + "%] | Old: " + oldLogic.getChargedEnergy() + "[" + + oldLogic.getChargedEnergyWithLowPower() + " -> " + unefficientEnergyOld + "%])"); + + // fail("New logic results in a lower SoC (New: " + newLogic.getFinalSoc() + " | + // Old: "+ oldLogic.getFinalSoc() + ") - " + testDescription); + return resultState; + } + + private static class DelayChargeResult { + + private float finalSoc; + private float chargedEnergy; + private float chargedEnergyWithLowPower; + + public DelayChargeResult(float finalSoc, float chargedEnergy, float chargedEnergyWithLowPower) { + this.finalSoc = finalSoc; + this.chargedEnergy = chargedEnergy; + this.chargedEnergyWithLowPower = chargedEnergyWithLowPower; + } + + public float getFinalSoc() { + return this.finalSoc; + } + + public float getChargedEnergy() { + return this.chargedEnergy; + } + + public float getChargedEnergyWithLowPower() { + return this.chargedEnergyWithLowPower; + } + } + + private static enum DelayChargeResultState { + OK("OK - SoC as bevore"), // + WARNING("WARNING - Lower SoC"), // + PERFECT("PERFECT - Higher SoC"); + + private String text; + + DelayChargeResultState(String text) { + this.text = text; + } + } + @Test public void calculateAvailEnergy_test() throws Exception { final var clock = new TimeLeapClock(Instant.parse("2020-01-01T08:00:00.00Z"), ZoneOffset.UTC); From 1949371e6de3d73807fc959edf05e4b69aedb5d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Oct 2024 23:35:47 +0200 Subject: [PATCH 08/19] Build(deps): Bump org.jetbrains.kotlin:kotlin-osgi-bundle from 2.0.20 to 2.0.21 in /cnf (#2840) * Build(deps): Bump org.jetbrains.kotlin:kotlin-osgi-bundle in /cnf Bumps org.jetbrains.kotlin:kotlin-osgi-bundle from 2.0.20 to 2.0.21. --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-osgi-bundle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 2 +- io.openems.backend.application/BackendApp.bndrun | 2 +- io.openems.edge.application/EdgeApp.bndrun | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index dca7896bc3f..54b6fdc6f82 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -329,7 +329,7 @@ org.jetbrains.kotlin kotlin-osgi-bundle - 2.0.20 + 2.0.21 org.jetbrains.kotlinx diff --git a/io.openems.backend.application/BackendApp.bndrun b/io.openems.backend.application/BackendApp.bndrun index e210938f4fd..e02d41046c8 100644 --- a/io.openems.backend.application/BackendApp.bndrun +++ b/io.openems.backend.application/BackendApp.bndrun @@ -115,7 +115,7 @@ org.apache.felix.scr;version='[2.2.12,2.2.13)',\ org.apache.felix.webconsole;version='[5.0.8,5.0.9)',\ org.apache.felix.webconsole.plugins.ds;version='[2.3.0,2.3.1)',\ - org.jetbrains.kotlin.osgi-bundle;version='[2.0.20,2.0.21)',\ + org.jetbrains.kotlin.osgi-bundle;version='[2.0.21,2.0.22)',\ org.jsr-305;version='[3.0.2,3.0.3)',\ org.ops4j.pax.logging.pax-logging-api;version='[2.2.1,2.2.2)',\ org.ops4j.pax.logging.pax-logging-log4j2;version='[2.2.1,2.2.2)',\ diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index fe957a9b082..3a1158ea4c1 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -418,7 +418,7 @@ org.eclipse.jetty.io;version='[9.4.28,9.4.29)',\ org.eclipse.jetty.util;version='[9.4.28,9.4.29)',\ org.eclipse.paho.mqttv5.client;version='[1.2.5,1.2.6)',\ - org.jetbrains.kotlin.osgi-bundle;version='[2.0.20,2.0.21)',\ + org.jetbrains.kotlin.osgi-bundle;version='[2.0.21,2.0.22)',\ org.jsoup;version='[1.18.1,1.18.2)',\ org.jsr-305;version='[3.0.2,3.0.3)',\ org.openmuc.jmbus;version='[3.3.0,3.3.1)',\ From 40b3e5d5bc0a30a38bf76a63c2c246b9da629132 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Sun, 20 Oct 2024 00:24:02 +0200 Subject: [PATCH 09/19] Add common dependabot configuration --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fe2414bdf30..7e89f85d2cc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,6 +14,9 @@ updates: patterns: - "org.dhatim:fastexcel" - "org.dhatim:fastexcel-reader" + bouncycastle: + patterns: + - "org.bouncycastle:*" - package-ecosystem: npm directory: "/ui" From 58cf33d634e87ab56c27abd496f41031dd2970b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Oct 2024 00:31:59 +0200 Subject: [PATCH 10/19] Build(deps): Bump org.bouncycastle:bcpkix-jdk15to18 from 1.77 to 1.78.1 in /cnf (#2841) * Build(deps): Bump org.bouncycastle:bcpkix-jdk15to18 in /cnf Bumps [org.bouncycastle:bcpkix-jdk15to18](https://github.com/bcgit/bc-java) from 1.77 to 1.78.1. - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcpkix-jdk15to18 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Switch dependency to `jdk18on` (where `18` stands for JDK 1.8) * Update bndrun --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Feilmeier --- cnf/pom.xml | 12 ++++++++++-- io.openems.edge.application/EdgeApp.bndrun | 6 +++--- io.openems.edge.controller.api.mqtt/bnd.bnd | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/cnf/pom.xml b/cnf/pom.xml index 54b6fdc6f82..4445a228051 100644 --- a/cnf/pom.xml +++ b/cnf/pom.xml @@ -281,9 +281,17 @@ + org.bouncycastle - bcpkix-jdk15to18 - 1.77 + bcpkix-jdk18on + 1.78.1 + + + + + org.bouncycastle + bcprov-jdk18on + 1.78.1 org.dhatim diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 3a1158ea4c1..4dfeab59531 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -194,9 +194,9 @@ -runbundles: \ Java-WebSocket;version='[1.5.4,1.5.5)',\ - bcpkix;version='[1.77.0,1.77.1)',\ - bcprov;version='[1.77.0,1.77.1)',\ - bcutil;version='[1.77.0,1.77.1)',\ + bcpkix;version='[1.78.1,1.78.2)',\ + bcprov;version='[1.78.1,1.78.2)',\ + bcutil;version='[1.78.1,1.78.2)',\ com.fasterxml.aalto-xml;version='[1.3.3,1.3.4)',\ com.fazecast.jSerialComm;version='[2.10.4,2.10.5)',\ com.ghgande.j2mod;version='[3.2.1,3.2.2)',\ diff --git a/io.openems.edge.controller.api.mqtt/bnd.bnd b/io.openems.edge.controller.api.mqtt/bnd.bnd index 816ceaf6133..880e35262f0 100644 --- a/io.openems.edge.controller.api.mqtt/bnd.bnd +++ b/io.openems.edge.controller.api.mqtt/bnd.bnd @@ -5,8 +5,8 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ - bcpkix;version='1.77',\ - bcprov;version='1.77',\ + bcpkix;version='1.78.1',\ + bcprov;version='1.78.1',\ io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ From 34b5e3a9cde6a49b5c9fab2112781062c8e7fd43 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Tue, 22 Oct 2024 09:11:18 +0200 Subject: [PATCH 11/19] Energy Scheduler - next generation of Time-of-Use optimization (#2789) - Introduce generically usable EnergyScheduleHandler (ESH) for executing energy simulations and applying schedules - Provide nice debugLog output and channel "SimulationsPerQuarter" to detect performance issues - Use Cache for cost of Genotypes - **Add config property "Version" to be able to switch between old (only ESS, but fast and well tested) and new (generic ESH but slower) implementation.** - Attention: be sure to set EnergyScheduler (`Core.Energy`) and `Controller.Ess.Time-Of-Use-Tariff` to the same Version! - Introduce new implementation of `EnergyFlow` that uses linear constraint validation and optimization - Implement ESHs for - `Controller.Ess.Time-Of-Use-Tariff`, - `Controller.Ess.EmergencyCapacityReserve`, - `Controller.Ess.LimitTotalDischarge` and - `Controller.Ess.GridOptimizedCharge` (MANUAL only) - Old implementations are moved to `v1` packages and marked @Deprecated and will be removed in one of the next versions of OpenEMS. Unfortunately right now this leads to some mixed code. --- cnf/build.bnd | 1 + .../io/openems/edge/common/sum/DummySum.java | 44 ++ .../openems/edge/common/test/TestUtils.java | 23 + .../openems/edge/common/type/TypeUtils.java | 48 +- .../openems/edge/common/sum/DummySumTest.java | 22 + .../edge/common/type/TypeUtilsTest.java | 72 +- .../bnd.bnd | 1 + ...rollerEssEmergencyCapacityReserveImpl.java | 41 +- .../bnd.bnd | 3 +- .../ControllerEssFixActivePowerImpl.java | 54 +- .../ess/fixactivepower/package-info.java | 3 + .../bnd.bnd | 4 +- .../ControllerEssGridOptimizedChargeImpl.java | 108 ++- .../ess/gridoptimizedcharge/package-info.java | 3 + .../EnergyScheduleHandlerTest.java | 40 ++ .../bnd.bnd | 3 +- .../ControllerEssLimitTotalDischargeImpl.java | 34 +- .../bnd.bnd | 3 + .../ess/timeofusetariff/Config.java | 7 +- .../TimeOfUseTariffController.java | 15 +- .../TimeOfUseTariffControllerImpl.java | 208 +++++- .../controller/ess/timeofusetariff/Utils.java | 154 ++-- .../jsonrpc/GetScheduleRequest.java | 2 +- .../jsonrpc/GetScheduleResponse.java | 230 ++++++ .../timeofusetariff/jsonrpc/package-info.java | 3 + .../v1/EnergyScheduleHandlerV1.java | 88 +++ .../ess/timeofusetariff/v1/UtilsV1.java | 132 ++++ .../ess/timeofusetariff/v1/package-info.java | 3 + .../ess/timeofusetariff/MyConfig.java | 12 + .../TimeOfUseTariffControllerImplTest.java | 37 +- .../ess/timeofusetariff/UtilsTest.java | 109 ++- .../jsonrpc/GetScheduleResponseTest.java | 242 +++++++ .../timeofusetariff/jsonrpc}/TestData.java | 44 +- .../ess/timeofusetariff/v1/UtilsV1Test.java | 101 +++ io.openems.edge.energy.api/bnd.bnd | 1 + .../edge/energy/api/EnergyConstants.java | 15 + .../edge/energy/api/EnergySchedulable.java | 4 +- .../energy/api/EnergyScheduleHandler.java | 489 +++++++++++-- .../edge/energy/api/EnergyScheduler.java | 19 +- .../openems/edge/energy/api/EnergyUtils.java | 125 ++++ .../io/openems/edge/energy/api/Version.java | 20 + .../energy/api/simulation/Coefficient.java | 36 + .../energy/api/simulation/EnergyFlow.java | 657 ++++++++++++++++++ .../simulation/GlobalSimulationsContext.java | 325 +++++++++ .../api/simulation/OneSimulationContext.java | 59 ++ .../energy/api/simulation/package-info.java | 3 + .../test/AbstractDummyEnergySchedulable.java | 16 + .../api/test/DummyEnergySchedulable.java | 37 + .../test/DummyGlobalSimulationsContext.java | 99 +++ .../edge/energy/api/test/package-info.java | 3 + .../edge/energy/api/EnergyUtilsTest.java | 55 ++ .../GlobalSimulationsContextTest.java | 95 +++ io.openems.edge.energy/bnd.bnd | 13 +- .../src/io/openems/edge/energy/Config.java | 5 + .../edge/energy/EnergySchedulerImpl.java | 139 +++- .../io/openems/edge/energy/LogVerbosity.java | 2 +- .../edge/energy/optimizer/Optimizer.java | 279 +++++--- .../edge/energy/optimizer/QuickSchedules.java | 217 ++++++ .../energy/optimizer/SimulationResult.java | 177 +++++ .../edge/energy/optimizer/Simulator.java | 322 ++++++--- .../openems/edge/energy/optimizer/Utils.java | 503 ++------------ .../{ => v1}/jsonrpc/GetScheduleResponse.java | 7 +- .../optimizer/EnergyFlowV1.java} | 35 +- .../optimizer/GlobalContextV1.java} | 27 +- .../optimizer/InitialPopulationV1Utils.java} | 17 +- .../edge/energy/v1/optimizer/OptimizerV1.java | 159 +++++ .../optimizer/ParamsUtilsV1.java} | 17 +- .../optimizer/ParamsV1.java} | 15 +- .../{ => v1}/optimizer/ScheduleDatas.java | 21 +- .../edge/energy/v1/optimizer/SimulatorV1.java | 178 +++++ .../edge/energy/v1/optimizer/UtilsV1.java | 382 ++++++++++ .../edge/energy/EnergySchedulerImplTest.java | 104 +-- .../test/io/openems/edge/energy/MyConfig.java | 14 +- .../energy/api/simulation/EnergyFlowTest.java | 468 +++++++++++++ .../edge/energy/optimizer/OptimizerTest.java | 64 +- .../energy/optimizer/QuickSchedulesTest.java | 65 ++ .../optimizer/SimulationResultTest.java | 56 ++ .../edge/energy/optimizer/SimulatorTest.java | 236 ++----- .../edge/energy/optimizer/TestData.java | 67 ++ .../edge/energy/optimizer/UtilsTest.java | 334 +-------- .../edge/energy/optimizer/app/AppUtils.java | 108 +++ .../app/EnergyPerformanceTestApp.java | 44 ++ .../optimizer/app/RunOptimizerFromLogApp.java | 90 +++ .../energy/v1/EnergySchedulerImplTest.java | 127 ++++ .../jsonrpc/GetScheduleResponseTest.java | 7 +- .../optimizer/EnergyFlowV1Test.java} | 77 +- .../InitialPopulationV1UtilsTest.java} | 38 +- .../optimizer/IntegrationTestsV1.java} | 21 +- .../energy/v1/optimizer/OptimizerV1Test.java | 22 + .../optimizer/ParamsUtilsV1Test.java} | 9 +- .../optimizer/RunOptimizerFromLogV1App.java} | 15 +- .../optimizer/ScheduleDatasV1Test.java} | 31 +- .../energy/v1/optimizer/SimulatorV1Test.java | 208 ++++++ .../edge/energy/v1/optimizer/TestDataV1.java | 40 ++ .../edge/energy/v1/optimizer/UtilsV1Test.java | 304 ++++++++ .../ess/test/DummyManagedSymmetricEss.java | 9 + .../EssGenericManagedSymmetricImpl.java | 8 + .../api/test/AbstractDummyScheduler.java | 15 + .../scheduler/api/test/DummyScheduler.java | 46 ++ .../edge/scheduler/api/test/package-info.java | 3 + 100 files changed, 7502 insertions(+), 1595 deletions(-) create mode 100644 io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java create mode 100644 io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java create mode 100644 io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java create mode 100644 io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java rename {io.openems.edge.energy/src/io/openems/edge/energy => io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff}/jsonrpc/GetScheduleRequest.java (94%) create mode 100644 io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java create mode 100644 io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/package-info.java create mode 100644 io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/EnergyScheduleHandlerV1.java create mode 100644 io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1.java create mode 100644 io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/package-info.java create mode 100644 io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponseTest.java rename {io.openems.edge.energy/test/io/openems/edge/energy => io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc}/TestData.java (61%) create mode 100644 io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1Test.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyConstants.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyUtils.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/Version.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/Coefficient.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/EnergyFlow.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/GlobalSimulationsContext.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/OneSimulationContext.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/package-info.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/AbstractDummyEnergySchedulable.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyEnergySchedulable.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyGlobalSimulationsContext.java create mode 100644 io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/package-info.java create mode 100644 io.openems.edge.energy.api/test/io/openems/edge/energy/api/EnergyUtilsTest.java create mode 100644 io.openems.edge.energy.api/test/io/openems/edge/energy/api/simulation/GlobalSimulationsContextTest.java create mode 100644 io.openems.edge.energy/src/io/openems/edge/energy/optimizer/QuickSchedules.java create mode 100644 io.openems.edge.energy/src/io/openems/edge/energy/optimizer/SimulationResult.java rename io.openems.edge.energy/src/io/openems/edge/energy/{ => v1}/jsonrpc/GetScheduleResponse.java (90%) rename io.openems.edge.energy/src/io/openems/edge/energy/{optimizer/EnergyFlow.java => v1/optimizer/EnergyFlowV1.java} (76%) rename io.openems.edge.energy/src/io/openems/edge/energy/{optimizer/GlobalContext.java => v1/optimizer/GlobalContextV1.java} (67%) rename io.openems.edge.energy/src/io/openems/edge/energy/{optimizer/InitialPopulationUtils.java => v1/optimizer/InitialPopulationV1Utils.java} (91%) create mode 100644 io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java rename io.openems.edge.energy/src/io/openems/edge/energy/{optimizer/ParamsUtils.java => v1/optimizer/ParamsUtilsV1.java} (88%) rename io.openems.edge.energy/src/io/openems/edge/energy/{optimizer/Params.java => v1/optimizer/ParamsV1.java} (94%) rename io.openems.edge.energy/src/io/openems/edge/energy/{ => v1}/optimizer/ScheduleDatas.java (94%) create mode 100644 io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/SimulatorV1.java create mode 100644 io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/UtilsV1.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/api/simulation/EnergyFlowTest.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/optimizer/QuickSchedulesTest.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulationResultTest.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/optimizer/TestData.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/AppUtils.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/EnergyPerformanceTestApp.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/RunOptimizerFromLogApp.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/v1/EnergySchedulerImplTest.java rename io.openems.edge.energy/test/io/openems/edge/energy/{ => v1}/jsonrpc/GetScheduleResponseTest.java (86%) rename io.openems.edge.energy/test/io/openems/edge/energy/{optimizer/EnergyFlowTest.java => v1/optimizer/EnergyFlowV1Test.java} (74%) rename io.openems.edge.energy/test/io/openems/edge/energy/{optimizer/InitialPopulationUtilsTest.java => v1/optimizer/InitialPopulationV1UtilsTest.java} (70%) rename io.openems.edge.energy/test/io/openems/edge/energy/{optimizer/IntegrationTests.java => v1/optimizer/IntegrationTestsV1.java} (80%) create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/OptimizerV1Test.java rename io.openems.edge.energy/test/io/openems/edge/energy/{optimizer/ParamsUtilsTest.java => v1/optimizer/ParamsUtilsV1Test.java} (83%) rename io.openems.edge.energy/test/io/openems/edge/energy/{optimizer/RunOptimizerFromLogApp.java => v1/optimizer/RunOptimizerFromLogV1App.java} (66%) rename io.openems.edge.energy/test/io/openems/edge/energy/{optimizer/ScheduleDatasTest.java => v1/optimizer/ScheduleDatasV1Test.java} (85%) create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/SimulatorV1Test.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/TestDataV1.java create mode 100644 io.openems.edge.energy/test/io/openems/edge/energy/v1/optimizer/UtilsV1Test.java create mode 100644 io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/AbstractDummyScheduler.java create mode 100644 io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/DummyScheduler.java create mode 100644 io.openems.edge.scheduler.api/src/io/openems/edge/scheduler/api/test/package-info.java diff --git a/cnf/build.bnd b/cnf/build.bnd index 00cba399ba6..dbc9605619d 100644 --- a/cnf/build.bnd +++ b/cnf/build.bnd @@ -41,6 +41,7 @@ buildpath: \ org.osgi.service.metatype.annotations;version='1.4.1',\ org.osgi.util.promise;version='1.2.0',\ com.google.guava;version='33.3.1.jre',\ + com.google.guava.failureaccess;version='1.0.2',\ com.google.gson;version='2.11.0',\ testpath: \ diff --git a/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java b/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java index 41a246d6b33..2a2b9bd7568 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java +++ b/io.openems.edge.common/src/io/openems/edge/common/sum/DummySum.java @@ -49,4 +49,48 @@ public DummySum withGridActivePower(int value) { return this.self(); } + /** + * Set {@link Sum.ChannelId#ESS_CAPACITY}. + * + * @param value the value + * @return myself + */ + public DummySum withEssCapacity(int value) { + withValue(this, Sum.ChannelId.ESS_CAPACITY, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#ESS_SOC}. + * + * @param value the value + * @return myself + */ + public DummySum withEssSoc(int value) { + withValue(this, Sum.ChannelId.ESS_SOC, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#ESS_MIN_DISCHARGE_POWER}. + * + * @param value the value + * @return myself + */ + public DummySum withEssMinDischargePower(int value) { + withValue(this, Sum.ChannelId.ESS_MIN_DISCHARGE_POWER, value); + return this.self(); + } + + /** + * Set {@link Sum.ChannelId#ESS_MAX_DISCHARGE_POWER}. + * + * @param value the value + * @return myself + */ + public DummySum withEssMaxDischargePower(int value) { + withValue(this, Sum.ChannelId.ESS_MAX_DISCHARGE_POWER, value); + return this.self(); + } + } diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java b/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java index f8f0e2e7a10..4cb54a3f37e 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/TestUtils.java @@ -3,10 +3,13 @@ import java.io.IOException; import java.net.ServerSocket; import java.time.Instant; +import java.util.function.BiFunction; +import java.util.function.Function; import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.channel.Channel; import io.openems.edge.common.channel.ChannelId; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; public class TestUtils { @@ -80,4 +83,24 @@ public static void withValue(Channel channel, Object value) { channel.setNextValue(value); channel.nextProcessImage(); } + + /** + * Helper to test a {@link #withValue(Channel, Object)} method in a JUnit test. + * + * @param the type of the {@link AbstractDummyOpenemsComponent} + * @param sut the actual system-under-test + * @param setter the getChannel getter method + * @param getter the withChannel setter method + */ + public static void testWithValue(T sut, BiFunction setter, Function> getter) { + var before = getter.apply(sut).get(); + if (before != null) { + throw new IllegalArgumentException("TestUtils.testWithValue() expected [null] got [" + before + "]"); + } + setter.apply(sut, 123); + var after = getter.apply(sut).get().intValue(); + if (after != 123) { + throw new IllegalArgumentException("TestUtils.testWithValue() expected [123] got [" + after + "]"); + } + } } diff --git a/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java b/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java index babada99a81..d43a8bc6716 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java +++ b/io.openems.edge.common/src/io/openems/edge/common/type/TypeUtils.java @@ -364,23 +364,15 @@ public static JsonElement getAsJson(OpenemsType type, Object originalValue) { return JsonNull.INSTANCE; } var value = TypeUtils.getAsType(type, originalValue); - switch (type) { - case BOOLEAN: - return new JsonPrimitive((Boolean) value ? 1 : 0); - case SHORT: - return new JsonPrimitive((Short) value); - case INTEGER: - return new JsonPrimitive((Integer) value); - case LONG: - return new JsonPrimitive((Long) value); - case FLOAT: - return new JsonPrimitive((Float) value); - case DOUBLE: - return new JsonPrimitive((Double) value); - case STRING: - return new JsonPrimitive((String) value); - } - throw new IllegalArgumentException("Converter for value [" + value + "] to JSON is not implemented."); + return switch (type) { + case BOOLEAN -> new JsonPrimitive((Boolean) value ? 1 : 0); + case SHORT -> new JsonPrimitive((Short) value); + case INTEGER -> new JsonPrimitive((Integer) value); + case LONG -> new JsonPrimitive((Long) value); + case FLOAT -> new JsonPrimitive((Float) value); + case DOUBLE -> new JsonPrimitive((Double) value); + case STRING -> new JsonPrimitive((String) value); + }; } /** @@ -427,6 +419,28 @@ public static Integer sum(Integer... values) { return result; } + /** + * Safely add Floats. If one of them is null it is considered '0'. If all of + * them are null, 'null' is returned. + * + * @param values the {@link Float} values + * @return the sum + */ + public static Float sum(Float... values) { + Float result = null; + for (Float value : values) { + if (value == null) { + continue; + } + if (result == null) { + result = value; + } else { + result += value; + } + } + return result; + } + /** * Safely add Longs. If one of them is null it is considered '0'. If all of them * are null, 'null' is returned. diff --git a/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java b/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java new file mode 100644 index 00000000000..8cb89d3a6d2 --- /dev/null +++ b/io.openems.edge.common/test/io/openems/edge/common/sum/DummySumTest.java @@ -0,0 +1,22 @@ +package io.openems.edge.common.sum; + +import static io.openems.edge.common.test.TestUtils.testWithValue; + +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsException; + +public class DummySumTest { + + @Test + public void test() throws OpenemsException { + final var sut = new DummySum(); + + testWithValue(sut, DummySum::withProductionAcActivePower, Sum::getProductionAcActivePower); + testWithValue(sut, DummySum::withGridActivePower, Sum::getGridActivePower); + testWithValue(sut, DummySum::withEssCapacity, Sum::getEssCapacity); + testWithValue(sut, DummySum::withEssSoc, Sum::getEssSoc); + testWithValue(sut, DummySum::withEssMinDischargePower, Sum::getEssMinDischargePower); + testWithValue(sut, DummySum::withEssMaxDischargePower, Sum::getEssMaxDischargePower); + } +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java b/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java index 83c76584a1c..f5ff84b47f3 100644 --- a/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java +++ b/io.openems.edge.common/test/io/openems/edge/common/type/TypeUtilsTest.java @@ -1,5 +1,15 @@ package io.openems.edge.common.type; +import static com.google.gson.JsonNull.INSTANCE; +import static io.openems.common.types.OpenemsType.BOOLEAN; +import static io.openems.common.types.OpenemsType.DOUBLE; +import static io.openems.common.types.OpenemsType.FLOAT; +import static io.openems.common.types.OpenemsType.INTEGER; +import static io.openems.common.types.OpenemsType.LONG; +import static io.openems.common.types.OpenemsType.SHORT; +import static io.openems.common.types.OpenemsType.STRING; +import static io.openems.edge.common.type.TypeUtils.getAsJson; +import static io.openems.edge.common.type.TypeUtils.sum; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -7,8 +17,9 @@ import org.junit.Test; +import com.google.gson.JsonPrimitive; + import io.openems.common.function.ThrowingRunnable; -import io.openems.common.types.OpenemsType; import io.openems.common.types.OptionsEnum; import io.openems.edge.common.channel.value.Value; @@ -81,8 +92,8 @@ public void testMin() { @Test public void testSumDouble() { - assertNull(TypeUtils.sum((Double) null, null)); - assertEquals(4.0, TypeUtils.sum(1.5, 2.5), 0.1); + assertNull(sum((Double) null, null)); + assertEquals(4.0, sum(1.5, 2.5), 0.1); } @Test @@ -269,6 +280,47 @@ public void testGetAsType() { } } + @Test + public void testGetAsJson() { + assertEquals(INSTANCE, getAsJson(INTEGER, null)); + assertEquals(new JsonPrimitive(0), getAsJson(BOOLEAN, false)); + assertEquals(new JsonPrimitive(1), getAsJson(BOOLEAN, true)); + assertEquals(new JsonPrimitive(123), getAsJson(SHORT, 123)); + assertEquals(new JsonPrimitive(234), getAsJson(INTEGER, 234)); + assertEquals(new JsonPrimitive(345), getAsJson(LONG, 345)); + assertEquals(new JsonPrimitive(45.6F), getAsJson(FLOAT, 45.6F)); + assertEquals(new JsonPrimitive(56.7), getAsJson(DOUBLE, 56.7)); + assertEquals(new JsonPrimitive("678"), getAsJson(STRING, "678")); + } + + @Test + public void sumInteger() { + assertEquals(6, sum(1, 2, 3).intValue()); + assertNull(sum((Integer) null)); + assertEquals(6, sum(1, null, 2, 3).intValue()); + } + + @Test + public void sumFloat() { + assertEquals(6F, sum(1F, 2F, 3F).floatValue(), 0.001F); + assertNull(sum((Float) null)); + assertEquals(6F, sum(1F, null, 2F, 3F).floatValue(), 0.001F); + } + + @Test + public void sumLong() { + assertEquals(6L, sum(1L, 2L, 3L).longValue()); + assertNull(sum((Long) null)); + assertEquals(6L, sum(1L, null, 2L, 3L).longValue()); + } + + @Test + public void sumDouble() { + assertEquals(6., sum(1., 2., 3.).doubleValue(), 0.001); + assertNull(sum((Double) null)); + assertEquals(6., sum(1., null, 2., 3.).doubleValue(), 0.001); + } + private static void assertException(ThrowingRunnable runnable) { try { runnable.run(); @@ -279,31 +331,31 @@ private static void assertException(ThrowingRunnable runnable) { } private static Boolean getAsBoolean(Object value) { - return TypeUtils.getAsType(OpenemsType.BOOLEAN, value); + return TypeUtils.getAsType(BOOLEAN, value); } private static Short getAsShort(Object value) { - return TypeUtils.getAsType(OpenemsType.SHORT, value); + return TypeUtils.getAsType(SHORT, value); } private static Integer getAsInteger(Object value) { - return TypeUtils.getAsType(OpenemsType.INTEGER, value); + return TypeUtils.getAsType(INTEGER, value); } private static Long getAsLong(Object value) { - return TypeUtils.getAsType(OpenemsType.LONG, value); + return TypeUtils.getAsType(LONG, value); } private static Float getAsFloat(Object value) { - return TypeUtils.getAsType(OpenemsType.FLOAT, value); + return TypeUtils.getAsType(FLOAT, value); } private static Double getAsDouble(Object value) { - return TypeUtils.getAsType(OpenemsType.DOUBLE, value); + return TypeUtils.getAsType(DOUBLE, value); } private static String getAsString(Object value) { - return TypeUtils.getAsType(OpenemsType.STRING, value); + return TypeUtils.getAsType(STRING, value); } private static enum MyOptionsEnum implements OptionsEnum { diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd b/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd index 3f814d45c0c..0a8bfd62f05 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/bnd.bnd @@ -8,6 +8,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ + io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ io.openems.edge.ess.generic,\ diff --git a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java index 35ba0a200aa..68e1ad4fc09 100644 --- a/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java +++ b/io.openems.edge.controller.ess.emergencycapacityreserve/src/io/openems/edge/controller/ess/emergencycapacityreserve/ControllerEssEmergencyCapacityReserveImpl.java @@ -1,6 +1,10 @@ package io.openems.edge.controller.ess.emergencycapacityreserve; +import static io.openems.edge.energy.api.EnergyUtils.socToEnergy; +import static java.lang.Math.max; + import java.util.OptionalInt; +import java.util.function.Supplier; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -25,6 +29,8 @@ import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.Context; import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine; import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.StateMachine.State; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.ess.api.ManagedSymmetricEss; @Designate(ocd = Config.class, factory = true) @@ -34,7 +40,7 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsComponent - implements ControllerEssEmergencyCapacityReserve, Controller, OpenemsComponent { + implements ControllerEssEmergencyCapacityReserve, EnergySchedulable, Controller, OpenemsComponent { /** Minimum reserve SoC value in [%]. */ private static final int reservSocMinValue = 5; @@ -42,6 +48,7 @@ public class ControllerEssEmergencyCapacityReserveImpl extends AbstractOpenemsCo private static final int reservSocMaxValue = 100; private final Logger log = LoggerFactory.getLogger(ControllerEssEmergencyCapacityReserveImpl.class); + private final EnergyScheduleHandler energyScheduleHandler; private final StateMachine stateMachine = new StateMachine(State.NO_LIMIT); private final RampFilter rampFilter = new RampFilter(); @@ -65,6 +72,10 @@ public ControllerEssEmergencyCapacityReserveImpl() { Controller.ChannelId.values(), // ControllerEssEmergencyCapacityReserve.ChannelId.values() // ); + this.energyScheduleHandler = buildEnergyScheduleHandler(// + () -> this.config.isReserveSocEnabled() // + ? this.config.reserveSoc() // + : null); } @Activate @@ -77,6 +88,7 @@ private void activate(ComponentContext context, Config config) { protected void modified(ComponentContext context, String id, String alias, boolean enabled) { super.modified(context, id, alias, enabled); this.updateConfig(this.config); + this.energyScheduleHandler.triggerReschedule(); } @Override @@ -191,4 +203,31 @@ private OptionalInt getLastValidSoc(IntegerReadChannel channel) { .mapToInt(Value::get) // .findFirst(); } + + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

    + * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param minSoc supplier for the configured minSoc + * @return a {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier minSoc) { + return EnergyScheduleHandler.of(// + simContext -> minSoc.get() == null // + ? null // + : socToEnergy(simContext.ess().totalEnergy(), minSoc.get()), // + (simContext, period, energyFlow, minEnergy) -> { + if (minEnergy != null) { + energyFlow.setEssMaxDischarge(max(0, simContext.getEssInitial() - minEnergy)); + } + }); + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } } diff --git a/io.openems.edge.controller.ess.fixactivepower/bnd.bnd b/io.openems.edge.controller.ess.fixactivepower/bnd.bnd index 9b0e7d57001..38c5412d005 100644 --- a/io.openems.edge.controller.ess.fixactivepower/bnd.bnd +++ b/io.openems.edge.controller.ess.fixactivepower/bnd.bnd @@ -8,8 +8,9 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ + io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ io.openems.edge.timedata.api,\ - + -testpath: \ ${testpath} diff --git a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java index ad3f5905e0e..2fe3a64a3e7 100644 --- a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java +++ b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/ControllerEssFixActivePowerImpl.java @@ -1,5 +1,9 @@ package io.openems.edge.controller.ess.fixactivepower; +import static io.openems.edge.energy.api.EnergyUtils.toEnergy; + +import java.util.function.Supplier; + import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -17,10 +21,13 @@ import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.controller.api.Controller; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.ess.api.HybridEss; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.api.PowerConstraint; import io.openems.edge.ess.power.api.Pwr; +import io.openems.edge.ess.power.api.Relationship; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.api.TimedataProvider; import io.openems.edge.timedata.api.utils.CalculateActiveTime; @@ -32,10 +39,11 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class ControllerEssFixActivePowerImpl extends AbstractOpenemsComponent - implements ControllerEssFixActivePower, Controller, OpenemsComponent, TimedataProvider { + implements ControllerEssFixActivePower, EnergySchedulable, Controller, OpenemsComponent, TimedataProvider { private final CalculateActiveTime calculateCumulatedActiveTime = new CalculateActiveTime(this, ControllerEssFixActivePower.ChannelId.CUMULATED_ACTIVE_TIME); + private final EnergyScheduleHandler energyScheduleHandler; @Reference private ConfigurationAdmin cm; @@ -54,6 +62,13 @@ public ControllerEssFixActivePowerImpl() { Controller.ChannelId.values(), // ControllerEssFixActivePower.ChannelId.values() // ); + this.energyScheduleHandler = buildEnergyScheduleHandler(() -> new EshContext(// + this.config.mode(), // + toEnergy(switch (this.config.phase()) { + case ALL -> this.config.power(); + case L1, L2, L3 -> this.config.power() * 3; + }), // + this.config.relationship())); } @Activate @@ -70,6 +85,7 @@ private void modified(ComponentContext context, Config config) { if (this.applyConfig(context, config)) { return; } + this.energyScheduleHandler.triggerReschedule(); } private boolean applyConfig(ComponentContext context, Config config) { @@ -137,4 +153,40 @@ protected static Integer getAcPower(ManagedSymmetricEss ess, HybridEssMode hybri public Timedata getTimedata() { return this.timedata; } + + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

    + * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param context a supplier for the configured {@link EshContext} + * @return a {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier context) { + return EnergyScheduleHandler.of(// + simContext -> context.get(), // + (simContext, period, energyFlow, ctrlContext) -> { + switch (ctrlContext.mode) { + case MANUAL_ON: + switch (ctrlContext.relationship) { + case EQUALS -> energyFlow.setEss(ctrlContext.energy); + case GREATER_OR_EQUALS -> energyFlow.setEssMaxCharge(-ctrlContext.energy); + case LESS_OR_EQUALS -> energyFlow.setEssMaxDischarge(ctrlContext.energy); + } + break; + case MANUAL_OFF: + break; + } + }); + } + + public static record EshContext(Mode mode, int energy, Relationship relationship) { + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } } \ No newline at end of file diff --git a/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java new file mode 100644 index 00000000000..aab27855be8 --- /dev/null +++ b/io.openems.edge.controller.ess.fixactivepower/src/io/openems/edge/controller/ess/fixactivepower/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.fixactivepower; diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd b/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd index ed3817a1757..d00c7cb673d 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/bnd.bnd @@ -8,10 +8,12 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ + io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ io.openems.edge.meter.api,\ io.openems.edge.predictor.api,\ io.openems.edge.timedata.api,\ -testpath: \ - ${testpath} + ${testpath},\ + org.apache.commons.math3,\ diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java index cb1ffc764a6..dc05f7914c4 100644 --- a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/ControllerEssGridOptimizedChargeImpl.java @@ -1,11 +1,19 @@ package io.openems.edge.controller.ess.gridoptimizedcharge; +import static java.util.stream.Collectors.groupingBy; + +import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZonedDateTime; import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collections; import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.function.Supplier; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -22,6 +30,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableSortedMap; + import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.edge.common.channel.IntegerReadChannel; @@ -33,6 +43,8 @@ import io.openems.edge.common.filter.RampFilter; import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.api.Controller; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.meter.api.ElectricityMeter; import io.openems.edge.predictor.api.manager.PredictorManager; @@ -46,8 +58,9 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.REQUIRE // ) -public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsComponent implements - ControllerEssGridOptimizedCharge, Controller, OpenemsComponent, TimedataProvider, ComponentManagerProvider { +public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsComponent + implements ControllerEssGridOptimizedCharge, EnergySchedulable, Controller, OpenemsComponent, TimedataProvider, + ComponentManagerProvider { /** * Buffer in watt taken into account in the calculation of the first and last @@ -58,6 +71,7 @@ public class ControllerEssGridOptimizedChargeImpl extends AbstractOpenemsCompone protected final RampFilter rampFilter = new RampFilter(); private final Logger log = LoggerFactory.getLogger(ControllerEssGridOptimizedChargeImpl.class); + private final EnergyScheduleHandler energyScheduleHandler; /* * Time counter for the important states @@ -107,6 +121,9 @@ public ControllerEssGridOptimizedChargeImpl() { Controller.ChannelId.values(), // ControllerEssGridOptimizedCharge.ChannelId.values() // ); + this.energyScheduleHandler = buildEnergyScheduleHandler(// + () -> this.config.mode(), // + () -> DelayCharge.parseTime(this.config.manualTargetTime())); } @Activate @@ -442,4 +459,91 @@ public Timedata getTimedata() { return this.timedata; } + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

    + * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param mode a supplier for the configured {@link Mode} + * @param manualTargetTime a supplier for the configured manualTargetTime + * @return a {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler buildEnergyScheduleHandler(Supplier mode, + Supplier manualTargetTime) { + return EnergyScheduleHandler.of(// + simContext -> { + // TODO try to reuse existing logic for parsing, calculating limits, etc.; for + // now this only works for current day and MANUAL mode + final var limits = ImmutableSortedMap.naturalOrder(); + final var periodsPerDay = simContext.periods().stream() // + .collect(groupingBy(p -> p.time().truncatedTo(ChronoUnit.DAYS))); + if (!periodsPerDay.isEmpty()) { + final var firstDayMignight = Collections.min(periodsPerDay.keySet()); + + for (var entry : periodsPerDay.entrySet()) { + // Find target time for this day + var midnight = entry.getKey(); // beginning of this day + var periods = entry.getValue(); // periods of this day + ZonedDateTime targetTime = switch (mode.get()) { + case OFF -> midnight; // Can not happen + case MANUAL -> midnight // + .withHour(manualTargetTime.get().getHour()) // + .withMinute(manualTargetTime.get().getMinute()); + case AUTOMATIC -> midnight; // TODO + }; + // Find first period with Production > Consumption + var firstExcessEnergyOpt = periods.stream() // + .filter(p -> p.production() > p.consumption()) // + .findFirst(); + if (firstExcessEnergyOpt.isEmpty() + || targetTime.isBefore(firstExcessEnergyOpt.get().time())) { + // Production exceeds Consumption never or too late on this day + // -> set no limit for this day + limits.put(midnight, OptionalInt.empty()); + continue; + } + var firstExcessEnergy = firstExcessEnergyOpt.get().time(); + + // Set no limit for early hours of the day + if (firstExcessEnergy.isAfter(midnight)) { + limits.put(midnight, OptionalInt.empty()); + } + + // Calculate actual charge limit + var noOfQuarters = (int) Duration.between(firstExcessEnergy, targetTime).toMinutes() / 15; + final var totalEnergy = midnight == firstDayMignight // + ? // use actual data for first day + simContext.ess().totalEnergy() - simContext.ess().currentEnergy() + : // assume full charge from second day + simContext.ess().totalEnergy(); + limits.put(firstExcessEnergy, OptionalInt.of(totalEnergy / noOfQuarters)); + + // No limit after targetTime + limits.put(targetTime, OptionalInt.empty()); + } + } + + return new EshContext(limits.build()); + }, // + (simContext, period, energyFlow, ctrlContext) -> { + var limitEntry = ctrlContext.limits.floorEntry(period.time()); + if (limitEntry == null) { + return; + } + var limit = limitEntry.getValue(); + if (limit.isPresent()) { + energyFlow.setEssMaxCharge(limit.getAsInt()); + } + }); + } + + private static record EshContext(ImmutableSortedMap limits) { + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } } diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java new file mode 100644 index 00000000000..9736f9da6be --- /dev/null +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/src/io/openems/edge/controller/ess/gridoptimizedcharge/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.gridoptimizedcharge; diff --git a/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java new file mode 100644 index 00000000000..0f33e93c960 --- /dev/null +++ b/io.openems.edge.controller.ess.gridoptimizedcharge/test/io/openems/edge/controller/ess/gridoptimizedcharge/EnergyScheduleHandlerTest.java @@ -0,0 +1,40 @@ +package io.openems.edge.controller.ess.gridoptimizedcharge; + +import static org.junit.Assert.assertEquals; + +import java.time.LocalTime; + +import org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import org.junit.Test; + +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.Coefficient; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.OneSimulationContext; +import io.openems.edge.energy.api.test.DummyGlobalSimulationsContext; + +public class EnergyScheduleHandlerTest { + + @Test + public void testManual() { + var esh = ControllerEssGridOptimizedChargeImpl.buildEnergyScheduleHandler(// + () -> Mode.MANUAL, // + () -> LocalTime.of(10, 00)); + var gsc = DummyGlobalSimulationsContext.fromHandlers(esh); + ((AbstractEnergyScheduleHandler) esh /* this is safe */).initialize(gsc); + + assertEquals(3894, getEssMaxCharge(gsc, esh, 0)); + assertEquals(1214, getEssMaxCharge(gsc, esh, 26)); + assertEquals(4000, getEssMaxCharge(gsc, esh, 40)); + } + + private static int getEssMaxCharge(GlobalSimulationsContext gsc, EnergyScheduleHandler esh, int periodIndex) { + var osc = OneSimulationContext.from(gsc); + var period = gsc.periods().get(periodIndex); + var ef = EnergyFlow.Model.from(osc, period); + ((EnergyScheduleHandler.WithOnlyOneState) esh).simulatePeriod(OneSimulationContext.from(gsc), period, ef); + return ((int) ef.getExtremeCoefficientValue(Coefficient.ESS, GoalType.MINIMIZE)) * -1; + } +} diff --git a/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd b/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd index 0f4d0e0a95c..db75c019243 100644 --- a/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd +++ b/io.openems.edge.controller.ess.limittotaldischarge/bnd.bnd @@ -8,7 +8,8 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ - io.openems.edge.ess.api + io.openems.edge.energy.api,\ + io.openems.edge.ess.api,\ -testpath: \ ${testpath} diff --git a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java index a75ecf85f1b..e826546a526 100644 --- a/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java +++ b/io.openems.edge.controller.ess.limittotaldischarge/src/io/openems/edge/controller/ess/limittotaldischarge/ControllerEssLimitTotalDischargeImpl.java @@ -1,8 +1,12 @@ package io.openems.edge.controller.ess.limittotaldischarge; +import static io.openems.edge.energy.api.EnergyUtils.socToEnergy; +import static java.lang.Math.max; + import java.time.Duration; import java.time.Instant; import java.util.Optional; +import java.util.function.IntSupplier; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; @@ -19,6 +23,8 @@ import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.controller.api.Controller; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.power.api.Phase; import io.openems.edge.ess.power.api.Pwr; @@ -30,9 +36,10 @@ configurationPolicy = ConfigurationPolicy.REQUIRE // ) public class ControllerEssLimitTotalDischargeImpl extends AbstractOpenemsComponent - implements ControllerEssLimitTotalDischarge, Controller, OpenemsComponent { + implements ControllerEssLimitTotalDischarge, EnergySchedulable, Controller, OpenemsComponent { private final Logger log = LoggerFactory.getLogger(ControllerEssLimitTotalDischargeImpl.class); + private final EnergyScheduleHandler energyScheduleHandler; @Reference private ComponentManager componentManager; @@ -55,6 +62,8 @@ public ControllerEssLimitTotalDischargeImpl() { Controller.ChannelId.values(), // ControllerEssLimitTotalDischarge.ChannelId.values() // ); + this.energyScheduleHandler = buildEnergyScheduleHandler(// + () -> this.minSoc); } @Activate @@ -211,4 +220,27 @@ private boolean changeState(State nextState) { return false; } } + + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

    + * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param minSoc a supplier for the configured minSoc + * @return a {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler buildEnergyScheduleHandler(IntSupplier minSoc) { + return EnergyScheduleHandler.of(// + simContext -> socToEnergy(simContext.ess().totalEnergy(), minSoc.getAsInt()), // + (simContext, period, energyFlow, minEnergy) -> { + energyFlow.setEssMaxDischarge(max(0, simContext.getEssInitial() - minEnergy)); + }); + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.energyScheduleHandler; + } } diff --git a/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd b/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd index d45bd8553ce..767f72aac39 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd +++ b/io.openems.edge.controller.ess.timeofusetariff/bnd.bnd @@ -3,6 +3,8 @@ Bundle-Vendor: FENECON GmbH Bundle-License: https://opensource.org/licenses/EPL-2.0 Bundle-Version: 1.0.0.${tstamp} +# TODO remove emergencycapacityreserve and limittotaldischarge after v1 + -buildpath: \ ${buildpath},\ io.openems.common,\ @@ -14,6 +16,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.ess.api,\ io.openems.edge.timedata.api,\ io.openems.edge.timeofusetariff.api,\ + org.apache.commons.math3,\ -testpath: \ ${testpath},\ diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java index 99793e89191..bceba3c4d55 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Config.java @@ -3,6 +3,8 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.energy.api.Version; + @ObjectClassDefinition(// name = "Controller Ess Time-Of-Use Tariff", // description = "Optimize behaviour of an ESS in combination with a Time-Of-Use (ToU) Tariff.") @@ -40,9 +42,12 @@ @AttributeDefinition(name = "Limit Charge Power for §14a EnWG", description = "Always apply §14a EnWG limitation of 4.2 kW") boolean limitChargePowerFor14aEnWG() default false; + @AttributeDefinition(name = "Version", description = "Select version of implementation") + Version version() default Version.V1_ESS_ONLY; + @AttributeDefinition(name = "Ess target filter", description = "This is auto-generated by 'Ess-ID'.") String ess_target() default "(enabled=true)"; String webconsole_configurationFactory_nameHint() default "Controller Ess Time-Of-Use Tariff [{id}]"; -} \ No newline at end of file +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java index 9876f79201e..b241b53977a 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffController.java @@ -8,10 +8,11 @@ import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.controller.api.Controller; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1; +import io.openems.edge.energy.api.EnergySchedulable; -public interface TimeOfUseTariffController extends Controller, OpenemsComponent { - - public static final int PERIODS_PER_HOUR = 4; +@SuppressWarnings("deprecation") +public interface TimeOfUseTariffController extends Controller, EnergySchedulable, OpenemsComponent { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { /** @@ -52,6 +53,14 @@ public Doc doc() { } } + /** + * Get the {@link EnergyScheduleHandlerV1}. + * + * @return {@link EnergyScheduleHandlerV1} + */ + @Deprecated + public EnergyScheduleHandlerV1 getEnergyScheduleHandlerV1(); + /** * Gets the Channel for {@link ChannelId#QUARTERLY_PRICES}. * diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java index 5ae75684899..48b0bc09338 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImpl.java @@ -1,12 +1,25 @@ package io.openems.edge.controller.ess.timeofusetariff; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.ESS_MAX_SOC; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateAutomaticMode; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeEnergyInChargeGrid; +import static io.openems.edge.energy.api.simulation.Coefficient.ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_GRID; +import static java.lang.Math.min; +import static java.lang.Math.round; +import static org.apache.commons.math3.optim.linear.Relationship.EQ; +import static org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MAXIMIZE; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BooleanSupplier; +import java.util.function.IntSupplier; +import java.util.function.Supplier; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -31,11 +44,16 @@ import io.openems.edge.controller.api.Controller; import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.Context; import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState; +import io.openems.edge.controller.ess.timeofusetariff.jsonrpc.GetScheduleRequest; +import io.openems.edge.controller.ess.timeofusetariff.jsonrpc.GetScheduleResponse; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1.ContextV1; +import io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1; import io.openems.edge.energy.api.EnergySchedulable; import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.energy.api.EnergyScheduler; +import io.openems.edge.energy.api.simulation.EnergyFlow; import io.openems.edge.ess.api.ManagedSymmetricEss; import io.openems.edge.ess.power.api.Phase; import io.openems.edge.ess.power.api.Pwr; @@ -50,15 +68,13 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.REQUIRE // ) +@SuppressWarnings("deprecation") public class TimeOfUseTariffControllerImpl extends AbstractOpenemsComponent implements TimeOfUseTariffController, - EnergySchedulable, Controller, OpenemsComponent, TimedataProvider, ComponentJsonApi { + EnergySchedulable, Controller, OpenemsComponent, TimedataProvider, ComponentJsonApi { - public static record Context(List ctrlEmergencyCapacityReserves, - List ctrlLimitTotalDischarges, ManagedSymmetricEss ess, - ControlMode controlMode, int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG) { - } - - private final EnergyScheduleHandler energyScheduleHandler; + @Deprecated + private final EnergyScheduleHandlerV1 energyScheduleHandlerV1; + private final EnergyScheduleHandler.WithDifferentStates energyScheduleHandler; private final CalculateActiveTime calculateDelayedTime = new CalculateActiveTime(this, TimeOfUseTariffController.ChannelId.DELAYED_TIME); private final CalculateActiveTime calculateChargedTime = new CalculateActiveTime(this, @@ -80,20 +96,23 @@ public static record Context(List ctrlEme @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile Timedata timedata; + @Deprecated @Reference(policyOption = ReferencePolicyOption.GREEDY, // cardinality = ReferenceCardinality.MULTIPLE, // target = "(&(enabled=true)(isReserveSocEnabled=true))") - private List ctrlEmergencyCapacityReserves = new CopyOnWriteArrayList<>(); + private volatile List ctrlEmergencyCapacityReserves = new CopyOnWriteArrayList<>(); + @Deprecated @Reference(policyOption = ReferencePolicyOption.GREEDY, // cardinality = ReferenceCardinality.MULTIPLE, // target = "(enabled=true)") - private List ctrlLimitTotalDischarges = new CopyOnWriteArrayList<>(); + private volatile List ctrlLimitTotalDischarges = new CopyOnWriteArrayList<>(); @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) private ManagedSymmetricEss ess; @Reference + @Deprecated private EnergyScheduler energyScheduler; private Config config = null; @@ -104,11 +123,18 @@ public TimeOfUseTariffControllerImpl() { Controller.ChannelId.values(), // TimeOfUseTariffController.ChannelId.values() // ); - this.energyScheduleHandler = new EnergyScheduleHandler<>(// + + this.energyScheduleHandlerV1 = new EnergyScheduleHandlerV1(// () -> this.config.controlMode().states, // - () -> new Context(this.ctrlEmergencyCapacityReserves, this.ctrlLimitTotalDischarges, this.ess, + () -> new ContextV1(this.ctrlEmergencyCapacityReserves, this.ctrlLimitTotalDischarges, this.ess, this.config.controlMode(), this.config.maxChargePowerFromGrid(), this.config.limitChargePowerFor14aEnWG())); + + this.energyScheduleHandler = buildEnergyScheduleHandler(// + () -> this.ess, // + () -> this.config.controlMode(), // + () -> this.config.maxChargePowerFromGrid(), // + () -> this.config.limitChargePowerFor14aEnWG()); } @Activate @@ -121,6 +147,7 @@ private void activate(ComponentContext context, Config config) { private void modified(ComponentContext context, Config config) { super.modified(context, config.id(), config.alias(), config.enabled()); this.applyConfig(config); + this.energyScheduleHandler.triggerReschedule(); } private synchronized void applyConfig(Config config) { @@ -138,12 +165,26 @@ protected void deactivate() { @Override public void run() throws OpenemsNamedException { - // Mode given from the configuration. - var as = switch (this.config.mode()) { - case AUTOMATIC -> calculateAutomaticMode(this.sum, this.ess, - this.energyScheduleHandler.getCurrentEssChargeInChargeGrid(), this.config.maxChargePowerFromGrid(), - this.config.limitChargePowerFor14aEnWG(), this.getCurrentPeriodState()); - case OFF -> new ApplyState(StateMachine.BALANCING, null); + // Version and Mode given from the configuration. + final var as = switch (this.config.version()) { + + case V1_ESS_ONLY // + -> switch (this.config.mode()) { + case AUTOMATIC // + -> UtilsV1.calculateAutomaticMode(this.energyScheduleHandlerV1, this.sum, this.ess, + this.config.maxChargePowerFromGrid(), this.config.limitChargePowerFor14aEnWG()); + case OFF // + -> new ApplyState(StateMachine.BALANCING, null); + }; + + case V2_ENERGY_SCHEDULABLE // + -> switch (this.config.mode()) { + case AUTOMATIC // + -> calculateAutomaticMode(this.sum, this.ess, this.config.maxChargePowerFromGrid(), + this.config.limitChargePowerFor14aEnWG(), this.energyScheduleHandler.getCurrentPeriod()); + case OFF // + -> new ApplyState(StateMachine.BALANCING, null); + }; }; // Update Channels @@ -159,14 +200,6 @@ public void run() throws OpenemsNamedException { } } - private StateMachine getCurrentPeriodState() { - var state = this.energyScheduleHandler.getCurrentState(); - if (state != null) { - return state; - } - return BALANCING; // Default Fallback - } - @Override public Timedata getTimedata() { return this.timedata; @@ -174,21 +207,134 @@ public Timedata getTimedata() { @Override public void buildJsonApiRoutes(JsonApiBuilder builder) { - this.energyScheduler.buildJsonApiRoutes(builder); + builder.handleRequest(GetScheduleRequest.METHOD, call -> // + switch (this.config.version()) { + case V1_ESS_ONLY // + -> this.energyScheduler.handleGetScheduleRequestV1(call, this.id()); + + case V2_ENERGY_SCHEDULABLE // + -> GetScheduleResponse.from(call.getRequest().getId(), // + this.id(), this.componentManager.getClock(), this.ess, this.timedata, this.energyScheduleHandler); + }); } @Override public String debugLog() { var b = new StringBuilder() // .append(this.getStateMachine()); // - if (this.getCurrentPeriodState() == null) { - b.append("|No Schedule available"); + + switch (this.config.version()) { + case V1_ESS_ONLY -> { + if (this.energyScheduleHandlerV1 == null || this.energyScheduleHandlerV1.getCurrentState() == null) { + b.append("|No Schedule available"); + } + } + case V2_ENERGY_SCHEDULABLE -> { + if (this.energyScheduleHandler.getCurrentPeriod() == null) { + b.append("|No Schedule available"); + } + } } return b.toString(); } + /** + * Builds the {@link EnergyScheduleHandler}. + * + *

    + * This is public so that it can be used by the EnergyScheduler integration + * test. + * + * @param ess a supplier for the + * {@link ManagedSymmetricEss} + * @param controlMode a supplier for the configured + * {@link ControlMode} + * @param maxChargePowerFromGrid a supplier for the configured + * maxChargePowerFromGrid + * @param limitChargePowerFor14aEnWG a supplier for the configured + * limitChargePowerFor14aEnWG + * @return a typed {@link EnergyScheduleHandler} + */ + public static EnergyScheduleHandler.WithDifferentStates buildEnergyScheduleHandler( + Supplier ess, Supplier controlMode, IntSupplier maxChargePowerFromGrid, + BooleanSupplier limitChargePowerFor14aEnWG) { + return EnergyScheduleHandler.of(// + StateMachine.BALANCING, // + () -> controlMode.get().states, // + simContext -> { + // Maximium-SoC in CHARGE_GRID is 90 % + var maxSocEnergyInChargeGrid = round(simContext.ess().totalEnergy() * (ESS_MAX_SOC / 100)); + var essChargeInChargeGrid = calculateChargeEnergyInChargeGrid(simContext); + return new EshContext(ess.get(), controlMode.get(), maxChargePowerFromGrid.getAsInt(), + limitChargePowerFor14aEnWG.getAsBoolean(), maxSocEnergyInChargeGrid, essChargeInChargeGrid); + }, // + (simContext, period, energyFlow, ctrlContext, state) -> { + switch (state) { + case BALANCING -> applyBalancing(energyFlow); // TODO Move to CtrlBalancing + case DELAY_DISCHARGE -> applyDelayDischarge(energyFlow); + case CHARGE_GRID -> { + energyFlow.setEssMaxCharge(ctrlContext.maxSocEnergyInChargeGrid - simContext.getEssInitial()); + applyChargeGrid(energyFlow, ctrlContext.essChargeInChargeGrid); + } + } + }, // + Utils::postprocessSimulatorState); + } + + /** + * Simulate {@link EnergyFlow} in {@link StateMachine#BALANCING}. + * + * @param model the {@link EnergyFlow.Model} + */ + public static void applyBalancing(EnergyFlow.Model model) { + var target = model.consumption - model.production; + model.setFittingCoefficientValue(ESS, EQ, target); + } + + /** + * Simulate {@link EnergyFlow} in DELAY_DISCHARGE. + * + * @param model the {@link EnergyFlow.Model} + */ + public static void applyDelayDischarge(EnergyFlow.Model model) { + var target = min(0 /* Charge -> apply Balancing */, model.consumption - model.production); + model.setFittingCoefficientValue(ESS, EQ, target); + } + + /** + * Simulate {@link EnergyFlow} in {@link StateMachine#CHARGE_GRID}. + * + * @param model the {@link EnergyFlow.Model} + * @param chargeEnergy the target charge-from-grid energy + */ + public static void applyChargeGrid(EnergyFlow.Model model, int chargeEnergy) { + model.setExtremeCoefficientValue(PROD_TO_ESS, MAXIMIZE); + model.setExtremeCoefficientValue(GRID_TO_CONS, MAXIMIZE); + model.setFittingCoefficientValue(GRID_TO_ESS, EQ, chargeEnergy); + } + + /** + * Simulate {@link EnergyFlow} in a future DISCHARGE_GRID state. + * + * @param model the {@link EnergyFlow.Model} + * @param dischargeEnergy the target discharge-to-grid energy + */ + public static void applyDischargeGrid(EnergyFlow.Model model, int dischargeEnergy) { + model.setExtremeCoefficientValue(PROD_TO_GRID, MAXIMIZE); + model.setFittingCoefficientValue(GRID_TO_ESS, EQ, -dischargeEnergy); + } + + public static record EshContext(ManagedSymmetricEss ess, ControlMode controlMode, int maxChargePowerFromGrid, + boolean limitChargePowerFor14aEnWG, int maxSocEnergyInChargeGrid, int essChargeInChargeGrid) { + } + @Override - public EnergyScheduleHandler getEnergyScheduleHandler() { + public EnergyScheduleHandler.WithDifferentStates getEnergyScheduleHandler() { return this.energyScheduleHandler; } + + @Override + public EnergyScheduleHandlerV1 getEnergyScheduleHandlerV1() { + return this.energyScheduleHandlerV1; + } } diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java index 6543f9b24c1..7b21d653571 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/Utils.java @@ -1,23 +1,27 @@ package io.openems.edge.controller.ess.timeofusetariff; -import static io.openems.edge.common.type.TypeUtils.multiply; +import static com.google.common.math.Quantiles.percentiles; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController.PERIODS_PER_HOUR; +import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR; +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; +import static io.openems.edge.energy.api.EnergyUtils.toPower; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.round; -import static java.util.stream.IntStream.concat; +import static java.util.Arrays.stream; -import java.util.List; -import java.util.Objects; +import com.google.common.primitives.ImmutableIntArray; import io.openems.common.types.ChannelAddress; import io.openems.edge.common.sum.Sum; import io.openems.edge.controller.api.Controller; -import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; -import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; import io.openems.edge.ess.api.HybridEss; import io.openems.edge.ess.api.ManagedSymmetricEss; @@ -52,29 +56,6 @@ private Utils() { public static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower"); public static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc"); - /** - * Returns the configured minimum SoC, or zero. - * - * @param ctrlLimitTotalDischarges the list of - * {@link ControllerEssLimitTotalDischarge} - * @param ctrlEmergencyCapacityReserves the list of - * {@link ControllerEssEmergencyCapacityReserve} - * @return the value in [%] - */ - public static int getEssMinSocPercentage(List ctrlLimitTotalDischarges, - List ctrlEmergencyCapacityReserves) { - return concat(// - ctrlLimitTotalDischarges.stream() // - .map(ctrl -> ctrl.getMinSoc().get()) // - .filter(Objects::nonNull) // - .mapToInt(v -> max(0, v)), // only positives - ctrlEmergencyCapacityReserves.stream() // - .map(ctrl -> ctrl.getActualReserveSoc().get()) // - .filter(Objects::nonNull) // - .mapToInt(v -> max(0, v))) // only positives - .max().orElse(0); - } - public static record ApplyState(StateMachine actualState, Integer setPoint) { } @@ -83,20 +64,19 @@ public static record ApplyState(StateMachine actualState, Integer setPoint) { * * @param sum the {@link Sum} * @param ess the {@link ManagedSymmetricEss} - * @param essChargeInChargeGrid ESS Charge Energy in CHARGE_GRID State [Wh] * @param maxChargePowerFromGrid the configured max charge from grid power * @param limitChargePowerFor14aEnWG Limit Charge Power for §14a EnWG - * @param targetState the scheduled target {@link StateMachine} + * @param period the scheduled {@link Period} * @return {@link ApplyState} */ - public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess, Integer essChargeInChargeGrid, - int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG, StateMachine targetState) { + public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess, int maxChargePowerFromGrid, + boolean limitChargePowerFor14aEnWG, Period period) { final StateMachine actualState; final Integer setPoint; var gridActivePower = sum.getGridActivePower().get(); // current buy-from/sell-to grid var essActivePower = ess.getActivePower().get(); // current charge/discharge ESS - if (gridActivePower == null || essActivePower == null) { + if (period == null || gridActivePower == null || essActivePower == null) { // undefined state return new ApplyState(BALANCING, null); } @@ -104,9 +84,9 @@ public static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess // Post-process and get actual state final var pwrBalancing = gridActivePower + essActivePower; final var pwrDelayDischarge = calculateDelayDischargePower(ess); - final var pwrChargeGrid = calculateChargeGridPower(essChargeInChargeGrid, ess, essActivePower, gridActivePower, - maxChargePowerFromGrid, limitChargePowerFor14aEnWG); - actualState = postprocessRunState(targetState, pwrBalancing, pwrDelayDischarge, pwrChargeGrid); + final var pwrChargeGrid = calculateChargeGridPower(period.context().essChargeInChargeGrid(), ess, + essActivePower, gridActivePower, maxChargePowerFromGrid, limitChargePowerFor14aEnWG); + actualState = postprocessRunState(period.state(), pwrBalancing, pwrDelayDischarge, pwrChargeGrid); // Get and apply ActivePower Less-or-Equals Set-Point setPoint = switch (actualState) { @@ -156,8 +136,39 @@ public static StateMachine postprocessRunState(StateMachine state, int pwrBalanc return state; } - protected static int calculateEssChargeInChargeGridPowerFromParams(Integer essChargeInChargeGrid, - ManagedSymmetricEss ess) { + /** + * Post-Process a state of a Period during Simulation, i.e. replace with + * 'better' state with the same behaviour. + * + *

    + * NOTE: heavy computation is ok here, because this method is called only at the + * end with the best Schedule. + * + * @param ef the {@link EnergyFlow} for the state + * @param state the initial state + * @return the new state + */ + public static StateMachine postprocessSimulatorState(EnergyFlow ef, StateMachine state) { + if (state == CHARGE_GRID) { + // CHARGE_GRID,... + if (ef.getGridToEss() <= 0) { + // but battery is not charged from grid + state = DELAY_DISCHARGE; + } + } + + if (state == DELAY_DISCHARGE) { + // DELAY_DISCHARGE,... + if (ef.getEss() < 0) { + // but battery gets charged + state = BALANCING; + } + } + + return state; + } + + protected static int calculateEssChargeInChargeGridPower(Integer essChargeInChargeGrid, ManagedSymmetricEss ess) { if (essChargeInChargeGrid != null) { return toPower(essChargeInChargeGrid); } @@ -187,7 +198,7 @@ protected static int calculateEssChargeInChargeGridPowerFromParams(Integer essCh public static int calculateChargeGridPower(Integer essChargeInChargeGrid, ManagedSymmetricEss ess, int essActivePower, int gridActivePower, int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG) { var realGridPower = gridActivePower + essActivePower; // 'real', without current ESS charge/discharge - var targetChargePower = calculateEssChargeInChargeGridPowerFromParams(essChargeInChargeGrid, ess) // + var targetChargePower = calculateEssChargeInChargeGridPower(essChargeInChargeGrid, ess) // + min(0, realGridPower) * -1; // add excess production var effectiveGridBuyPower = max(0, realGridPower) + targetChargePower; var chargePower = max(0, targetChargePower - max(0, effectiveGridBuyPower - maxChargePowerFromGrid)); @@ -236,12 +247,63 @@ public static int calculateDelayDischargePower(ManagedSymmetricEss ess) { } /** - * Converts energy [Wh/15 min] to power [W]. + * Calculates the default ESS charge energy per period in + * {@link StateMachine#CHARGE_GRID}. * - * @param energy the energy value - * @return the power value + *

    + * Applies {@link #ESS_CHARGE_C_RATE} with the minimum of usable ESS energy or + * predicted consumption energy that cannot be supplied from production. + * + * @param gsc the {@link GlobalSimulationsContext} + * @return the value in [Wh] */ - private static Integer toPower(Integer energy) { - return multiply(energy, PERIODS_PER_HOUR); + public static int calculateChargeEnergyInChargeGrid(GlobalSimulationsContext gsc) { + var refs = ImmutableIntArray.builder(); + + // Uses the total available energy as reference (= fallback) + var fallback = max(0, round(ESS_MAX_SOC / 100F * gsc.ess().totalEnergy())); + add(refs, fallback); + + // Uses the total excess consumption as reference + add(refs, gsc.periods().stream() // + .mapToInt(p -> p.consumption() - p.production()) // calculates excess Consumption Energy per Period + .sum()); + + add(refs, gsc.periods().stream() // + .takeWhile(p -> p.consumption() >= p.production()) // take only first Periods + .mapToInt(p -> p.consumption() - p.production()) // calculates excess Consumption Energy per Period + .sum()); + + // Uses the excess consumption during high price periods as reference + { + var prices = gsc.periods().stream() // + .mapToDouble(GlobalSimulationsContext.Period::price) // + .toArray(); + var peakIndex = findFirstPeakIndex(findFirstValleyIndex(0, prices), prices); + var firstPrices = stream(prices) // + .limit(peakIndex) // + .toArray(); + if (firstPrices.length > 0) { + var percentilePrice = percentiles().index(95).compute(firstPrices); + add(refs, gsc.periods().stream() // + .limit(peakIndex) // + .filter(p -> p.price() >= percentilePrice) // takes only prices > percentile + .mapToInt(p -> p.consumption() - p.production()) // excess Consumption Energy per Period + .sum()); + } + } + + return (int) round(// + refs.build().stream() // + .average() // + .orElse(fallback) // + * ESS_CHARGE_C_RATE / PERIODS_PER_HOUR); } + + private static void add(ImmutableIntArray.Builder builder, int value) { + if (value > 0) { + builder.add(value); + } + } + } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java similarity index 94% rename from io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java rename to io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java index 1dbdb0dc7d8..ff357bfd827 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleRequest.java +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleRequest.java @@ -1,4 +1,4 @@ -package io.openems.edge.energy.jsonrpc; +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; import com.google.gson.JsonObject; diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java new file mode 100644 index 00000000000..d60e3eae6e6 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponse.java @@ -0,0 +1,230 @@ +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; + +import static io.openems.common.utils.JsonUtils.buildJsonObject; +import static io.openems.common.utils.JsonUtils.getAsOptionalDouble; +import static io.openems.common.utils.JsonUtils.getAsOptionalInt; +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static io.openems.edge.common.type.TypeUtils.fitWithin; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_CONSUMPTION; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_ESS_DISCHARGE_POWER; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_ESS_SOC; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_GRID; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_PRODUCTION; +import static io.openems.edge.energy.api.EnergyUtils.toPower; +import static java.lang.Math.round; +import static java.util.Optional.ofNullable; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Set; +import java.util.SortedMap; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSortedMap; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext; +import io.openems.edge.controller.ess.timeofusetariff.Utils; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period; +import io.openems.edge.ess.api.SymmetricEss; +import io.openems.edge.timedata.api.Timedata; + +/** + * Represents a JSON-RPC Response for 'getSchedule'. + * + *

    + * {
    + *   "jsonrpc": "2.0",
    + *   "id": "UUID",
    + *   "result": {
    + *     'schedule': [{
    + *      'timestamp':...,
    + *      'price':...,
    + *      'state':...,
    + *      'grid':...,
    + *      'production':...,
    + *      'consumption':...,
    + *      'ess':...,
    + *      'soc':...,
    + *     }]
    + *   }
    + * }
    + * 
    + */ +public class GetScheduleResponse extends JsonrpcResponseSuccess { + + private static final Logger LOG = LoggerFactory.getLogger(GetScheduleResponse.class); + + private final JsonObject result; + + public GetScheduleResponse(UUID id, JsonObject result) { + super(id); + this.result = result; + } + + @Override + public JsonObject getResult() { + return this.result; + } + + /** + * Builds a {@link GetScheduleResponse} with last three hours data and current + * Schedule. + * + * @param requestId the JSON-RPC request-id + * @param componentId the Component-ID of the parent + * {@link TimeOfUseTariffController} + * @param clock a {@link Clock} + * @param ess the {@link SymmetricEss} + * @param timedata the {@link Timedata} + * @param energyScheduleHandler the {@link EnergyScheduleHandler} + * @return the {@link GetScheduleResponse} + * @throws OpenemsNamedException on error + */ + public static GetScheduleResponse from(UUID requestId, String componentId, Clock clock, SymmetricEss ess, + Timedata timedata, + EnergyScheduleHandler.WithDifferentStates energyScheduleHandler) { + final var schedule = energyScheduleHandler.getSchedule(); + final JsonArray result; + if (schedule.isEmpty()) { + result = new JsonArray(); + } else { + final var historic = fromHistoricData(componentId, schedule.firstKey(), timedata); + final var future = fromSchedule(ess, schedule); + result = Stream.concat(historic, future) // + .collect(toJsonArray()); + } + + return new GetScheduleResponse(requestId, // + buildJsonObject() // + .add("schedule", result) // + .build()); + } + + /** + * Queries the last three hours' data and converts it to a {@link Stream} of + * {@link JsonObject}s suitable for a {@link GetScheduleResponse}. + * + * @param componentId Component-ID of {@link TimeOfUseTariffControllerImpl} + * @param firstSchedule {@link ZonedDateTime} of the first entry in the Schedule + * (rounded down to 15 minutes) + * @param timedata the {@link Timedata} + * @return {@link Stream} of {@link JsonObject}s + */ + // TODO protected is sufficient after v1 + public static Stream fromHistoricData(String componentId, ZonedDateTime firstSchedule, + Timedata timedata) { + // Process last three hours of historic data + final var fromTime = firstSchedule.minusHours(3); + final var toTime = firstSchedule.minusSeconds(1); + final var channelQuarterlyPrices = new ChannelAddress(componentId, "QuarterlyPrices"); + final var channelStateMachine = new ChannelAddress(componentId, "StateMachine"); + SortedMap> data = null; + try { + data = timedata.queryHistoricData(null, fromTime, toTime, // + Set.of(channelQuarterlyPrices, channelStateMachine, // + Utils.SUM_GRID, SUM_PRODUCTION, SUM_CONSUMPTION, SUM_ESS_DISCHARGE_POWER, SUM_ESS_SOC), + new Resolution(15, ChronoUnit.MINUTES)); + } catch (Exception e) { + LOG.warn("Unable to read historic data: " + e.getMessage()); + } + if (data == null) { + return Stream.of(); + } + + return data.entrySet().stream() // + .map(e -> { + var d = e.getValue(); + Function getter = (c) -> ofNullable(d.get(c)) + .orElse(JsonNull.INSTANCE); + + return buildJsonObject() // + .addProperty("timestamp", e.getKey()) // + .addProperty("price", + getAsOptionalDouble(getter.apply(channelQuarterlyPrices)).orElse(null)) // + .addProperty("state", + getAsOptionalInt(getter.apply(channelStateMachine)).orElse(BALANCING.getValue())) // + .addProperty("grid", getAsOptionalInt(getter.apply(SUM_GRID)).orElse(null)) // + .addProperty("production", getAsOptionalInt(getter.apply(SUM_PRODUCTION)).orElse(null)) // + .addProperty("consumption", getAsOptionalInt(getter.apply(SUM_CONSUMPTION)).orElse(null)) // + .addProperty("ess", getAsOptionalInt(getter.apply(SUM_ESS_DISCHARGE_POWER)).orElse(null)) // + .addProperty("soc", getAsOptionalInt(getter.apply(SUM_ESS_SOC)).orElse(null)) // + .build(); + }); + } + + /** + * Converts the Schedule to a {@link Stream} of {@link JsonObject}s suitable for + * a {@link GetScheduleResponse}. + * + * @param ess the {@link SymmetricEss} + * @param schedule the {@link EnergyScheduleHandler} schedule + * @return {@link Stream} of {@link JsonObject}s + */ + protected static Stream fromSchedule(SymmetricEss ess, + ImmutableSortedMap> schedule) { + final var essTotalEnergy = ess.getCapacity().orElse(0); + return schedule.entrySet().stream() // + .map(e -> { + var p = e.getValue(); + + return buildJsonObject() // + .addProperty("timestamp", e.getKey()) // + .addProperty("price", p.price()) // + .addProperty("state", p.state().getValue()) // + .addProperty("grid", toPower(p.energyFlow().getGrid())) // + .addProperty("production", toPower(p.energyFlow().getProd())) // + .addProperty("consumption", toPower(p.energyFlow().getCons())) // + .addProperty("ess", toPower(p.energyFlow().getEss())) // + .addProperty("soc", round(fitWithin(0F, 100F, // + p.essInitialEnergy() * 100F / essTotalEnergy))) // + .build(); + }); + } + + /** + * Creates an empty default Schedule in case no Schedule is available. + * + * @param clock the {@link Clock} + * @param defaultState the default {@link StateMachine} + * @return {@link Stream} of {@link JsonObject}s + */ + protected static Stream empty(Clock clock, StateMachine defaultState) { + final var now = ZonedDateTime.now(clock); + final var numberOfPeriods = 96; + + return IntStream.range(0, numberOfPeriods) // + .mapToObj(i -> { + return buildJsonObject() // + .addProperty("timestamp", now.plusMinutes(i * 15)) // + .add("price", JsonNull.INSTANCE) // + .addProperty("state", defaultState.getValue()) // + .add("grid", JsonNull.INSTANCE) // + .add("production", JsonNull.INSTANCE) // + .add("consumption", JsonNull.INSTANCE) // + .add("ess", JsonNull.INSTANCE) // + .add("soc", JsonNull.INSTANCE) // + .build(); + }); + } + +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/package-info.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/package-info.java new file mode 100644 index 00000000000..c3c8d61c926 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/EnergyScheduleHandlerV1.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/EnergyScheduleHandlerV1.java new file mode 100644 index 00000000000..305fa7ee007 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/EnergyScheduleHandlerV1.java @@ -0,0 +1,88 @@ +package io.openems.edge.controller.ess.timeofusetariff.v1; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; +import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; +import io.openems.edge.controller.ess.timeofusetariff.ControlMode; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.ess.api.ManagedSymmetricEss; + +@Deprecated +public class EnergyScheduleHandlerV1 { + + public static record ContextV1(List ctrlEmergencyCapacityReserves, + List ctrlLimitTotalDischarges, ManagedSymmetricEss ess, + ControlMode controlMode, int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG) { + } + + private final Supplier availableStates; + private final Supplier context; + + private ImmutableSortedMap> schedule = ImmutableSortedMap.of(); + + public EnergyScheduleHandlerV1(Supplier availableStates, Supplier context) { + this.availableStates = availableStates; + this.context = context; + } + + /** + * Gets the available States. + * + * @return an Array of States + */ + public StateMachine[] getAvailableStates() { + return this.availableStates.get(); + } + + /** + * Gets the Context. + * + * @return the Context + */ + public ContextV1 getContext() { + return this.context.get(); + } + + public static record Period(STATE state, Integer essChargeInChargeGrid) { + } + + /** + * Sets the Schedule. Called by Optimizer. + * + * @param schedule the Schedule + */ + public synchronized void setSchedule(ImmutableSortedMap> schedule) { + this.schedule = schedule; + } + + /** + * Gets the current State or null. + * + * @return the State or null + */ + public synchronized StateMachine getCurrentState() { + return Optional.ofNullable(this.schedule.get(roundDownToQuarter(ZonedDateTime.now()))) // + .map(Period::state) // + .orElse(null); + } + + /** + * Gets the current essChargeInChargeGrid or null. + * + * @return the essChargeInChargeGrid or null + */ + public synchronized Integer getCurrentEssChargeInChargeGrid() { + return Optional.ofNullable(this.schedule.get(roundDownToQuarter(ZonedDateTime.now()))) // + .map(Period::essChargeInChargeGrid) // + .orElse(null); + } + +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1.java new file mode 100644 index 00000000000..56db25223e9 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1.java @@ -0,0 +1,132 @@ +package io.openems.edge.controller.ess.timeofusetariff.v1; + +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeGridPower; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateDelayDischargePower; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.postprocessRunState; +import static java.lang.Math.max; +import static java.util.stream.IntStream.concat; + +import java.util.List; +import java.util.Objects; + +import io.openems.edge.common.sum.Sum; +import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; +import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; +import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState; +import io.openems.edge.ess.api.ManagedSymmetricEss; + +/** + * Utils for {@link TimeOfUseTariffController}. + * + *

    + * All energy values are in [Wh] and positive, unless stated differently. + */ +@Deprecated +public final class UtilsV1 { + + private UtilsV1() { + } + + public static final int PERIODS_PER_HOUR = 4; + + /** + * Returns the configured minimum SoC, or zero. + * + * @param ctrlLimitTotalDischarges the list of + * {@link ControllerEssLimitTotalDischarge} + * @param ctrlEmergencyCapacityReserves the list of + * {@link ControllerEssEmergencyCapacityReserve} + * @return the value in [%] + */ + public static int getEssMinSocPercentage(List ctrlLimitTotalDischarges, + List ctrlEmergencyCapacityReserves) { + return concat(// + ctrlLimitTotalDischarges.stream() // + .map(ctrl -> ctrl.getMinSoc().get()) // + .filter(Objects::nonNull) // + .mapToInt(v -> max(0, v)), // only positives + ctrlEmergencyCapacityReserves.stream() // + .map(ctrl -> ctrl.getActualReserveSoc().get()) // + .filter(Objects::nonNull) // + .mapToInt(v -> max(0, v))) // only positives + .max().orElse(0); + } + + /** + * Calculate Automatic Mode. + * + * @param esh the {@link EnergyScheduleHandlerV1} + * @param sum the {@link Sum} + * @param ess the {@link ManagedSymmetricEss} + * @param maxChargePowerFromGrid the configured max charge from grid power + * @param limitChargePowerFor14aEnWG Limit Charge Power for §14a EnWG + * @return {@link ApplyState} + */ + public static ApplyState calculateAutomaticMode(EnergyScheduleHandlerV1 esh, Sum sum, ManagedSymmetricEss ess, + int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG) { + final var targetState = getCurrentPeriodState(esh); + final var essChargeInChargeGrid = esh.getCurrentEssChargeInChargeGrid(); + return calculateAutomaticMode(sum, ess, essChargeInChargeGrid, maxChargePowerFromGrid, + limitChargePowerFor14aEnWG, targetState); + } + + /** + * Calculate Automatic Mode. + * + * @param sum the {@link Sum} + * @param ess the {@link ManagedSymmetricEss} + * @param essChargeInChargeGrid ESS Charge Energy in CHARGE_GRID State [Wh] + * @param maxChargePowerFromGrid the configured max charge from grid power + * @param limitChargePowerFor14aEnWG Limit Charge Power for §14a EnWG + * @param targetState the scheduled target {@link StateMachine} + * @return {@link ApplyState} + */ + protected static ApplyState calculateAutomaticMode(Sum sum, ManagedSymmetricEss ess, Integer essChargeInChargeGrid, + int maxChargePowerFromGrid, boolean limitChargePowerFor14aEnWG, StateMachine targetState) { + final StateMachine actualState; + final Integer setPoint; + + var gridActivePower = sum.getGridActivePower().get(); // current buy-from/sell-to grid + var essActivePower = ess.getActivePower().get(); // current charge/discharge ESS + if (gridActivePower == null || essActivePower == null) { + // undefined state + return new ApplyState(BALANCING, null); + } + + // Post-process and get actual state + final var pwrBalancing = gridActivePower + essActivePower; + final var pwrDelayDischarge = calculateDelayDischargePower(ess); + final var pwrChargeGrid = calculateChargeGridPower(essChargeInChargeGrid, ess, essActivePower, gridActivePower, + maxChargePowerFromGrid, limitChargePowerFor14aEnWG); + actualState = postprocessRunState(targetState, pwrBalancing, pwrDelayDischarge, pwrChargeGrid); + + // Get and apply ActivePower Less-or-Equals Set-Point + setPoint = switch (actualState) { + case BALANCING -> null; // delegate to next priority Controller + case DELAY_DISCHARGE -> pwrDelayDischarge; + case CHARGE_GRID -> pwrChargeGrid; + }; + + return new ApplyState(actualState, setPoint); + } + + /** + * Gets the current period state of the {@link EnergyScheduleHandlerV1} or + * {@link StateMachine#BALANCING}. + * + * @param esh the {@link EnergyScheduleHandlerV1} + * @return the {@link StateMachine} + */ + public static StateMachine getCurrentPeriodState(EnergyScheduleHandlerV1 esh) { + if (esh != null) { + var state = esh.getCurrentState(); + if (state != null) { + return state; + } + } + return BALANCING; // Default Fallback + } +} diff --git a/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/package-info.java b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/package-info.java new file mode 100644 index 00000000000..7c842e31832 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/src/io/openems/edge/controller/ess/timeofusetariff/v1/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.controller.ess.timeofusetariff.v1; diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/MyConfig.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/MyConfig.java index bc0c137b4a2..60f8bef585d 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/MyConfig.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/MyConfig.java @@ -2,6 +2,7 @@ import io.openems.common.test.AbstractComponentConfig; import io.openems.common.utils.ConfigUtils; +import io.openems.edge.energy.api.Version; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { @@ -16,6 +17,7 @@ protected static class Builder { private int maxChargePowerFromGrid; private boolean limitChargePowerFor14aEnWG; private RiskLevel riskLevel; + private Version version; private Builder() { } @@ -65,6 +67,11 @@ public Builder setLimitChargePowerFor14aEnWG(boolean limitChargePowerFor14aEnWG) return this; } + public Builder setVersion(Version version) { + this.version = version; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -121,6 +128,11 @@ public RiskLevel riskLevel() { return this.builder.riskLevel; } + @Override + public Version version() { + return this.builder.version; + } + @Override public String ess_target() { return ConfigUtils.generateReferenceTargetFilter(this.id(), this.ess_id()); diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java index a0623215e0f..3750aa110a7 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/TimeOfUseTariffControllerImplTest.java @@ -13,8 +13,13 @@ import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext; import io.openems.edge.controller.test.ControllerTest; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.Version; +import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timedata.test.DummyTimedata; import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider; @@ -23,18 +28,25 @@ public class TimeOfUseTariffControllerImplTest { @Test public void test() throws Exception { final var clock = createDummyClock(); - create(clock) // + create(clock, // + new DummyManagedSymmetricEss("ess0") // + .withSoc(60) // + .withCapacity(10000), // + new DummyTimedata("timedata0")) // .deactivate(); } /** * Creates a {@link TimeOfUseTariffControllerImpl} instance. * - * @param clock a {@link Clock} + * @param clock a {@link Clock} + * @param ess the {@link SymmetricEss} + * @param timedata the {@link Timedata} * @return the object * @throws Exception on error */ - public static TimeOfUseTariffControllerImpl create(Clock clock) throws Exception { + public static TimeOfUseTariffControllerImpl create(Clock clock, SymmetricEss ess, Timedata timedata) + throws Exception { var componentManager = new DummyComponentManager(clock); var sum = new DummySum(); var timeOfUseTariff = DummyTimeOfUseTariffProvider.empty(clock); @@ -43,12 +55,10 @@ public static TimeOfUseTariffControllerImpl create(Clock clock) throws Exception new ControllerTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", componentManager) // - .addReference("timedata", new DummyTimedata("timedata0")) // + .addReference("timedata", timedata) // .addReference("timeOfUseTariff", timeOfUseTariff) // .addReference("sum", sum) // - .addReference("ess", new DummyManagedSymmetricEss("ess0") // - .withSoc(60) // - .withCapacity(10000)) // + .addReference("ess", ess) // .activate(MyConfig.create() // .setId("ctrl0") // .setEnabled(false) // @@ -58,9 +68,22 @@ public static TimeOfUseTariffControllerImpl create(Clock clock) throws Exception .setEssMaxChargePower(5000) // .setMaxChargePowerFromGrid(10000) // .setLimitChargePowerFor14aEnWG(false) // + .setVersion(Version.V2_ENERGY_SCHEDULABLE) // .setRiskLevel(MEDIUM) // .build()) // .next(new TestCase()); return sut; } + + /** + * Gets the {@link EnergyScheduleHandler}. + * + * @param ctrl the {@link TimeOfUseTariffControllerImpl} + * @return the object + * @throws Exception on error + */ + public static EnergyScheduleHandler.WithDifferentStates getEnergyScheduleHandler( + TimeOfUseTariffControllerImpl ctrl) throws Exception { + return ctrl.getEnergyScheduleHandler(); + } } diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/UtilsTest.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/UtilsTest.java index c7a104c3419..ef883691f38 100644 --- a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/UtilsTest.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/UtilsTest.java @@ -5,22 +5,36 @@ import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateAutomaticMode; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeEnergyInChargeGrid; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateChargeGridPower; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateDelayDischargePower; -import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateEssChargeInChargeGridPowerFromParams; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateEssChargeInChargeGridPower; import static io.openems.edge.controller.ess.timeofusetariff.Utils.calculateMaxChargeProductionPower; import static org.junit.Assert.assertEquals; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + import org.junit.Test; +import com.google.common.collect.ImmutableList; + +import io.openems.common.test.TimeLeapClock; import io.openems.edge.common.sum.DummySum; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.EshContext; import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; import io.openems.edge.ess.api.SymmetricEss; import io.openems.edge.ess.test.DummyHybridEss; import io.openems.edge.ess.test.DummyManagedSymmetricEss; public class UtilsTest { + public static final TimeLeapClock CLOCK = new TimeLeapClock(Instant.ofEpochSecond(946684800), ZoneId.of("UTC")); + public static final ZonedDateTime TIME = ZonedDateTime.now(CLOCK); + @Test public void testCalculateChargeGridPower() { assertEquals(-10000, calculateChargeGridPower(null, // @@ -111,22 +125,28 @@ public void testCalculateDelayDischarge() { } @Test - public void testCalculateMaxChargeGridPowerFromParams() { + public void testCalculateMaxChargeGridPower() { final var ess = new DummyManagedSymmetricEss("ess0"); // No params, initial ESS - assertEquals(0, calculateEssChargeInChargeGridPowerFromParams(null, ess)); + assertEquals(0, calculateEssChargeInChargeGridPower(null, ess)); // No params, ESS with MaxApparentPower withValue(ess, SymmetricEss.ChannelId.MAX_APPARENT_POWER, 1000); - assertEquals(250, calculateEssChargeInChargeGridPowerFromParams(null, ess)); + assertEquals(250, calculateEssChargeInChargeGridPower(null, ess)); // No params, ESS with Capacity withValue(ess, SymmetricEss.ChannelId.CAPACITY, 15000); - assertEquals(7500, calculateEssChargeInChargeGridPowerFromParams(null, ess)); + assertEquals(7500, calculateEssChargeInChargeGridPower(null, ess)); // With params (22 kWh; but few Consumption) - assertEquals(5360, calculateEssChargeInChargeGridPowerFromParams(1340, ess)); + assertEquals(5360, calculateEssChargeInChargeGridPower(1340, ess)); + } + + private static EnergyScheduleHandler.WithDifferentStates.Period mockPeriod( + StateMachine state, int essChargeInChargeGrid) { + return new EnergyScheduleHandler.WithDifferentStates.Period(state, 0, + new EshContext(null, null, 0, false, 0, essChargeInChargeGrid), null, 0); } @Test @@ -135,19 +155,17 @@ public void testCalculateAutomaticMode() { calculateAutomaticMode(// new DummySum(), // new DummyManagedSymmetricEss("ess0"), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // /* limitChargePowerFor14aEnWG */ true, // - BALANCING)); + mockPeriod(BALANCING, /* essChargeInChargeGrid */ 1000))); assertEquals("Null-Check", new ApplyState(BALANCING, null), // calculateAutomaticMode(// new DummySum() // .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0"), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // /* limitChargePowerFor14aEnWG */ true, // - BALANCING)); + mockPeriod(BALANCING, /* essChargeInChargeGrid */ 1000))); assertEquals("BALANCING", new ApplyState(BALANCING, null), // calculateAutomaticMode(// @@ -155,10 +173,9 @@ public void testCalculateAutomaticMode() { .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // /* limitChargePowerFor14aEnWG */ true, // - BALANCING)); + mockPeriod(BALANCING, /* essChargeInChargeGrid */ 1000))); assertEquals("DELAY_DISCHARGE stays DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // calculateAutomaticMode(// @@ -166,20 +183,19 @@ public void testCalculateAutomaticMode() { .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // /* limitChargePowerFor14aEnWG */ true, // - DELAY_DISCHARGE)); + mockPeriod(DELAY_DISCHARGE, /* essChargeInChargeGrid */ 1000))); + assertEquals("DELAY_DISCHARGE to BALANCING", new ApplyState(BALANCING, null), // calculateAutomaticMode(// new DummySum() // .withGridActivePower(-500), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // /* limitChargePowerFor14aEnWG */ true, // - DELAY_DISCHARGE)); + mockPeriod(DELAY_DISCHARGE, /* essChargeInChargeGrid */ 1000))); assertEquals("CHARGE_GRID stays CHARGE_GRID", new ApplyState(CHARGE_GRID, -1400), // calculateAutomaticMode(// @@ -187,29 +203,78 @@ public void testCalculateAutomaticMode() { .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 2000, // /* limitChargePowerFor14aEnWG */ true, // - CHARGE_GRID)); + mockPeriod(CHARGE_GRID, /* essChargeInChargeGrid */ 1000))); + assertEquals("CHARGE_GRID to DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // calculateAutomaticMode(// new DummySum() // .withGridActivePower(100), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 400, // /* limitChargePowerFor14aEnWG */ true, // - CHARGE_GRID)); + mockPeriod(CHARGE_GRID, /* essChargeInChargeGrid */ 1000))); + assertEquals("CHARGE_GRID to BALANCING", new ApplyState(BALANCING, null), // calculateAutomaticMode(// new DummySum() // .withGridActivePower(-500), // new DummyManagedSymmetricEss("ess0") // .withActivePower(500), // - /* essChargeInChargeGrid */ 1000, // /* maxChargePowerFromGrid */ 0, // /* limitChargePowerFor14aEnWG */ true, // - CHARGE_GRID)); + mockPeriod(CHARGE_GRID, /* essChargeInChargeGrid */ 1000))); } + + @Test + public void testCalculateChargeEnergyInChargeGrid() { + assertEquals(1375, calculateChargeEnergyInChargeGrid(// + new GlobalSimulationsContext(CLOCK, TIME, ImmutableList.of(), // + new GlobalSimulationsContext.Grid(0, 20000), // + new GlobalSimulationsContext.Ess(0, 12223, 5000, 5000), // + ImmutableList.of()))); + + assertEquals(525, calculateChargeEnergyInChargeGrid(// + new GlobalSimulationsContext(CLOCK, TIME, ImmutableList.of(), // + new GlobalSimulationsContext.Grid(0, 20000), // + new GlobalSimulationsContext.Ess(0, 12223, 5000, 5000), // + ImmutableList.of(// + new GlobalSimulationsContext.Period.Quarter(TIME, 0, 1000, 0), // + new GlobalSimulationsContext.Period.Quarter(TIME, 100, 1100, 0), // + new GlobalSimulationsContext.Period.Quarter(TIME, 200, 0, 0) // + )))); + + assertEquals(538, calculateChargeEnergyInChargeGrid(// + new GlobalSimulationsContext(CLOCK, TIME, ImmutableList.of(), // + new GlobalSimulationsContext.Grid(0, 20000), // + new GlobalSimulationsContext.Ess(0, 12223, 5000, 5000), // + ImmutableList.of(// + new GlobalSimulationsContext.Period.Quarter(TIME, 0, 700, 123), // + new GlobalSimulationsContext.Period.Quarter(TIME, 100, 600, 123), // + new GlobalSimulationsContext.Period.Quarter(TIME, 200, 500, 125), // + new GlobalSimulationsContext.Period.Quarter(TIME, 300, 400, 126), // + new GlobalSimulationsContext.Period.Quarter(TIME, 400, 300, 123), // + new GlobalSimulationsContext.Period.Quarter(TIME, 500, 200, 122), // + new GlobalSimulationsContext.Period.Quarter(TIME, 600, 100, 121), // + new GlobalSimulationsContext.Period.Quarter(TIME, 700, 0, 121) // + )))); + + assertEquals(499, calculateChargeEnergyInChargeGrid(// + new GlobalSimulationsContext(CLOCK, TIME, ImmutableList.of(), // + new GlobalSimulationsContext.Grid(0, 20000), // + new GlobalSimulationsContext.Ess(0, 12223, 5000, 5000), // + ImmutableList.of(// + new GlobalSimulationsContext.Period.Quarter(TIME, 0, 700, 120), // + new GlobalSimulationsContext.Period.Quarter(TIME, 100, 600, 121), // + new GlobalSimulationsContext.Period.Quarter(TIME, 200, 500, 122), // + new GlobalSimulationsContext.Period.Quarter(TIME, 300, 1140, 126), // + new GlobalSimulationsContext.Period.Quarter(TIME, 400, 1150, 125), // + new GlobalSimulationsContext.Period.Quarter(TIME, 500, 200, 122), // + new GlobalSimulationsContext.Period.Quarter(TIME, 600, 100, 121), // + new GlobalSimulationsContext.Period.Quarter(TIME, 700, 0, 121) // + )))); + } + } diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponseTest.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponseTest.java new file mode 100644 index 00000000000..4372db782f4 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/GetScheduleResponseTest.java @@ -0,0 +1,242 @@ +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.common.utils.JsonUtils.getAsJsonArray; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyBalancing; +import static io.openems.edge.controller.ess.timeofusetariff.UtilsTest.CLOCK; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.CONSUMPTION_PREDICTION_QUARTERLY; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PAST_HOURLY_PRICES; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PAST_SOC; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PAST_STATES; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PRODUCTION_888_20231106; +import static io.openems.edge.controller.ess.timeofusetariff.jsonrpc.TestData.PRODUCTION_PREDICTION_QUARTERLY; +import static org.junit.Assert.assertEquals; + +import java.time.ZonedDateTime; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.UuidUtils; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImplTest; +import io.openems.edge.controller.ess.timeofusetariff.Utils; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; +import io.openems.edge.timedata.test.DummyTimedata; + +public class GetScheduleResponseTest { + + @Test + public void test() throws Exception { + final var now = roundDownToQuarter(ZonedDateTime.now(CLOCK)); + final var ess = new DummyManagedSymmetricEss("ess0") // + .withCapacity(10000); + final var model = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(model); + final var energyFlow = model.solve(); + + // Simulate historic data + final var timedata = new DummyTimedata("timedata0"); + final var fromDate = now.minusHours(3); + for (var i = 0; i < 12; i++) { + var quarter = fromDate.plusMinutes(i * 15); + timedata.add(quarter, new ChannelAddress("ctrl0", "QuarterlyPrices"), PAST_HOURLY_PRICES[i]); + timedata.add(quarter, new ChannelAddress("ctrl0", "StateMachine"), PAST_STATES[i]); + timedata.add(quarter, Utils.SUM_PRODUCTION, PRODUCTION_PREDICTION_QUARTERLY[i]); + timedata.add(quarter, Utils.SUM_CONSUMPTION, CONSUMPTION_PREDICTION_QUARTERLY[i]); + timedata.add(quarter, Utils.SUM_ESS_SOC, PAST_SOC[i]); + timedata.add(quarter, Utils.SUM_ESS_DISCHARGE_POWER, PRODUCTION_888_20231106[i]); + timedata.add(quarter, Utils.SUM_GRID, PRODUCTION_888_20231106[i]); + } + + // Simulate future Schedule + var ctrl = TimeOfUseTariffControllerImplTest.create(CLOCK, ess, timedata); + var esh = TimeOfUseTariffControllerImplTest.getEnergyScheduleHandler(ctrl); + ((AbstractEnergyScheduleHandler /* this is safe */) esh).initialize(new GlobalSimulationsContext(CLOCK, null, + null, null, new GlobalSimulationsContext.Ess(0, 0, 0, 0), ImmutableList.of())); + esh.applySchedule(ImmutableSortedMap.naturalOrder() // + .put(now.plusMinutes(0), new Period.Transition(1, 0.1, energyFlow, 5000)) // + .put(now.plusMinutes(15), new Period.Transition(0, 0.2, energyFlow, 6000)) // + .put(now.plusMinutes(30), new Period.Transition(0, 0.3, energyFlow, 7000)) // + .build()); + + final var gsr = GetScheduleResponse.from(UuidUtils.getNilUuid(), "ctrl0", CLOCK, ess, timedata, esh); + + var schedule = getAsJsonArray(gsr.getResult(), "schedule"); + + assertEquals(""" + [ + { + "timestamp": "1999-12-31T21:00:00Z", + "price": 158.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 1021, + "ess": 0, + "soc": 60 + }, + { + "timestamp": "1999-12-31T21:15:00Z", + "price": 160.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 1208, + "ess": 0, + "soc": 62 + }, + { + "timestamp": "1999-12-31T21:30:00Z", + "price": 171.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 713, + "ess": 0, + "soc": 64 + }, + { + "timestamp": "1999-12-31T21:45:00Z", + "price": 174.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 931, + "ess": 0, + "soc": 66 + }, + { + "timestamp": "1999-12-31T22:00:00Z", + "price": 161.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 2847, + "ess": 0, + "soc": 65 + }, + { + "timestamp": "1999-12-31T22:15:00Z", + "price": 152.0, + "state": 3, + "grid": 0, + "production": 0, + "consumption": 2551, + "ess": 0, + "soc": 67 + }, + { + "timestamp": "1999-12-31T22:30:00Z", + "price": 120.0, + "state": 3, + "grid": 0, + "production": 0, + "consumption": 1558, + "ess": 0, + "soc": 70 + }, + { + "timestamp": "1999-12-31T22:45:00Z", + "price": 111.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 1234, + "ess": 0, + "soc": 73 + }, + { + "timestamp": "1999-12-31T23:00:00Z", + "price": 105.0, + "state": 2, + "grid": 0, + "production": 0, + "consumption": 433, + "ess": 0, + "soc": 76 + }, + { + "timestamp": "1999-12-31T23:15:00Z", + "price": 105.0, + "state": 1, + "grid": 0, + "production": 0, + "consumption": 633, + "ess": 0, + "soc": 79 + }, + { + "timestamp": "1999-12-31T23:30:00Z", + "price": 74.0, + "state": 2, + "grid": 0, + "production": 0, + "consumption": 1355, + "ess": 0, + "soc": 83 + }, + { + "timestamp": "1999-12-31T23:45:00Z", + "price": 73.0, + "state": 2, + "grid": 0, + "production": 0, + "consumption": 606, + "ess": 0, + "soc": 87 + }, + { + "timestamp": "2000-01-01T00:00:00Z", + "price": 0.1, + "state": 0, + "grid": 0, + "production": 10000, + "consumption": 2000, + "ess": -8000, + "soc": 50 + }, + { + "timestamp": "2000-01-01T00:15:00Z", + "price": 0.2, + "state": 1, + "grid": 0, + "production": 10000, + "consumption": 2000, + "ess": -8000, + "soc": 60 + }, + { + "timestamp": "2000-01-01T00:30:00Z", + "price": 0.3, + "state": 1, + "grid": 0, + "production": 10000, + "consumption": 2000, + "ess": -8000, + "soc": 70 + } + ]""", JsonUtils.prettyToString(schedule)); + } + + @Test + public void testEmpty() throws OpenemsNamedException { + var response = GetScheduleResponse.empty(CLOCK, StateMachine.BALANCING).toList().get(0); + assertEquals(StateMachine.BALANCING.getValue(), JsonUtils.getAsInt(response, "state")); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/TestData.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/TestData.java similarity index 61% rename from io.openems.edge.energy/test/io/openems/edge/energy/TestData.java rename to io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/TestData.java index c89873819e7..a56e5a5097d 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/TestData.java +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/jsonrpc/TestData.java @@ -1,31 +1,26 @@ -package io.openems.edge.energy; - -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; - -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +package io.openems.edge.controller.ess.timeofusetariff.jsonrpc; public class TestData { // Edge 888; 06.11.2023 - public static final Integer[] PRODUCTION_888_20231106 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 12, 19, 24, 92, 301, 441, 653, 741, 1921, 1923, 1649, 2045, 2638, 3399, 4071, 4359, + protected static final Integer[] PRODUCTION_888_20231106 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 12, 19, 24, 92, 301, 441, 653, 741, 1921, 1923, 1649, 2045, 2638, 3399, 4071, 4359, 4516, 5541, 6993, 6292, 3902, 7700, 9098, 9555, 8119, 6868, 6560, 6380, 6193, 5389, 4349, 3743, 5367, 5319, 4383, 2243, 1122, 1315, 1107, 268, 48, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - public static final Integer[] CONSUMPTION_888_20231106 = { 308, 285, 384, 471, 480, 385, 464, 448, 288, 333, 346, + protected static final Integer[] CONSUMPTION_888_20231106 = { 308, 285, 384, 471, 480, 385, 464, 448, 288, 333, 346, 313, 1786, 332, 300, 259, 373, 358, 279, 308, 309, 415, 392, 299, 2913, 3105, 4416, 4442, 497, 5910, 4106, 2171, 3898, 922, 1601, 1088, 303, 2384, 430, 2428, 2899, 371, 613, 1663, 366, 2072, 456, 1589, 2004, 488, 199, 1628, 613, 198, 1796, 202, 1180, 4975, 4493, 5511, 7757, 2926, 2640, 4335, 2630, 2799, 5111, 2979, 3062, 4842, 4194, 4474, 4750, 4876, 1238, 1395, 1425, 1123, 3366, 4088, 418, 436, 3234, 1504, 1092, 1853, 365, 628, 2095, 552, 1113, 1808, 3223, 1629, 1329, 264 }; - public static final Double[] PRICES_888_20231106 = { 155., 152., 152., 152., 157., 172., 238., 266., 266., 241., + protected static final Double[] PRICES_888_20231106 = { 155., 152., 152., 152., 157., 172., 238., 266., 266., 241., 224., 219., 221., 232., 248., 271., 286., 316., 332., 318., 284., 278., 270., 257. }; - public static final Integer[] PRODUCTION_PREDICTION_QUARTERLY = { + protected static final Integer[] PRODUCTION_PREDICTION_QUARTERLY = { /* 00:00-03:45 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // /* 04:00-07:45 */ @@ -52,8 +47,7 @@ public class TestData { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // }; - public static final Integer[] CONSUMPTION_PREDICTION_QUARTERLY = { - + protected static final Integer[] CONSUMPTION_PREDICTION_QUARTERLY = { /* 00:00-03:450 */ 1021, 1208, 713, 931, 2847, 2551, 1558, 1234, 433, 633, 1355, 606, 430, 1432, 1121, 502, // /* 04:00-07:45 */ @@ -80,7 +74,7 @@ public class TestData { 3226, 2358, 1778, 1002, 455, 654, 534, 1587, 1638, 459, 330, 258, 368, 728, 1096, 878 // }; - public static final Double[] HOURLY_PRICES_SUMMER = { // + protected static final Double[] HOURLY_PRICES_SUMMER = { // 70.95, 71.98, 71.95, 74.96, // 78.93, 80., 84.01, 111.03, // 105.04, 105., 74.23, 73.28, // @@ -89,35 +83,19 @@ public class TestData { 149.99, 157.43, 130.9, 120.14 // }; - public static final StateMachine[] STATES = { // - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, DELAY_DISCHARGE, - BALANCING, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, BALANCING, - BALANCING, BALANCING, BALANCING, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE, - DELAY_DISCHARGE, DELAY_DISCHARGE, DELAY_DISCHARGE // - }; - - public static final Integer[] PAST_STATES = { // + protected static final Integer[] PAST_STATES = { // 1, 1, 1, 1, // 1, 3, 3, 1, // 2, 1, 2, 2, // }; - public static final Integer[] PAST_SOC = { // + protected static final Integer[] PAST_SOC = { // 60, 62, 64, 66, // 65, 67, 70, 73, // 76, 79, 83, 87, // }; - public static final Integer[] PAST_HOURLY_PRICES = { // + protected static final Integer[] PAST_HOURLY_PRICES = { // 158, 160, 171, 174, // 161, 152, 120, 111, // 105, 105, 74, 73, // diff --git a/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1Test.java b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1Test.java new file mode 100644 index 00000000000..cd5fd7bd569 --- /dev/null +++ b/io.openems.edge.controller.ess.timeofusetariff/test/io/openems/edge/controller/ess/timeofusetariff/v1/UtilsV1Test.java @@ -0,0 +1,101 @@ +package io.openems.edge.controller.ess.timeofusetariff.v1; + +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1.calculateAutomaticMode; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import io.openems.edge.common.sum.DummySum; +import io.openems.edge.controller.ess.timeofusetariff.Utils.ApplyState; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; + +@SuppressWarnings("deprecation") +public class UtilsV1Test { + + @Test + public void testCalculateAutomaticMode() { + assertEquals("Null-Check", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum(), // + new DummyManagedSymmetricEss("ess0"), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ true, // + BALANCING)); + assertEquals("Null-Check", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0"), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ true, // + BALANCING)); + + assertEquals("BALANCING", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ true, // + BALANCING)); + + assertEquals("DELAY_DISCHARGE stays DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ true, // + DELAY_DISCHARGE)); + assertEquals("DELAY_DISCHARGE to BALANCING", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(-500), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ true, // + DELAY_DISCHARGE)); + + assertEquals("CHARGE_GRID stays CHARGE_GRID", new ApplyState(CHARGE_GRID, -1400), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 2000, // + /* limitChargePowerFor14aEnWG */ true, // + CHARGE_GRID)); + assertEquals("CHARGE_GRID to DELAY_DISCHARGE", new ApplyState(DELAY_DISCHARGE, 0), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(100), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 400, // + /* limitChargePowerFor14aEnWG */ true, // + CHARGE_GRID)); + assertEquals("CHARGE_GRID to BALANCING", new ApplyState(BALANCING, null), // + calculateAutomaticMode(// + new DummySum() // + .withGridActivePower(-500), // + new DummyManagedSymmetricEss("ess0") // + .withActivePower(500), // + /* essChargeInChargeGrid */ 1000, // + /* maxChargePowerFromGrid */ 0, // + /* limitChargePowerFor14aEnWG */ true, // + CHARGE_GRID)); + } +} diff --git a/io.openems.edge.energy.api/bnd.bnd b/io.openems.edge.energy.api/bnd.bnd index 49a30e8f94b..e7874ed1370 100644 --- a/io.openems.edge.energy.api/bnd.bnd +++ b/io.openems.edge.energy.api/bnd.bnd @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.edge.controller.api,\ io.openems.edge.predictor.api,\ io.openems.edge.timeofusetariff.api,\ + org.apache.commons.math3,\ -testpath: \ ${testpath},\ diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyConstants.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyConstants.java new file mode 100644 index 00000000000..0cca2cc68df --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyConstants.java @@ -0,0 +1,15 @@ +package io.openems.edge.energy.api; + +import io.openems.common.types.ChannelAddress; + +public class EnergyConstants { + + public static final int PERIODS_PER_HOUR = 4; + + public static final ChannelAddress SUM_PRODUCTION = new ChannelAddress("_sum", "ProductionActivePower"); + public static final ChannelAddress SUM_UNMANAGED_CONSUMPTION = new ChannelAddress("_sum", + "UnmanagedConsumptionActivePower"); + + private EnergyConstants() { + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergySchedulable.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergySchedulable.java index cf2f7e66e0d..601a80179c8 100644 --- a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergySchedulable.java +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergySchedulable.java @@ -2,12 +2,12 @@ import io.openems.edge.controller.api.Controller; -public interface EnergySchedulable extends Controller { +public interface EnergySchedulable extends Controller { /** * Get the {@link EnergyScheduleHandler}. * * @return {@link EnergyScheduleHandler} */ - public EnergyScheduleHandler getEnergyScheduleHandler(); + public EnergyScheduleHandler getEnergyScheduleHandler(); } diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduleHandler.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduleHandler.java index ca5ca2ebc45..bad193c7dd6 100644 --- a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduleHandler.java +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduleHandler.java @@ -1,76 +1,477 @@ package io.openems.edge.energy.api; +import static com.google.common.base.MoreObjects.toStringHelper; import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import java.time.Clock; import java.time.ZonedDateTime; -import java.util.Optional; +import java.util.Arrays; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Supplier; +import java.util.stream.IntStream; -import com.google.common.collect.ImmutableMap; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableSortedMap; -public class EnergyScheduleHandler { +import io.openems.edge.controller.api.Controller; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.PostProcessor; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.OneSimulationContext; - private final Supplier availableStates; - private final Supplier context; - - private ImmutableMap> schedule = ImmutableMap.of(); - - public EnergyScheduleHandler(Supplier availableStates, Supplier context) { - this.availableStates = availableStates; - this.context = context; - } +public sealed interface EnergyScheduleHandler { /** - * Gets the available States. + * Creates an {@link EnergyScheduleHandler} for a {@link Controller} with + * different states that can be evaluated. * - * @return an Array of States + * @param the type of the State + * @param the type of the Context + * @param defaultState the default State if no other is explicitly scheduled + * @param statesSupplier a {@link Supplier} for available States + * @param contextFunction a {@link Function} to create a Context + * @param simulator a simulator that modifies a given {@link EnergyFlow} + * @return an {@link EnergyScheduleHandler} */ - public STATE[] getAvailableStates() { - return this.availableStates.get(); + public static EnergyScheduleHandler.WithDifferentStates of(// + STATE defaultState, // + Supplier statesSupplier, // + Function contextFunction, // + WithDifferentStates.Simulator simulator) { + return new EnergyScheduleHandler.WithDifferentStates(defaultState, statesSupplier, + contextFunction, simulator, EnergyScheduleHandler.WithDifferentStates.PostProcessor.doNothing()); } /** - * Gets the Context. + * Creates an {@link EnergyScheduleHandler} for a {@link Controller} with + * different states that can be evaluated. * - * @return the Context + * @param the type of the State + * @param the type of the Context + * @param defaultState the default State if no other is explicitly scheduled + * @param statesSupplier a {@link Supplier} for available States + * @param contextFunction a {@link Function} to create a Context + * @param simulator a simulator that modifies a given {@link EnergyFlow} + * @param postProcessor a {@link PostProcessor} + * @return an {@link EnergyScheduleHandler} */ - public CONTEXT getContext() { - return this.context.get(); - } - - public static record Period(STATE state, Integer essChargeInChargeGrid) { + public static EnergyScheduleHandler.WithDifferentStates of(// + STATE defaultState, // + Supplier statesSupplier, // + Function contextFunction, // + WithDifferentStates.Simulator simulator, // + WithDifferentStates.PostProcessor postProcessor) { + return new EnergyScheduleHandler.WithDifferentStates(defaultState, statesSupplier, + contextFunction, simulator, postProcessor); } /** - * Sets the Schedule. Called by Optimizer. + * Creates an {@link EnergyScheduleHandler} for a {@link Controller} with only a + * single state. * - * @param schedule the Schedule + * @param the type of the Context + * @param contextFunction a {@link Function} to create a Context + * @param simulator a simulator that modifies a given {@link EnergyFlow} + * @return an {@link EnergyScheduleHandler} */ - public synchronized void setSchedule(ImmutableMap> schedule) { - this.schedule = schedule; + public static EnergyScheduleHandler.WithOnlyOneState of(// + Function contextFunction, // + WithOnlyOneState.Simulator simulator) { + return new EnergyScheduleHandler.WithOnlyOneState(contextFunction, simulator); } /** - * Gets the current State or null. - * - * @return the State or null + * Triggers Rescheduling by the Energy Scheduler. */ - public synchronized STATE getCurrentState() { - return Optional.ofNullable(this.schedule.get(roundDownToQuarter(ZonedDateTime.now()))) // - .map(Period::state) // - .orElse(null); + public void triggerReschedule(); + + public abstract static sealed class AbstractEnergyScheduleHandler implements EnergyScheduleHandler { + + private final Function contextFunction; + + protected Clock clock; + protected CONTEXT context; + private Runnable onRescheduleCallback; + + public AbstractEnergyScheduleHandler(Function contextFunction) { + this.contextFunction = contextFunction; + } + + /** + * Initialize the {@link EnergyScheduleHandler}. + * + *

    + * This method is called internally before a Simulation is executed. + * + * @param asc the {@link GlobalSimulationsContext} + */ + public void initialize(GlobalSimulationsContext asc) { + this.clock = asc.clock(); + this.context = this.contextFunction.apply(asc); + } + + /** + * This method sets the callback for events that require Rescheduling. + * + * @param callback the {@link Runnable} callback + */ + public synchronized void setOnRescheduleCallback(Runnable callback) { + this.onRescheduleCallback = callback; + } + + /** + * This method removes the callback. + */ + public synchronized void removeOnRescheduleCallback() { + this.onRescheduleCallback = null; + } + + @Override + public void triggerReschedule() { + var onRescheduleCallback = this.onRescheduleCallback; + if (onRescheduleCallback != null) { + onRescheduleCallback.run(); + } + } + + protected ZonedDateTime getNow() { + var clock = this.clock; + if (clock != null) { + return ZonedDateTime.now(clock); + } + return ZonedDateTime.now(); + } + + protected void buildToString(MoreObjects.ToStringHelper toStringHelper) { + var context = this.context; + if (context != null) { + toStringHelper.addValue(context); + } + } } - // TODO hacky... find a better way! - /** - * Gets the current essChargeInChargeGrid or null. - * - * @return the essChargeInChargeGrid or null - */ - public synchronized Integer getCurrentEssChargeInChargeGrid() { - return Optional.ofNullable(this.schedule.get(roundDownToQuarter(ZonedDateTime.now()))) // - .map(Period::essChargeInChargeGrid) // - .orElse(null); + public static final class WithDifferentStates extends AbstractEnergyScheduleHandler { + + public static interface Simulator { + /** + * Simulates a Period. + * + * @param osc the {@link OneSimulationContext} + * @param period the {@link GlobalSimulationsContext.Period} + * @param model the {@link EnergyFlow.Model} + * @param context the Controller Context + * @param state the simulated State + */ + public void simulate(OneSimulationContext osc, GlobalSimulationsContext.Period period, + EnergyFlow.Model model, CONTEXT context, STATE state); + } + + public static interface PostProcessor { + + /** + * A 'do-nothing' {@link PostProcessor}. + * + * @param the type of the State + * @return the same State + */ + public static PostProcessor doNothing() { + return (energyFlow, state) -> state; + } + + /** + * Post-Process a state of a Period during Simulation, i.e. replace with + * 'better' state with the equivalent behaviour. + * + *

    + * NOTE: heavy computation is ok here, because this method is called only at the + * end with the best Schedule. + * + * @param energyFlow the {@link EnergyFlow} + * @param state the initial state + * @return the new state + */ + public STATE postProcess(EnergyFlow energyFlow, STATE state); + } + + private final STATE defaultState; + private final Supplier availableStatesSupplier; + private final Simulator simulator; + private final WithDifferentStates.PostProcessor postProcessor; + private final SortedMap> schedule = new TreeMap<>(); + + private STATE[] availableStates; + + private WithDifferentStates(// + STATE defaultState, // + Supplier availableStatesSupplier, // + Function contextFunction, // + Simulator simulator, // + WithDifferentStates.PostProcessor postProcessor) { + super(contextFunction); + this.defaultState = defaultState; + this.availableStatesSupplier = availableStatesSupplier; + this.simulator = simulator; + this.postProcessor = postProcessor; + } + + @Override + public void initialize(GlobalSimulationsContext asc) { + super.initialize(asc); + this.availableStates = this.availableStatesSupplier.get(); + } + + /** + * Gets the default State. + * + * @return the default State + */ + public STATE getDefaultState() { + return this.defaultState; + } + + /** + * Gets the index of the default State. + * + * @return the index of the default State + */ + public int getDefaultStateIndex() { + var states = this.availableStates; + if (states == null) { + throw new IllegalAccessError( + "EnergySchedulerHandler is uninitialized. `initialize()` must be called first."); + } + return IntStream.range(0, states.length) // + .filter(i -> states[i] == this.defaultState) // + .findFirst() // + .orElse(0 /* fallback */); + } + + /** + * Gets the available States. + * + * @return an Array of States + */ + public STATE[] getAvailableStates() { + return this.availableStates; + } + + /** + * Simulates a Period. + * + * @param osc the {@link OneSimulationContext} + * @param period the simulated {@link GlobalSimulationsContext.Period} + * @param model the {@link EnergyFlow.Model} + * @param stateIndex the index of the simulated state + */ + public void simulatePeriod(OneSimulationContext osc, GlobalSimulationsContext.Period period, + EnergyFlow.Model model, int stateIndex) { + this.simulator.simulate(osc, period, model, this.context, this.availableStates[stateIndex]); + } + + /** + * Post-processes a Period of the best Schedule. + * + *

    + * This method is called internally after the Simulations are executed with the + * found best Schedule. + * + * @param period the {@link GlobalSimulationsContext.Period} + * @param osc the {@link OneSimulationContext} + * @param energyFlow the {@link EnergyFlow} + * @param stateIndex the index of the simulated state + * @return the post-processed state index + */ + public int postProcessPeriod(GlobalSimulationsContext.Period period, OneSimulationContext osc, + EnergyFlow energyFlow, int stateIndex) { + return this.getStateIndex(this.postProcessor.postProcess(energyFlow, this.availableStates[stateIndex])); + } + + public static record Period( + /** STATE of the Period */ + STATE state, + /** Price [1/MWh] */ + double price, // + /** EnergyScheduleHandler Context */ + CONTEXT context, // + /** Simulated EnergyFlow */ + EnergyFlow energyFlow, // + /** the initial ESS energy in the beginning of the period in [Wh] */ + int essInitialEnergy) { + + /** + * This class is only used internally to apply the Schedule. + */ + public static record Transition(int stateIndex, double price, EnergyFlow energyFlow, int essInitialEnergy) { + } + + /** + * Builds a {@link EnergyScheduleHandler.WithDifferentStates.Period} from a + * {@link EnergyScheduleHandler.WithDifferentStates.Period.Transition} record. + * + * @param the type of the State + * @param the type of the Context + * @param t the + * {@link EnergyScheduleHandler.WithDifferentStates.Period.Transition} + * record + * @param getState a method to translate a 'stateIndex' to a STATE + * @param context the CONTEXT used during simulation + * @return a {@link Period} record + */ + public static Period fromTransitionRecord(Period.Transition t, + IntFunction getState, CONTEXT context) { + return new Period<>(getState.apply(t.stateIndex), t.price, context, t.energyFlow, t.essInitialEnergy); + } + } + + /** + * Applies a new Schedule. + * + *

    + * This method is called by the {@link EnergyScheduler}. + * + * @param schedule the new Schedule as Map of ZonedDateTime to State-Index + */ + public void applySchedule(ImmutableSortedMap schedule) { + final var thisQuarter = roundDownToQuarter(this.getNow()); + final var nextQuarter = thisQuarter.plusMinutes(15); + final var currentContext = this.context; + synchronized (this.schedule) { + // Clear outdated entries + this.schedule.headMap(thisQuarter).clear(); + + // Remove future entries + this.schedule.tailMap(nextQuarter).clear(); + + // Update entries from param + var states = this.availableStates; + if (states.length == 0) { + System.err.println("States is empty!"); // TODO proper log + return; + } + schedule.forEach((k, t) -> { + this.schedule.put(k, Period.fromTransitionRecord(t, this::getState, currentContext)); + }); + } + } + + /** + * Gets a copy of the current Schedule. + * + * @return the Schedule + */ + public ImmutableSortedMap> getSchedule() { + synchronized (this.schedule) { + return ImmutableSortedMap.copyOfSorted(this.schedule); + } + } + + /** + * Gets the current {@link Period} record. + * + * @return the record of the currently scheduled Period; possibly null + */ + public Period getCurrentPeriod() { + synchronized (this.schedule) { + final var thisQuarter = roundDownToQuarter(this.getNow()); + return this.schedule.get(thisQuarter); + } + } + + /** + * Gets the string representation for the given stateIndex. + * + * @param stateIndex the index of the state + * @return string representation + */ + public String toStateString(int stateIndex) { + return this.getState(stateIndex).toString(); + } + + /** + * Gets the STATE for the given stateIndex. + * + * @param stateIndex the stateIndex + * @return the STATE + */ + private STATE getState(int stateIndex) { + var states = this.availableStates; + return stateIndex < states.length // + ? states[stateIndex] // + : this.defaultState; + } + + /** + * Gets the stateIndex for the given STATE. + * + * @param state the STATE + * @return the stateIndex; or zero if not found + */ + private int getStateIndex(STATE state) { + var states = this.availableStates; + for (var i = 0; i < states.length; i++) { + if (states[i] == state) { + return i; + } + } + return 0; + } + + @Override + public String toString() { + var toStringHelper = toStringHelper("ESH.WithDifferentStates"); + var availableStates = this.availableStates; + if (availableStates != null) { + toStringHelper.add("availableStates", Arrays.toString(availableStates)); + } + super.buildToString(toStringHelper); + return toStringHelper.toString(); + } } + public static final class WithOnlyOneState extends AbstractEnergyScheduleHandler { + + public static interface Simulator { + /** + * Simulates a Period. + * + * @param osc the {@link OneSimulationContext} + * @param period the {@link GlobalSimulationsContext.Period} + * @param model the {@link EnergyFlow.Model} + * @param context the Controller Context + */ + public void simulate(OneSimulationContext osc, GlobalSimulationsContext.Period period, + EnergyFlow.Model model, CONTEXT context); + } + + private final Simulator simulator; + + private WithOnlyOneState(// + Function contextFunction, // + Simulator simulator) { + super(contextFunction); + this.simulator = simulator; + } + + /** + * Simulates a Period. + * + * @param simContext the {@link OneSimulationContext} + * @param period the {@link GlobalSimulationsContext.Period} + * @param model the {@link EnergyFlow.Model} + */ + public void simulatePeriod(OneSimulationContext simContext, GlobalSimulationsContext.Period period, + EnergyFlow.Model model) { + this.simulator.simulate(simContext, period, model, this.context); + } + + @Override + public String toString() { + var toStringHelper = toStringHelper("ESH.WithOnlyOneState"); + super.buildToString(toStringHelper); + return toStringHelper.toString(); + } + } } diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduler.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduler.java index 7d58264b26b..f48c0344a78 100644 --- a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduler.java +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyScheduler.java @@ -1,19 +1,22 @@ package io.openems.edge.energy.api; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.base.JsonrpcResponse; +import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.jsonapi.ComponentJsonApi; +import io.openems.edge.common.jsonapi.Call; /** * The global Energy Schedule optimizer singleton. */ -public interface EnergyScheduler extends OpenemsComponent, ComponentJsonApi { +public interface EnergyScheduler extends OpenemsComponent { public static final String SINGLETON_SERVICE_PID = "Core.Energy"; public static final String SINGLETON_COMPONENT_ID = "_energy"; public enum ChannelId implements io.openems.edge.common.channel.ChannelId { - ; + SIMULATIONS_PER_QUARTER(Doc.of(OpenemsType.INTEGER)); private final Doc doc; @@ -26,4 +29,14 @@ public Doc doc() { return this.doc; } } + + /** + * Handles a GetScheduleRequest. + * + * @param call the JsonApi {@link Call} + * @param id the Component-ID of the Controller + * @return the GetScheduleResponse + */ + @Deprecated + public JsonrpcResponse handleGetScheduleRequestV1(Call call, String id); } diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyUtils.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyUtils.java new file mode 100644 index 00000000000..e499135eade --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/EnergyUtils.java @@ -0,0 +1,125 @@ +package io.openems.edge.energy.api; + +import static io.openems.edge.common.type.TypeUtils.multiply; +import static io.openems.edge.common.type.TypeUtils.orElse; +import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR; +import static java.util.Arrays.stream; + +import java.util.Objects; +import java.util.stream.IntStream; + +public class EnergyUtils { + + private EnergyUtils() { + } + + /** + * Converts a State-of-Charge [%] to Energy [Wh]. + * + * @param totalEnergy the total energy in [Wh] + * @param soc the State-of-Charge in [%] + * @return the energy in [Wh] + */ + public static int socToEnergy(int totalEnergy, int soc) { + return totalEnergy /* [Wh] */ / 100 * soc; + } + + /** + * Finds the first valley in an array of doubles, e.g. prices. + * + * @param fromIndex start searching from this index + * @param values the values array + * @return the index of the valley + */ + public static int findFirstValleyIndex(int fromIndex, double[] values) { + if (values.length <= fromIndex) { + return fromIndex; + } else { + var previous = values[fromIndex]; + for (var i = fromIndex + 1; i < values.length; i++) { + var value = values[i]; + if (value > previous) { + return i - 1; + } + previous = value; + } + } + return values.length - 1; + } + + /** + * Finds the first peak in an array of doubles, e.g. prices. + * + * @param fromIndex start searching from this index + * @param values the values array + * @return the index of the peak + */ + public static int findFirstPeakIndex(int fromIndex, double[] values) { + if (values.length <= fromIndex) { + return fromIndex; + } else { + var previous = values[fromIndex]; + for (var i = fromIndex + 1; i < values.length; i++) { + var value = values[i]; + if (value < previous) { + return i - 1; + } + previous = value; + } + } + return values.length - 1; + } + + /** + * Converts power [W] to energy [Wh/15 min]. + * + * @param power the power value + * @return the energy value + */ + public static int toEnergy(int power) { + return power / PERIODS_PER_HOUR; + } + + /** + * Converts energy [Wh/15 min] to power [W]. + * + * @param energy the energy value + * @return the power value + */ + public static Integer toPower(Integer energy) { + return multiply(energy, PERIODS_PER_HOUR); + } + + /** + * Interpolate an Array of {@link Integer}s. + * + *

    + * Replaces nulls with previous value. If first entry is null, it is set to + * first available value. If all values are null, all are set to 0. + * + * @param values the values + * @return values without nulls + */ + public static int[] interpolateArray(Integer[] values) { + var firstNonNull = stream(values) // + .filter(Objects::nonNull) // + .findFirst(); + var lastNonNullIndex = IntStream.range(0, values.length) // + .filter(i -> values[i] != null) // + .reduce((first, second) -> second); // + if (lastNonNullIndex.isEmpty()) { + return new int[0]; + } + var result = new int[lastNonNullIndex.getAsInt() + 1]; + if (firstNonNull.isEmpty()) { + // all null + return result; + } + int last = firstNonNull.get(); + for (var i = 0; i < result.length; i++) { + int value = orElse(values[i], last); + result[i] = last = value; + } + return result; + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/Version.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/Version.java new file mode 100644 index 00000000000..41012cf10de --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/Version.java @@ -0,0 +1,20 @@ +package io.openems.edge.energy.api; + +public enum Version { + /** + * Version 1. + * + *

    + * Well tested and production ready, but applies only to + * "Controller.Ess.Time-Of-Use-Tariff". + */ + V1_ESS_ONLY, // + /** + * Version 1. + * + *

    + * Work-in-progress that uses new EnergySchedulable interface to provide real + * multi-objective optimization. + */ + V2_ENERGY_SCHEDULABLE +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/Coefficient.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/Coefficient.java new file mode 100644 index 00000000000..14fa453d462 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/Coefficient.java @@ -0,0 +1,36 @@ +package io.openems.edge.energy.api.simulation; + +import static com.google.common.base.CaseFormat.UPPER_CAMEL; +import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; + +public enum Coefficient { + /* _sum/Production; positive */ + PROD, + /* _sum/Consumption; positive */ + CONS, + /* _sum/EssActivePower; charge negative; discharge positive */ + ESS, + /* _sum/EssActivePower; sell-to-grid negative, buy-from-grid positive */ + GRID, + /* Production -> Consumption, positive */ + PROD_TO_CONS, + /* Production -> Grid, positive */ + PROD_TO_GRID, + /* Production -> ESS, positive */ + PROD_TO_ESS, + /* Grid -> Consumption, positive */ + GRID_TO_CONS, + /* ESS -> Consumption, positive */ + ESS_TO_CONS, + /* Grid -> ESS, discharge-to-grid negative, charge-from-grid positive */ + GRID_TO_ESS; + + /** + * Gets the {@link Coefficient#name()} in CamelCase. + * + * @return name + */ + public String toCamelCase() { + return UPPER_UNDERSCORE.to(UPPER_CAMEL, this.name()); + } +} \ No newline at end of file diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/EnergyFlow.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/EnergyFlow.java new file mode 100644 index 00000000000..4984c83a8ae --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/EnergyFlow.java @@ -0,0 +1,657 @@ +package io.openems.edge.energy.api.simulation; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static io.openems.edge.energy.api.simulation.Coefficient.CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.ESS_TO_CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.GRID_TO_ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_CONS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_ESS; +import static io.openems.edge.energy.api.simulation.Coefficient.PROD_TO_GRID; +import static java.lang.Double.NaN; +import static java.lang.Math.min; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static org.apache.commons.math3.optim.linear.Relationship.EQ; +import static org.apache.commons.math3.optim.linear.Relationship.GEQ; +import static org.apache.commons.math3.optim.linear.Relationship.LEQ; +import static org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MAXIMIZE; +import static org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MINIMIZE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +import org.apache.commons.math3.exception.MathIllegalStateException; +import org.apache.commons.math3.optim.PointValuePair; +import org.apache.commons.math3.optim.linear.LinearConstraint; +import org.apache.commons.math3.optim.linear.LinearConstraintSet; +import org.apache.commons.math3.optim.linear.LinearObjectiveFunction; +import org.apache.commons.math3.optim.linear.Relationship; +import org.apache.commons.math3.optim.linear.SimplexSolver; +import org.apache.commons.math3.optim.nonlinear.scalar.GoalType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period; + +/** + * Holds the {@link Solution} of an {@link EnergyFlow.Model} and provides helper + * functions to access the individual {@link Coefficient}s. + */ +public class EnergyFlow { + + private static final Logger LOG = LoggerFactory.getLogger(EnergyFlow.class); + + private final double[] point; + + private EnergyFlow(PointValuePair pvp) { + this.point = pvp.getPointRef(); + } + + /** + * Gets {@link Coefficient#PROD}. + * + * @return the value + */ + public int getProd() { + return this.getValue(PROD); + } + + /** + * Gets {@link Coefficient#CONS}. + * + * @return the value + */ + public int getCons() { + return this.getValue(CONS); + } + + /** + * Gets {@link Coefficient#ESS}. + * + * @return the value + */ + public int getEss() { + return this.getValue(ESS); + } + + /** + * Gets {@link Coefficient#GRID}. + * + * @return the value + */ + public int getGrid() { + return this.getValue(GRID); + } + + /** + * Gets {@link Coefficient#PROD_TO_CONS}. + * + * @return the value + */ + public int getProdToCons() { + return this.getValue(PROD_TO_CONS); + } + + /** + * Gets {@link Coefficient#PROD_TO_ESS}. + * + * @return the value + */ + public int getProdToEss() { + return this.getValue(PROD_TO_ESS); + } + + /** + * Gets {@link Coefficient#PROD_TO_GRID}. + * + * @return the value + */ + public int getProdToGrid() { + return this.getValue(PROD_TO_GRID); + } + + /** + * Gets {@link Coefficient#GRID_TO_CONS}. + * + * @return the value + */ + public int getGridToCons() { + return this.getValue(GRID_TO_CONS); + } + + /** + * Gets {@link Coefficient#GRID_TO_ESS}. + * + * @return the value + */ + public int getGridToEss() { + return this.getValue(GRID_TO_ESS); + } + + /** + * Gets {@link Coefficient#ESS_TO_CONS}. + * + * @return the value + */ + public int getEssToCons() { + return this.getValue(ESS_TO_CONS); + } + + private int getValue(Coefficient coefficient) { + return toInt(this.point[coefficient.ordinal()]); + } + + /** + * Prints all {@link Coefficient}s and their values line by line. + */ + public void print() { + for (var c : Coefficient.values()) { + LOG.info(c.toCamelCase() + ": " + this.getValue(c)); + } + } + + @Override + public String toString() { + return toStringHelper(this) // + .addValue(stream(Coefficient.values()) // + .map(c -> new StringBuilder() // + .append(c.toCamelCase()) // + .append("=") // + .append(this.getValue(c)) // + .toString()) // + .collect(joining(", "))) // + .toString(); + } + + /** + * Models an EnergyFlow as a Linear Equation System with defined + * {@link Coefficient}s for GRID, ESS, CONS, etc. + */ + public static class Model { + + /** + * Generates a {@link EnergyFlow.Model} from a {@link OneSimulationContext} and + * a {@link Period}. + * + * @param osc the {@link OneSimulationContext} + * @param period the {@link Period} + * @return a new {@link EnergyFlow.Model} + */ + public static EnergyFlow.Model from(OneSimulationContext osc, Period period) { + final int factor; // TODO replace with switch in Java 21 + if (period instanceof GlobalSimulationsContext.Period.Hour) { + factor = 4; + } else { + factor = 1; + } + final var ess = osc.global.ess(); + final var grid = osc.global.grid(); + return new EnergyFlow.Model(// + /* production */ period.production(), // + /* consumption */ period.consumption(), // + /* essMaxCharge */ min(ess.maxChargeEnergy() * factor, ess.totalEnergy() - osc.getEssInitial()), // + /* essMaxDischarge */ min(ess.maxDischargeEnergy() * factor, osc.getEssInitial()), // + /* gridMaxBuy */ grid.maxBuy() * factor, // + /* gridMaxSell */ grid.maxSell() * factor); + } + + public final int production; + public final int consumption; + public final int essMaxCharge; + public final int essMaxDischarge; + public final int gridMaxBuy; + public final int gridMaxSell; + + private final List constraints = new ArrayList(); + + public Model(int production, int consumption, int essMaxCharge, int essMaxDischarge, int gridMaxBuy, + int gridMaxSell) { + this.production = production; + this.consumption = consumption; + this.essMaxCharge = essMaxCharge; + this.essMaxDischarge = essMaxDischarge; + this.gridMaxBuy = gridMaxBuy; + this.gridMaxSell = gridMaxSell; + + this + // Internal Relationships + .addConstraint(c -> c // Sum + .setCoefficient(PROD, 1) // + .setCoefficient(ESS, 1) // + .setCoefficient(GRID, 1) // + .setCoefficient(CONS, -1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(c -> c // Distribute Production + .setCoefficient(PROD, -1) // + .setCoefficient(PROD_TO_CONS, 1) // + .setCoefficient(PROD_TO_ESS, 1) // + .setCoefficient(PROD_TO_GRID, 1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(b -> b // Distribute Consumption + .setCoefficient(CONS, 1) // + .setCoefficient(ESS_TO_CONS, -1) // + .setCoefficient(GRID_TO_CONS, -1) // + .setCoefficient(PROD_TO_CONS, -1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(b -> b // Distribute Grid + .setCoefficient(GRID, -1) // + .setCoefficient(PROD_TO_GRID, -1) // + .setCoefficient(GRID_TO_CONS, 1) // + .setCoefficient(GRID_TO_ESS, 1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(b -> b // Distribute ESS + .setCoefficient(ESS, -1) // + .setCoefficient(PROD_TO_ESS, -1) // + .setCoefficient(ESS_TO_CONS, 1) // + .setCoefficient(GRID_TO_ESS, -1) // + .toLinearConstraint(EQ, 0)) // + .addConstraint(b -> b // Only Positive PROD_TO_ESS + .setCoefficient(PROD_TO_ESS, 1) // + .toLinearConstraint(GEQ, 0)) // + .addConstraint(b -> b // Only Positive PROD_TO_GRID + .setCoefficient(PROD_TO_GRID, 1) // + .toLinearConstraint(GEQ, 0)) // + .addConstraint(b -> b // Only Positive ESS_TO_CONS + .setCoefficient(ESS_TO_CONS, 1) // + .toLinearConstraint(GEQ, 0)) // + .addConstraint(b -> b // Only Positive GRID_TO_CONS + .setCoefficient(GRID_TO_CONS, 1) // + .toLinearConstraint(GEQ, 0)) + + // Production & Consumption + .addConstraint(c -> c // + .setCoefficient(PROD, 1) // + .toLinearConstraint(EQ, production)) // + .addConstraint(c -> c // + .setCoefficient(CONS, 1) // + .toLinearConstraint(EQ, consumption)) + .addConstraint(b -> b // PROD_TO_CONS + .setCoefficient(PROD_TO_CONS, 1) // + .toLinearConstraint(EQ, min(production, consumption))) + + // ESS Max Charge/Discharge + .addConstraint(c -> c // + .setCoefficient(ESS, 1) // + .toLinearConstraint(GEQ, -essMaxCharge)) // + .addConstraint(c -> c // + .setCoefficient(ESS, 1) // + .toLinearConstraint(LEQ, essMaxDischarge)) // + // Grid Max Buy/Sell + .addConstraint(c -> c // + .setCoefficient(GRID, 1) // + .toLinearConstraint(LEQ, gridMaxBuy)) // + .addConstraint(c -> c // + .setCoefficient(GRID, 1) // + .toLinearConstraint(GEQ, -gridMaxSell)); + } + + /** + * Sets the {@link Coefficient#ESS} Charge/Discharge Energy to the given value, + * while making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setEss(int value) { + return this.setFittingCoefficientValue(ESS, EQ, value); + } + + /** + * Limits the {@link Coefficient#ESS} Charge Energy to the given value, while + * making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setEssMaxCharge(int value) { + return this.setFittingCoefficientValue(ESS, GEQ, -value); + } + + /** + * Limits the {@link Coefficient#ESS} Discharge Energy to the given value, while + * making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setEssMaxDischarge(int value) { + return this.setFittingCoefficientValue(ESS, LEQ, value); + } + + /** + * Limits the {@link Coefficient#GRID} Buy Energy to the given value, while + * making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setGridMaxBuy(int value) { + return this.setFittingCoefficientValue(ESS, LEQ, value); + } + + /** + * Limits the {@link Coefficient#GRID} Sell Energy to the given value, while + * making sure the value fits in the active constraints. + * + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setGridMaxSell(int value) { + return this.setFittingCoefficientValue(ESS, GEQ, -value); + } + + /** + * Prints a table with all constraints. + */ + public void logConstraints() { + { + var b = new StringBuilder(); + for (var coefficient : Coefficient.values()) { + b.append(String.format("%s ", coefficient.toCamelCase())); + } + LOG.info(b.toString()); + } + for (var constraint : this.constraints) { + var b = new StringBuilder(); + var equation = constraint.getCoefficients(); + for (var coefficient : Coefficient.values()) { + b.append(String.format("% " + coefficient.name().length() + ".0f ", + equation.getEntry(coefficient.ordinal()))); + } + b.append(String.format("%2s % 10.0f", constraint.getRelationship(), constraint.getValue())); + LOG.info(b.toString()); + } + } + + /** + * Prints min/max values for a {@link Coefficient}. + * + * @param coefficient the {@link Coefficient} + */ + public void logMinMaxValues(Coefficient coefficient) { + var values = this.calculateMinMaxValues(coefficient); + var min = values[0]; + var max = values[1]; + LOG.info(String.format("%-12s % 5.0f % 5.0f %s", coefficient.toCamelCase(), min, max, + min == max ? "fixed" : "")); + } + + /** + * Prints a table with all constraints. + */ + public void logMinMaxValues() { + LOG.info(String.format("%-12s %5s %5s", "Coefficient", "Min", "Max")); + for (var coefficient : Coefficient.values()) { + this.logMinMaxValues(coefficient); + } + } + + @Override + public String toString() { + return "EnergyFlow.Model[" // + + Arrays.stream(Coefficient.values()) // + .map(coefficient -> { + var values = this.calculateMinMaxValues(coefficient); + var min = values[0]; + var max = values[1]; + var b = new StringBuilder().append(coefficient.toCamelCase()) // + .append("=") // + .append(min); + if (min == max) { + b // + .append("|fixed"); + } else { + b // + .append("|") // + .append(max); // + } + return b.toString(); + }) // + .collect(joining(",")) // + + "]"; + } + + /** + * Calculates the current Min and Max values for a given {@link Coefficient}. + * + * @param coefficient the {@link Coefficient} + * @return result[0] is the Min value; result[1] is the Max value + */ + private double[] calculateMinMaxValues(Coefficient coefficient) { + final double[] result = new double[2]; + try { + result[0] = this.getExtremeCoefficientValue(coefficient, MINIMIZE); + } catch (MathIllegalStateException e) { + result[0] = NaN; + } + try { + result[1] = this.getExtremeCoefficientValue(coefficient, MAXIMIZE); + } catch (MathIllegalStateException e) { + result[1] = NaN; + } + return result; + } + + private EnergyFlow.Model addConstraint(Function coefficients) { + this.constraints.add(coefficients.apply(new Coefficients())); + return this; + } + + /** + * Gets the minimum or maximum allowed value for the given {@link Coefficient}. + * + * @param coefficient the {@link Coefficient} + * @param goalType the {@link GoalType} + * @return the value + * @throws MathIllegalStateException if this {@link EnergyFlow.Model} is + * unsolvable + */ + public double getExtremeCoefficientValue(Coefficient coefficient, GoalType goalType) + throws MathIllegalStateException { + return solve(goalType, this.constraints, Coefficients.create() // + .setCoefficient(coefficient, 1) // + .toLinearObjectiveFunction(0)) // + .getPointRef()[coefficient.ordinal()]; + } + + /** + * Adds a {@link LinearConstraint} that sets the given {@link Coefficient} to + * the minimum or maximum allowed value. + * + * @param coefficient the {@link Coefficient} + * @param goalType the {@link GoalType} + */ + public void setExtremeCoefficientValue(Coefficient coefficient, GoalType goalType) { + try { + var value = this.getExtremeCoefficientValue(coefficient, goalType); + this.setCoefficientValue(coefficient, value); + } catch (MathIllegalStateException e) { + LOG.warn("[setExtremeCoefficientValue] " // + + "Unable to " + goalType + " " + coefficient + ": " + e.getMessage() + " " // + + this.toString()); + } + } + + /** + * Adds a {@link LinearConstraint} that sets the given {@link Coefficient} to + * the given value, while making sure the value fits in the active constraints. + * + * @param coefficient the {@link Coefficient} + * @param relationship the {@link Relationship}l + * @param value the value + * @return actually set value; {@link Double#NaN} on error + */ + public double setFittingCoefficientValue(Coefficient coefficient, Relationship relationship, double value) { + // Fit to MIN value + try { + var min = this.getExtremeCoefficientValue(coefficient, MINIMIZE); + if (value <= min) { + this.setCoefficientValue(coefficient, relationship, min); + return min; + } + } catch (MathIllegalStateException e) { + LOG.warn("[setFittingCoefficientValue] " // + + "Unable to MINIMIZE " + coefficient + ": " + e.getMessage() + " " // + + this.toString()); + return NaN; + } + + // Fit to MAX value + try { + var max = this.getExtremeCoefficientValue(coefficient, MAXIMIZE); + if (value > max) { + this.setCoefficientValue(coefficient, relationship, max); + return max; + } + } catch (MathIllegalStateException e) { + LOG.warn("[setFittingCoefficientValue] " // + + "Unable to MAXIMIZE " + coefficient + ": " + e.getMessage() + " " // + + this.toString()); + return NaN; + } + + // Apply coefficient value + this.setCoefficientValue(coefficient, relationship, value); + return value; + } + + /** + * Adds a {@link LinearConstraint} that sets the given {@link Coefficient} to + * the given value. + * + * @param coefficient the {@link Coefficient} + * @param value the value + */ + private void setCoefficientValue(Coefficient coefficient, double value) { + this.setCoefficientValue(coefficient, Relationship.EQ, value); + } + + /** + * Adds a {@link LinearConstraint} that constrains the given {@link Coefficient} + * to the given value and {@link Relationship}. + * + * @param coefficient the {@link Coefficient} + * @param relationship the {@link Relationship} + * @param value the value + */ + private void setCoefficientValue(Coefficient coefficient, Relationship relationship, double value) { + this.addConstraint(c -> c // + .setCoefficient(coefficient, 1) // + .toLinearConstraint(relationship, value)); + } + + /** + * Solves the {@link EnergyFlow.Model} and returns an {@link EnergyFlow}. + * + * @return the {@link EnergyFlow}; null if this {@link EnergyFlow.Model} is + * unsolvable + */ + public EnergyFlow solve() { + final double ess; + try { + ess = this.getExtremeCoefficientValue(ESS, MAXIMIZE); + } catch (MathIllegalStateException e) { + LOG.warn("[solve] " // + + "Unable to MAXIMIZE ESS: " + e.getMessage() + " " // + + this.toString()); + return null; + } + if (ess <= 0) { + // ESS Charge or Zero; GRID_TO_ESS must be >= 0 + this.setFittingCoefficientValue(GRID_TO_ESS, GEQ, 0); + this.setFittingCoefficientValue(ESS_TO_CONS, EQ, 0); + } + if (ess >= 0) { + // ESS Discharge or Zero + // Maximize ESS_TO_CONS (1st prio: PROD_TO_CONS; 3rd prio: GRID_TO_CONS) + final double essMax; + try { + essMax = this.getExtremeCoefficientValue(ESS_TO_CONS, MAXIMIZE); + } catch (MathIllegalStateException e) { + LOG.warn("[solve] " // + + "Unable to MAXIMIZE ESS_TO_CONS: " + e.getMessage() + " " // + + this.toString()); + return null; + } + this.setCoefficientValue(ESS_TO_CONS, min(essMax, ess)); + } + + var coefficients = initializeCoefficients(); + Arrays.fill(coefficients, 1); + try { + return new EnergyFlow(solve(MINIMIZE, this.constraints, new LinearObjectiveFunction(coefficients, 0))); + } catch (MathIllegalStateException e) { + LOG.warn("[solve] " // + + "Unable to solve EnergyFlow.Model: " + e.getMessage() + " " // + + this.toString()); + return null; + } + } + + /** + * Solves the linear equation system. + * + * @param goalType {@link GoalType#MINIMIZE} or + * {@link GoalType#MAXIMIZE} the objective function + * @param constraints the {@link LinearConstraint}s + * @param objectiveFunction the {@link LinearObjectiveFunction} + * @return the {@link PointValuePair} + * @throws MathIllegalStateException if this {@link EnergyFlow.Model} is + * unsolvable + */ + private static PointValuePair solve(GoalType goalType, Collection constraints, + LinearObjectiveFunction objectiveFunction) throws MathIllegalStateException { + return new SimplexSolver().optimize(// + objectiveFunction, // + new LinearConstraintSet(constraints), // + goalType); + } + } + + /** + * Helper class to provides a Builder-Pattern like way to create a coefficients + * array suitable for a {@link LinearConstraint} or + * {@link LinearObjectiveFunction}. + */ + private static class Coefficients { + + private static Coefficients create() { + return new Coefficients(); + } + + private final double[] coefficients; + + private Coefficients() { + this.coefficients = initializeCoefficients(); + } + + private Coefficients setCoefficient(Coefficient coefficient, int value) { + this.coefficients[coefficient.ordinal()] = value; + return this; + } + + private LinearConstraint toLinearConstraint(Relationship relationship, double value) { + return new LinearConstraint(this.coefficients, relationship, value); + } + + private LinearObjectiveFunction toLinearObjectiveFunction(int constantTerm) { + return new LinearObjectiveFunction(this.coefficients, constantTerm); + } + + } + + private static double[] initializeCoefficients() { + return new double[Coefficient.values().length]; + } + + private static int toInt(double value) { + return (int) Math.round(value); + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/GlobalSimulationsContext.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/GlobalSimulationsContext.java new file mode 100644 index 00000000000..e9bd6d9aa3b --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/GlobalSimulationsContext.java @@ -0,0 +1,325 @@ +package io.openems.edge.energy.api.simulation; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.openems.edge.common.type.TypeUtils.assertNull; +import static io.openems.edge.energy.api.EnergyConstants.SUM_PRODUCTION; +import static io.openems.edge.energy.api.EnergyConstants.SUM_UNMANAGED_CONSUMPTION; +import static io.openems.edge.energy.api.EnergyUtils.socToEnergy; +import static io.openems.edge.energy.api.EnergyUtils.toEnergy; +import static java.lang.Math.abs; +import static java.lang.Math.min; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.stream.IntStream; + +import com.google.common.collect.ImmutableList; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.utils.DateUtils; +import io.openems.edge.common.sum.Sum; +import io.openems.edge.common.type.TypeUtils; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period.Hour; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period.Quarter; +import io.openems.edge.predictor.api.manager.PredictorManager; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +/** + * Holds the simulation context that is used globally for all simulations in one + * optimization run. + * + *

    + * This record is usually created once per quarter. + */ +public record GlobalSimulationsContext(// + Clock clock, // + /** Start-Timestamp */ + ZonedDateTime startTime, // + ImmutableList handlers, // + Grid grid, // + Ess ess, // + /** + * Period is either mixed, with {@link Hour}s and {@link Quarter}s, or + * {@link Quarter}s only. + */ + ImmutableList periods) { + + @Override + public String toString() { + return toStringHelper(this) // + .add("startTime", this.startTime) // + .addValue(this.grid) // + .addValue(this.ess) // + .add("handlers", this.handlers) // + .toString(); + } + + public static record Ess(// + /** ESS Currently Available Energy (SoC in [Wh]) */ + int currentEnergy, // + /** ESS Total Energy (Capacity) [Wh] */ + int totalEnergy, // + /** ESS Max Charge Energy [Wh] */ + int maxChargeEnergy, // + /** ESS Max Discharge Energy [Wh] */ + int maxDischargeEnergy) { + } + + public static record Grid(// + /** Max Buy-From-Grid Energy [Wh] */ + int maxBuy, // + /** Max Sell-To-Grid Energy [Wh] */ + int maxSell) { + } + + public static sealed interface Period { + /** + * Start-Timestamp of the Period. + * + * @return the {@link ZonedDateTime} + */ + public ZonedDateTime time(); + + /** + * Production prediction for the Period in [Wh]. + * + * @return the production prediction + */ + public int production(); + + /** + * Consumption prediction for the Period in [Wh]. + * + * @return the consumption prediction + */ + public int consumption(); + + /** + * (Average) Grid-Buy-Price for the Period in [1/MWh]. + * + * @return the price + */ + public double price(); + + public static record Quarter(// + ZonedDateTime time, // + int production, // + int consumption, // + double price // + ) implements Period { + } + + public static record Hour(// + ZonedDateTime time, // + int production, // + int consumption, // + double price, // + /** Raw Periods, representing one QUARTER. */ + ImmutableList quarterPeriods // + ) implements Period { + } + } + + public static class Builder { + private Clock clock; + private ImmutableList handlers; + private Sum sum; + private PredictorManager predictorManager; + private TimeOfUseTariff timeOfUseTariff; + + /** + * The {@link Clock}. + * + * @param clock the {@link Clock} + * @return myself + */ + public Builder setClock(Clock clock) { + this.clock = clock; + return this; + } + + /** + * The {@link EnergyScheduleHandler}s of the {@link EnergySchedulable}s. + * + *

    + * The list is sorted by Scheduler. + * + * @param handlers the list of {@link EnergyScheduleHandler}s + * @return myself + */ + public Builder setEnergyScheduleHandlers(ImmutableList handlers) { + this.handlers = handlers; + return this; + } + + /** + * The {@link Sum}. + * + * @param sum the {@link Sum} + * @return myself + */ + public Builder setSum(Sum sum) { + this.sum = sum; + return this; + } + + /** + * The {@link PredictorManager}. + * + * @param predictorManager the {@link PredictorManager} + * @return myself + */ + public Builder setPredictorManager(PredictorManager predictorManager) { + this.predictorManager = predictorManager; + return this; + } + + /** + * The {@link TimeOfUseTariff}. + * + * @param timeOfUseTariff the {@link TimeOfUseTariff} + * @return myself + */ + public Builder setTimeOfUseTariff(TimeOfUseTariff timeOfUseTariff) { + this.timeOfUseTariff = timeOfUseTariff; + return this; + } + + /** + * Builds the {@link GlobalSimulationsContext}. + * + * @return the {@link GlobalSimulationsContext} record + */ + public GlobalSimulationsContext build() throws OpenemsException, IllegalArgumentException { + assertNull("Clock is not available", this.clock); + assertNull("EnergyScheduleHandlers are not available", this.handlers); + assertNull("Sum is not available", this.sum); + assertNull("Predictor-Manager is not available", this.predictorManager); + assertNull("TimeOfUseTariff is not available", this.timeOfUseTariff); + + final var startTime = DateUtils.roundDownToQuarter(ZonedDateTime.now(this.clock)); + + // Prediction values + final var consumptions = this.predictorManager.getPrediction(SUM_UNMANAGED_CONSUMPTION).asArray(); + final var productions = generateProductionPrediction(// + this.predictorManager.getPrediction(SUM_PRODUCTION).asArray(), // + consumptions.length); + + // Prices contains the price values and the time it is retrieved. + final var prices = this.timeOfUseTariff.getPrices().asArray(); + + final var numberOfPeriods = min(min(consumptions.length, productions.length), prices.length); + if (numberOfPeriods == 0) { + throw new IllegalArgumentException("No forecast periods available. " // + + "Consumptions[" + consumptions.length + "] " // + + "Productions[" + productions.length + "] " // + + "Prices[" + prices.length + "]"); + } + + // Helpers + final IntFunction toQuarterPeriod = (i) -> new Period.Quarter(// + startTime.plusMinutes(i * 15), // + toEnergy(productions[i]), // + toEnergy(consumptions[i]), // + prices[i]); + final var periodLengthHourFromIndex = calculatePeriodDurationHourFromIndex(startTime); + + var periods = ImmutableList.builder(); + + // Quarters + for (var i = 0; i < min(periodLengthHourFromIndex, numberOfPeriods); i++) { + periods.add(toQuarterPeriod.apply(i)); + } + + // Hours + final Function range = (i) -> IntStream.range(i, min(i + 4, numberOfPeriods)); + for (int i = periodLengthHourFromIndex, hour = periodLengthHourFromIndex / 4; // + i < numberOfPeriods; // + i += 4, hour++) { + periods.add(new Period.Hour(// + startTime.plusHours(hour), // + toEnergy(range.apply(i).map(j -> productions[j]).sum()), // + toEnergy(range.apply(i).map(j -> consumptions[j]).sum()), // + range.apply(i).mapToDouble(j -> prices[j]).average().getAsDouble(), // + range.apply(i) // + .mapToObj(j -> toQuarterPeriod.apply(j)) // + .collect(toImmutableList()))); + } + + final Ess ess; + { + var essTotalEnergy = this.sum.getEssCapacity().getOrError(); + var essInitialEnergy = socToEnergy(essTotalEnergy, this.sum.getEssSoc().getOrError()); + + // Power Values for scheduling battery for individual periods. + var maxDischargePower = TypeUtils.max(1000 /* at least 1000 W */, // + this.sum.getEssMaxDischargePower().get()); + var maxChargePower = TypeUtils.min(-1000 /* at least 1000 W */, // + this.sum.getEssMinDischargePower().get()); + // TODO + // if (context.limitChargePowerFor14aEnWG()) { + // maxChargePower = max(ESS_LIMIT_14A_ENWG, maxChargePower); // Apply §14a EnWG + // limit + // } + + ess = new Ess(essInitialEnergy, essTotalEnergy, toEnergy(abs(maxChargePower)), + toEnergy(maxDischargePower)); + } + final var grid = new Grid(40000 /* TODO */, 20000 /* TODO */); + + return new GlobalSimulationsContext(this.clock, startTime, this.handlers, grid, ess, periods.build()); + } + } + + /** + * Create a {@link GlobalSimulationsContext} {@link Builder}. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new GlobalSimulationsContext.Builder(); + } + + /** + * Postprocesses production prediction; makes sure length is at least the same + * as consumption prediction - filling up with zeroes. + * + * @param prediction the production prediction + * @param minLength the min length (= consumption prediction length) + * @return new production prediction + */ + protected static Integer[] generateProductionPrediction(Integer[] prediction, int minLength) { + if (prediction.length >= minLength) { + return prediction; + } + return IntStream.range(0, minLength) // + .mapToObj(i -> i > prediction.length - 1 ? 0 : prediction[i]) // + .toArray(Integer[]::new); + } + + /** + * Calculates the index when Period duration switches from {@link Hour} to + * {@link Quarter}. + * + *

    + * The index is calculated as "6 hours" plus remaining quarters of the current + * hour. + * + * @param time Start-Timestamp of the Schedule + * @return the index + */ + // TODO this should be set depending on the actual calculation time and + // quality of the best schedule result + protected static int calculatePeriodDurationHourFromIndex(ZonedDateTime time) { + var minute = time.getMinute(); + if (minute == 0) { + minute = 60; + } + return 6 * 4 + (60 - minute) / 15; + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/OneSimulationContext.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/OneSimulationContext.java new file mode 100644 index 00000000000..0e6244a2975 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/OneSimulationContext.java @@ -0,0 +1,59 @@ +package io.openems.edge.energy.api.simulation; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.lang.Math.max; + +/** + * Holds the simulation context that is used for one simulation of a full + * schedule with multiple periods. + * + *

    + * This record is usually created multiple times per second. + */ +public class OneSimulationContext { + + /** + * Builds a {@link OneSimulationContext}. + * + * @param asc the {@link GlobalSimulationsContext} + * @return the {@link OneSimulationContext} + */ + public static OneSimulationContext from(GlobalSimulationsContext asc) { + return new OneSimulationContext(asc, asc.ess().currentEnergy()); + } + + public final GlobalSimulationsContext global; + + private int essInitialEnergy; + + private OneSimulationContext(GlobalSimulationsContext gsc, int essInitialEnergy) { + this.global = gsc; + this.essInitialEnergy = essInitialEnergy; + } + + /** + * Calculates the initial SoC-Energy of the next Period. + * + * @param ess the ess charge/discharge energy of the current Period + */ + public synchronized void calculateEssInitial(int ess) { + this.essInitialEnergy = max(0, this.essInitialEnergy - ess); // always at least '0' + } + + /** + * The initial SoC-Energy of the Period. + * + * @return the value + */ + public int getEssInitial() { + return this.essInitialEnergy; + } + + @Override + public String toString() { + return toStringHelper(this) // + .add("essInitialEnergy", this.essInitialEnergy) // + .addValue(this.global) // + .toString(); + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/package-info.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/package-info.java new file mode 100644 index 00000000000..479bc6029cb --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/simulation/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.energy.api.simulation; diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/AbstractDummyEnergySchedulable.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/AbstractDummyEnergySchedulable.java new file mode 100644 index 00000000000..0880e69eb33 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/AbstractDummyEnergySchedulable.java @@ -0,0 +1,16 @@ +package io.openems.edge.energy.api.test; + +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.test.AbstractDummyOpenemsComponent; +import io.openems.edge.energy.api.EnergySchedulable; + +public abstract class AbstractDummyEnergySchedulable> + extends AbstractDummyOpenemsComponent implements EnergySchedulable, OpenemsComponent { + + protected AbstractDummyEnergySchedulable(String id, + io.openems.edge.common.channel.ChannelId[] firstInitialChannelIds, + io.openems.edge.common.channel.ChannelId[]... furtherInitialChannelIds) { + super(id, firstInitialChannelIds, furtherInitialChannelIds); + } + +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyEnergySchedulable.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyEnergySchedulable.java new file mode 100644 index 00000000000..53bac023549 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyEnergySchedulable.java @@ -0,0 +1,37 @@ +package io.openems.edge.energy.api.test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler; + +/** + * Provides a simple, simulated {@link EnergySchedulable} component that can be + * used together with the OpenEMS Component test framework. + */ +public class DummyEnergySchedulable extends AbstractDummyEnergySchedulable + implements EnergySchedulable, OpenemsComponent { + + private final EnergyScheduleHandler esh; + + public DummyEnergySchedulable(String id, EnergyScheduleHandler esh) { + super(id, // + OpenemsComponent.ChannelId.values() // + ); + this.esh = esh; + } + + @Override + protected final DummyEnergySchedulable self() { + return this; + } + + @Override + public void run() throws OpenemsNamedException { + } + + @Override + public EnergyScheduleHandler getEnergyScheduleHandler() { + return this.esh; + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyGlobalSimulationsContext.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyGlobalSimulationsContext.java new file mode 100644 index 00000000000..8918dd8f5f1 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/DummyGlobalSimulationsContext.java @@ -0,0 +1,99 @@ +package io.openems.edge.energy.api.test; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; + +import com.google.common.collect.ImmutableList; + +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period; + +public class DummyGlobalSimulationsContext { + + private DummyGlobalSimulationsContext() { + } + + public static final TimeLeapClock CLOCK = new TimeLeapClock(Instant.ofEpochSecond(946684800), ZoneId.of("UTC")); + public static final ZonedDateTime TIME = ZonedDateTime.now(CLOCK); + + /** + * Generates a {@link GlobalSimulationsContext} with the given + * {@link EnergyScheduleHandler}s. + * + * @param handlers the {@link EnergyScheduleHandler}s + * @return a {@link GlobalSimulationsContext} + */ + public static GlobalSimulationsContext fromHandlers(EnergyScheduleHandler... handlers) { + return new GlobalSimulationsContext(// + CLOCK, TIME, // + Arrays.stream(handlers).collect(toImmutableList()), // + new GlobalSimulationsContext.Grid(4000, 20000), // + new GlobalSimulationsContext.Ess(5000, 22000, 4000, 4000), // + ImmutableList.of(// + new Period.Quarter(time(0, 0), 0, 106, 293.70), // + new Period.Quarter(time(0, 15), 0, 86, 293.70), // + new Period.Quarter(time(0, 30), 0, 88, 293.70), // + new Period.Quarter(time(0, 45), 0, 81, 293.70), // + new Period.Quarter(time(1, 0), 0, 73, 294.30), // + new Period.Quarter(time(1, 15), 0, 68, 294.30), // + new Period.Quarter(time(1, 30), 0, 76, 294.30), // + new Period.Quarter(time(1, 45), 0, 149, 294.30), // + new Period.Quarter(time(2, 0), 0, 333, 289.30), // + new Period.Quarter(time(2, 15), 0, 61, 289.30), // + new Period.Quarter(time(2, 30), 0, 74, 289.30), // + new Period.Quarter(time(2, 45), 0, 73, 289.30), // + new Period.Quarter(time(3, 0), 0, 68, 288.00), // + new Period.Quarter(time(3, 15), 0, 66, 288.00), // + new Period.Quarter(time(3, 30), 0, 82, 288.00), // + new Period.Quarter(time(3, 45), 0, 99, 288.00), // + new Period.Quarter(time(4, 0), 0, 84, 288.80), // + new Period.Quarter(time(4, 15), 0, 80, 288.80), // + new Period.Quarter(time(4, 30), 0, 97, 288.80), // + new Period.Quarter(time(4, 45), 0, 85, 288.80), // + new Period.Quarter(time(5, 0), 0, 65, 302.90), // + new Period.Quarter(time(5, 15), 0, 69, 302.90), // + new Period.Quarter(time(5, 30), 0, 75, 302.90), // + new Period.Quarter(time(5, 45), 3, 90, 302.90), // + new Period.Quarter(time(6, 0), 6, 394, 331.70), // + new Period.Quarter(time(6, 15), 36, 106, 331.70), // + new Period.Quarter(time(6, 30), 112, 94, 331.70), // + new Period.Quarter(time(6, 45), 205, 74, 331.70), // + new Period.Quarter(time(7, 0), 342, 62, 342.50), // + new Period.Quarter(time(7, 15), 437, 74, 342.50), // + new Period.Quarter(time(7, 30), 518, 72, 342.50), // + new Period.Quarter(time(7, 45), 628, 60, 342.50), // + new Period.Quarter(time(8, 0), 931, 46, 332.70), // + new Period.Quarter(time(8, 15), 1159, 45, 332.70), // + new Period.Quarter(time(8, 30), 1349, 40, 332.70), // + new Period.Quarter(time(8, 45), 1543, 26, 332.70), // + new Period.Quarter(time(9, 0), 1743, 46, 311.80), // + new Period.Quarter(time(9, 15), 1920, 472, 311.80), // + new Period.Quarter(time(9, 30), 2112, 498, 311.80), // + new Period.Quarter(time(9, 45), 2209, 83, 311.80), // + new Period.Quarter(time(10, 0), 2436, 105, 292.10), // + new Period.Quarter(time(10, 15), 2671, 92, 292.10), // + new Period.Quarter(time(10, 30), 2723, 133, 292.10), // + new Period.Quarter(time(10, 45), 2824, 88, 292.10), // + new Period.Hour(time(11, 0), 11610, 716, 282.90, ImmutableList.of(// + new Period.Quarter(time(11, 0), 2878, 86, 282.90), // + new Period.Quarter(time(11, 15), 2871, 245, 282.90), // + new Period.Quarter(time(11, 30), 2883, 308, 282.90), // + new Period.Quarter(time(11, 45), 2978, 77, 282.90))), // + new Period.Hour(time(12, 0), 6118, 241, 260.70, ImmutableList.of(// + new Period.Quarter(time(12, 0), 3044, 54, 260.70), // + new Period.Quarter(time(12, 15), 3022, 64, 260.70), // + new Period.Quarter(time(12, 30), 3036, 64, 260.70), // + new Period.Quarter(time(12, 45), 3045, 59, 260.70))) // + )); + } + + private static ZonedDateTime time(int hours, int minutes) { + return TIME.plusHours(hours).plusMinutes(minutes); + } +} diff --git a/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/package-info.java b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/package-info.java new file mode 100644 index 00000000000..b8f87f83ce3 --- /dev/null +++ b/io.openems.edge.energy.api/src/io/openems/edge/energy/api/test/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.energy.api.test; diff --git a/io.openems.edge.energy.api/test/io/openems/edge/energy/api/EnergyUtilsTest.java b/io.openems.edge.energy.api/test/io/openems/edge/energy/api/EnergyUtilsTest.java new file mode 100644 index 00000000000..b4a6a7f2d69 --- /dev/null +++ b/io.openems.edge.energy.api/test/io/openems/edge/energy/api/EnergyUtilsTest.java @@ -0,0 +1,55 @@ +package io.openems.edge.energy.api; + +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; +import static io.openems.edge.energy.api.EnergyUtils.interpolateArray; +import static io.openems.edge.energy.api.EnergyUtils.toPower; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class EnergyUtilsTest { + + @Test + public void testFindFirstPeakIndex() { + assertEquals(0, findFirstPeakIndex(0, new double[0])); + assertEquals(0, findFirstPeakIndex(0, new double[] { 1 })); + assertEquals(0, findFirstPeakIndex(0, new double[] { 1, 0 })); + assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0 })); + assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0, 1 })); + assertEquals(5, findFirstPeakIndex(5, new double[0])); + } + + @Test + public void testFindFirstValleyIndex() { + assertEquals(0, findFirstValleyIndex(0, new double[0])); + assertEquals(0, findFirstValleyIndex(0, new double[] { 1 })); + assertEquals(1, findFirstValleyIndex(0, new double[] { 1, 0 })); + assertEquals(0, findFirstValleyIndex(0, new double[] { 0, 1, 0 })); + assertEquals(2, findFirstValleyIndex(1, new double[] { 0, 1, 0, 1 })); + assertEquals(5, findFirstValleyIndex(5, new double[0])); + } + + @Test + public void testInterpolateArrayInteger() { + assertArrayEquals(new int[] { 123, 123, 234, 234, 345 }, // + interpolateArray(new Integer[] { null, 123, 234, null, 345, null })); + + assertArrayEquals(new int[] {}, // + interpolateArray(new Integer[] { null })); + + assertArrayEquals(new int[] { 123, 123 }, // + interpolateArray(new Integer[] { null, 123 })); + + assertArrayEquals(new int[] { 123 }, // + interpolateArray(new Integer[] { 123, null })); + } + + @Test + public void testToPower() { + assertEquals(2000, (int) toPower(500)); + assertNull(toPower(null)); + } +} diff --git a/io.openems.edge.energy.api/test/io/openems/edge/energy/api/simulation/GlobalSimulationsContextTest.java b/io.openems.edge.energy.api/test/io/openems/edge/energy/api/simulation/GlobalSimulationsContextTest.java new file mode 100644 index 00000000000..18eb644ba9c --- /dev/null +++ b/io.openems.edge.energy.api/test/io/openems/edge/energy/api/simulation/GlobalSimulationsContextTest.java @@ -0,0 +1,95 @@ +package io.openems.edge.energy.api.simulation; + +import static io.openems.edge.energy.api.simulation.GlobalSimulationsContext.calculatePeriodDurationHourFromIndex; +import static io.openems.edge.energy.api.simulation.GlobalSimulationsContext.generateProductionPrediction; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.common.sum.DummySum; +import io.openems.edge.common.test.DummyComponentManager; +import io.openems.edge.energy.api.EnergyConstants; +import io.openems.edge.predictor.api.prediction.Prediction; +import io.openems.edge.predictor.api.test.DummyPredictor; +import io.openems.edge.predictor.api.test.DummyPredictorManager; +import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider; + +public class GlobalSimulationsContextTest { + + private static final TimeLeapClock CLOCK = new TimeLeapClock(Instant.ofEpochSecond(946684800), ZoneId.of("UTC")); + + @Test + public void testBuild() throws OpenemsNamedException { + final var cm = new DummyComponentManager(CLOCK); + final var now = ZonedDateTime.now(CLOCK); + final var sum = new DummySum() // + .withEssCapacity(10000) // + .withEssSoc(50) // + .withEssMinDischargePower(-4000) // + .withEssMaxDischargePower(5000); + final var predictorManager = new DummyPredictorManager(// + new DummyPredictor("predictor0", cm, Prediction.from(sum, // + EnergyConstants.SUM_UNMANAGED_CONSUMPTION, now, new Integer[] { // + 4000, 8000, 6000, 2000, 3000, 5000, 7000, 9000, // + 4001, 8001, 6001, 2001, 3001, 5001, 7001, 9001, // + 4002, 8002, 6002, 2002, 3002, 5002, 7002, 9002, // + 4003, 8003, 6003, 2003, 3003, 5003, 7003, 9003, // + 4004, 8004, 6004, 2004, 3004, 5004, 7004, 9004, // + }), EnergyConstants.SUM_UNMANAGED_CONSUMPTION), + new DummyPredictor("predictor1", cm, Prediction.from(sum, // + EnergyConstants.SUM_PRODUCTION, now, + new Integer[] { 8000, 9000, 10000, 11000, 7000, 4000, 3000, 5000, // + 8001, 9001, 10001, 11001, 7001, 4001, 3001, 5001, // + 8002, 9002, 10002, 11002, 7002, 4002, 3002, 5002, // + 8003, 9003, 10003, 11003, 7003, 4003, 3003, 5003, // + 8004, 9004, 10004, 11004, 7004, 4004, 3004, 5004, // + }), EnergyConstants.SUM_PRODUCTION)); + final var prices = DummyTimeOfUseTariffProvider.fromQuarterlyPrices(CLOCK, // + 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, // + 11.1, 12.1, 13.1, 14.1, 15.1, 16.1, 17.1, 18.1, // + 11.2, 12.2, 13.2, 14.2, 15.2, 16.2, 17.2, 18.2, // + 11.3, 12.3, 13.3, 14.3, 15.3, 16.3, 17.3, 18.3, // + 11.4, 12.4, 13.4, 14.4, 15.4, 16.4, 17.4, 18.4 // + ); + + var gsc = GlobalSimulationsContext.create() // + .setClock(CLOCK) // + .setEnergyScheduleHandlers(ImmutableList.of()) // + .setSum(sum) // + .setPredictorManager(predictorManager) // + .setTimeOfUseTariff(prices) // + .build(); + + assertEquals(1000 /* -4000 W */, gsc.ess().maxChargeEnergy()); + assertEquals(1250 /* 5000 W */, gsc.ess().maxDischargeEnergy()); + assertEquals(28, gsc.periods().size()); + var p0 = gsc.periods().get(0); + assertEquals(2000 /* Wh */, p0.production()); + assertEquals(1000 /* Wh */, p0.consumption()); + } + + @Test + public void testGenerateProductionPrediction() { + final var arr = new Integer[] { 1, 2, 3 }; + assertArrayEquals(arr, generateProductionPrediction(arr, 2)); + assertArrayEquals(new Integer[] { 1, 2, 3, 0 }, generateProductionPrediction(arr, 4)); + } + + @Test + public void testCalculatePeriodDurationHourFromIndex() { + assertEquals(24, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T14:00:00.00Z"))); + assertEquals(24 + 3, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T14:15:00.00Z"))); + assertEquals(24 + 2, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T14:30:00.00Z"))); + assertEquals(24 + 1, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T14:45:00.00Z"))); + assertEquals(24, calculatePeriodDurationHourFromIndex(ZonedDateTime.parse("2020-03-04T15:00:00.00Z"))); + } +} diff --git a/io.openems.edge.energy/bnd.bnd b/io.openems.edge.energy/bnd.bnd index 7df50f07c5e..2b14c6014a9 100644 --- a/io.openems.edge.energy/bnd.bnd +++ b/io.openems.edge.energy/bnd.bnd @@ -3,20 +3,29 @@ Bundle-Vendor: FENECON GmbH Bundle-License: https://opensource.org/licenses/EPL-2.0 Bundle-Version: 1.0.0.${tstamp} +# TODO remove emergencycapacityreserve and limittotaldischarge from buildpath after v1 + -buildpath: \ ${buildpath},\ io.openems.common,\ io.openems.edge.common,\ io.openems.edge.controller.api,\ io.openems.edge.controller.ess.emergencycapacityreserve,\ + io.openems.edge.controller.ess.fixactivepower,\ io.openems.edge.controller.ess.limittotaldischarge,\ io.openems.edge.controller.ess.timeofusetariff,\ io.openems.edge.energy.api,\ io.openems.edge.ess.api,\ io.openems.edge.predictor.api,\ + io.openems.edge.scheduler.api,\ io.openems.edge.timedata.api,\ io.openems.edge.timeofusetariff.api,\ io.openems.wrapper.jenetics,\ - + -testpath: \ - ${testpath} \ No newline at end of file + ${testpath},\ + io.openems.edge.controller.ess.emergencycapacityreserve,\ + io.openems.edge.controller.ess.gridoptimizedcharge,\ + io.openems.edge.controller.ess.limittotaldischarge,\ + io.openems.edge.controller.ess.timeofusetariff,\ + org.apache.commons.math3,\ diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/Config.java b/io.openems.edge.energy/src/io/openems/edge/energy/Config.java index dad00cde174..1552f7dafa1 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/Config.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/Config.java @@ -3,6 +3,8 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.energy.api.Version; + @ObjectClassDefinition(// name = "Core Energy Scheduler", // description = "The global Energy Scheduler.") @@ -14,5 +16,8 @@ @AttributeDefinition(name = "Log-Verbosity", description = "The log verbosity") LogVerbosity logVerbosity() default LogVerbosity.DEBUG_LOG; + @AttributeDefinition(name = "Version", description = "Select version of implementation") + Version version() default Version.V1_ESS_ONLY; + String webconsole_configurationFactory_nameHint() default "Core Energy Scheduler"; } \ No newline at end of file diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java b/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java index 88da0a70170..80d830c61cc 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/EnergySchedulerImpl.java @@ -1,10 +1,14 @@ package io.openems.edge.energy; -import static io.openems.edge.energy.optimizer.Utils.handleGetScheduleRequest; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.openems.common.utils.ThreadPoolUtils.shutdownAndAwaitTermination; +import static io.openems.edge.energy.optimizer.Utils.sortByScheduler; import java.time.ZonedDateTime; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; @@ -21,18 +25,24 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.base.JsonrpcResponse; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.ComponentManager; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.jsonapi.ComponentJsonApi; -import io.openems.edge.common.jsonapi.JsonApiBuilder; +import io.openems.edge.common.jsonapi.Call; import io.openems.edge.common.sum.Sum; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; import io.openems.edge.energy.api.EnergyScheduler; -import io.openems.edge.energy.jsonrpc.GetScheduleRequest; -import io.openems.edge.energy.optimizer.GlobalContext; +import io.openems.edge.energy.api.Version; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; import io.openems.edge.energy.optimizer.Optimizer; +import io.openems.edge.energy.v1.jsonrpc.GetScheduleResponse; +import io.openems.edge.energy.v1.optimizer.GlobalContextV1; +import io.openems.edge.energy.v1.optimizer.OptimizerV1; +import io.openems.edge.energy.v1.optimizer.UtilsV1; import io.openems.edge.predictor.api.manager.PredictorManager; import io.openems.edge.timedata.api.Timedata; import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; @@ -43,11 +53,13 @@ immediate = true, // configurationPolicy = ConfigurationPolicy.OPTIONAL // ) -public class EnergySchedulerImpl extends AbstractOpenemsComponent - implements OpenemsComponent, EnergyScheduler, ComponentJsonApi { +@SuppressWarnings("deprecation") +public class EnergySchedulerImpl extends AbstractOpenemsComponent implements OpenemsComponent, EnergyScheduler { /** The hard working Optimizer. */ + private final OptimizerV1 optimizerV1; private final Optimizer optimizer; + private final ExecutorService executor = Executors.newSingleThreadExecutor(); @Reference private ConfigurationAdmin cm; @@ -61,18 +73,40 @@ public class EnergySchedulerImpl extends AbstractOpenemsComponent @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile TimeOfUseTariff timeOfUseTariff; + @Reference + private io.openems.edge.scheduler.api.Scheduler scheduler; + @Reference private Sum sum; + private final List schedulables = new CopyOnWriteArrayList<>(); + @Reference(policyOption = ReferencePolicyOption.GREEDY, // cardinality = ReferenceCardinality.MULTIPLE, // policy = ReferencePolicy.DYNAMIC, // target = "(enabled=true)") - private volatile List> schedulables = new CopyOnWriteArrayList<>(); + private void addSchedulable(EnergySchedulable schedulable) { + this.schedulables.add(schedulable); + var esh = (AbstractEnergyScheduleHandler) schedulable.getEnergyScheduleHandler(); // this is safe + esh.setOnRescheduleCallback(() -> this.optimizer.triggerReschedule()); + this.resetOptimizer(); + } + + @SuppressWarnings("unused") + private void removeSchedulable(EnergySchedulable schedulable) { + this.schedulables.remove(schedulable); + var esh = (AbstractEnergyScheduleHandler) schedulable.getEnergyScheduleHandler(); // this is safe + esh.removeOnRescheduleCallback(); + this.resetOptimizer(); + } @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL) private volatile Timedata timedata; + @Deprecated + @Reference(policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL, target = "(enabled=true)") + private volatile TimeOfUseTariffController timeOfUseTariffController; + private Config config; public EnergySchedulerImpl() { @@ -80,57 +114,91 @@ public EnergySchedulerImpl() { OpenemsComponent.ChannelId.values(), // EnergyScheduler.ChannelId.values() // ); - // Prepare Optimizer and Context - this.optimizer = new Optimizer(// + + this.optimizerV1 = new OptimizerV1(// () -> this.config.logVerbosity(), // () -> { if (this.timeOfUseTariff == null) { throw new OpenemsException("TimeOfUseTariff is not available"); } - var ctrl = this.schedulables.stream() // - .filter(TimeOfUseTariffControllerImpl.class::isInstance) // - .map(TimeOfUseTariffControllerImpl.class::cast) // - .findFirst().orElse(null); - if (ctrl == null) { + if (this.timeOfUseTariffController == null) { throw new OpenemsException("TimeOfUseTariffController is not available"); } - var esh = ctrl.getEnergyScheduleHandler(); - // NOTE: This is a workaround while we refactor TimeOfUseTariffController - // This code assumes that the `EnergySchedulable` is a - // `TimeOfUseTariffController` - return GlobalContext.create() // + return GlobalContextV1.create() // .setClock(this.componentManager.getClock()) // - .setEnergyScheduleHandler(esh) // + .setEnergyScheduleHandler(this.timeOfUseTariffController.getEnergyScheduleHandlerV1()) // .setSum(this.sum) // .setPredictorManager(this.predictorManager) // .setTimeOfUseTariff(this.timeOfUseTariff) // .build(); }); + + this.optimizer = new Optimizer(// + () -> this.config.logVerbosity(), // + () -> { + // Sort Schedulables by the order in the Scheduler + var schedulables = sortByScheduler(this.scheduler, this.schedulables); + var eshs = schedulables.stream() // + .map(EnergySchedulable::getEnergyScheduleHandler) // + .collect(toImmutableList()); + + return GlobalSimulationsContext.create() // + .setClock(this.componentManager.getClock()) // + .setEnergyScheduleHandlers(eshs) // + .setSum(this.sum) // + .setPredictorManager(this.predictorManager) // + .setTimeOfUseTariff(this.timeOfUseTariff) // + .build(); + }, // + this.channel(EnergyScheduler.ChannelId.SIMULATIONS_PER_QUARTER)); } @Activate private void activate(ComponentContext context, Config config) throws OpenemsException { super.activate(context, SINGLETON_COMPONENT_ID, SINGLETON_SERVICE_PID, true); + if (this.applyConfig(config)) { - this.optimizer.activate(this.id()); + switch (config.version()) { + case V1_ESS_ONLY -> this.optimizerV1.activate(this.id()); + case V2_ENERGY_SCHEDULABLE -> this.executor.execute(this.optimizer); + } } } @Modified private void modified(ComponentContext context, Config config) throws OpenemsNamedException { super.modified(context, SINGLETON_COMPONENT_ID, SINGLETON_SERVICE_PID, true); - if (this.applyConfig(config)) { - this.optimizer.modified(this.id()); + this.applyConfig(config); + } + + private void resetOptimizer() { + if (this.config == null) { + return; // Wait for @Activate + } + switch (this.config.version()) { + case V1_ESS_ONLY -> this.optimizerV1.activate(this.id()); + case V2_ENERGY_SCHEDULABLE -> this.optimizer.triggerReschedule(); } } + @Override + public String debugLog() { + if (this.config != null && this.config.version() == Version.V2_ENERGY_SCHEDULABLE) { + return this.optimizer.debugLog(); + } + return null; + } + private synchronized boolean applyConfig(Config config) { this.config = config; if (OpenemsComponent.validateSingleton(this.cm, SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return false; } - if (!config.enabled()) { + if (config.enabled()) { + this.resetOptimizer(); + } else { + this.optimizerV1.deactivate(); this.optimizer.deactivate(); return false; } @@ -141,23 +209,18 @@ private synchronized boolean applyConfig(Config config) { @Override @Deactivate protected void deactivate() { + this.optimizerV1.deactivate(); this.optimizer.deactivate(); + shutdownAndAwaitTermination(this.executor, 0); super.deactivate(); } @Override - public String debugLog() { - if (this.config == null || this.config.logVerbosity() == LogVerbosity.NONE) { - return null; + public GetScheduleResponse handleGetScheduleRequestV1(Call call, String id) { + if (this.optimizerV1 != null) { + return UtilsV1.handleGetScheduleRequest(this.optimizerV1, call.getRequest().getId(), this.timedata, + this.timeOfUseTariff, id, ZonedDateTime.now(this.componentManager.getClock())); } - // TODO add debug log - return null; - } - - @Override - public void buildJsonApiRoutes(JsonApiBuilder builder) { - builder.handleRequest(GetScheduleRequest.METHOD, call -> handleGetScheduleRequest(// - this.optimizer, call.getRequest().getId(), this.timedata, this.timeOfUseTariff, - "ctrlEssTimeOfUseTariff0", ZonedDateTime.now(this.componentManager.getClock()))); + throw new IllegalArgumentException("This should have been Version V1"); } } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java b/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java index 130bd15e512..27f2d942c3b 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/LogVerbosity.java @@ -10,4 +10,4 @@ public enum LogVerbosity { * Trace. */ TRACE; -} \ No newline at end of file +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java index 3188e15f364..63ca31e3315 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Optimizer.java @@ -1,152 +1,215 @@ package io.openems.edge.energy.optimizer; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; -import static io.openems.edge.energy.optimizer.Simulator.simulate; +import static io.jenetics.engine.Limits.byExecutionTime; +import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.edge.energy.optimizer.QuickSchedules.findBestQuickSchedule; import static io.openems.edge.energy.optimizer.Utils.calculateExecutionLimitSeconds; -import static io.openems.edge.energy.optimizer.Utils.createSimulatorParams; +import static io.openems.edge.energy.optimizer.Utils.createSimulator; import static io.openems.edge.energy.optimizer.Utils.initializeRandomRegistryForProduction; -import static io.openems.edge.energy.optimizer.Utils.logSchedule; -import static io.openems.edge.energy.optimizer.Utils.updateSchedule; -import static java.lang.Thread.sleep; +import static io.openems.edge.energy.optimizer.Utils.logSimulationResult; +import static java.time.Duration.ofSeconds; -import java.time.Duration; -import java.time.Instant; +import java.time.Clock; import java.time.ZonedDateTime; -import java.util.Map.Entry; -import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.ImmutableSortedMap; - import io.openems.common.exceptions.OpenemsException; import io.openems.common.function.ThrowingSupplier; -import io.openems.common.test.TimeLeapClock; import io.openems.common.utils.FunctionUtils; -import io.openems.common.worker.AbstractImmediateWorker; +import io.openems.edge.common.channel.Channel; import io.openems.edge.energy.LogVerbosity; import io.openems.edge.energy.api.EnergyScheduleHandler; -import io.openems.edge.energy.optimizer.Simulator.Period; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; /** * This task is executed once in the beginning and afterwards every full 15 * minutes. */ -public class Optimizer extends AbstractImmediateWorker { +public class Optimizer implements Runnable { private final Logger log = LoggerFactory.getLogger(Optimizer.class); private final Supplier logVerbosity; - private final ThrowingSupplier globalContext; - private final TreeMap schedule = new TreeMap<>(); + private final ThrowingSupplier gscSupplier; + private final Channel simulationsPerQuarterChannel; + private final AtomicBoolean interruptFlag = new AtomicBoolean(false); - private Params params = null; + private Simulator simulator = null; + private SimulationResult simulationResult = SimulationResult.EMPTY; public Optimizer(Supplier logVerbosity, - ThrowingSupplier globalContext) { + ThrowingSupplier gscSupplier, // + Channel simulationsPerQuarterChannel) { this.logVerbosity = logVerbosity; - this.globalContext = globalContext; + this.gscSupplier = gscSupplier; + this.simulationsPerQuarterChannel = simulationsPerQuarterChannel; initializeRandomRegistryForProduction(); + } + + /** + * Deactivate the {@link Optimizer}. + */ + public synchronized void deactivate() { + this.interruptFlag.set(true); + } - // Run Optimizer thread in LOW PRIORITY - this.setPriority(Thread.MIN_PRIORITY); + /** + * Triggers Rescheduling. + */ + public void triggerReschedule() { + this.traceLog(() -> "Trigger Reschedule"); + this.interruptFlag.set(true); } @Override - public void forever() throws InterruptedException, OpenemsException { - this.traceLog(() -> "Start next run of Optimizer"); + public void run() { + try { + while (true) { + this.traceLog(() -> "Run..."); + this.interruptFlag.set(false); + + // Create the Simulator with GlobalSimulationsContext + createSimulator(this.gscSupplier, this.interruptFlag, // + simulator -> this.simulator = simulator, // + error -> { + this.traceLog(error); + this.applyEmptySimulationResult(); + }); + this.traceLog(() -> "Simulator is " + this.simulator); + final var simulator = this.simulator; + if (simulator == null) { + continue; + } - this.createParams(); // this possibly takes forever + this.runOnce(simulator); + } + } catch (InterruptedException | ExecutionException e) { + this.log.error("OPTIMIZER execution failed InterruptedException|ExecutionException: " + e.getMessage()); + e.printStackTrace(); + + // ignore + } catch (Exception e) { + this.log.error("OPTIMIZER execution failed: " + e.getMessage()); + e.printStackTrace(); + } + } - final var globalContext = this.globalContext.get(); - final var start = Instant.now(globalContext.clock()); + /** + * Run the optimization once. + * + * @param simulator the {@link Simulator} + * @throws InterruptedException on error + * @throws ExecutionException on error + */ + public void runOnce(Simulator simulator) throws InterruptedException, ExecutionException { + if (this.simulationResult == SimulationResult.EMPTY) { + // No Schedule available yet. Start with a default Schedule with all States + // set to default. + this.traceLog(() -> "No existing schedule available -> apply default"); + this.applyBestQuickSchedule(simulator); + } - long executionLimitSeconds; + this.traceLog(() -> "Run Simulation..."); - // Calculate max execution time till next quarter (with buffer) - executionLimitSeconds = calculateExecutionLimitSeconds(globalContext.clock()); + var simulationResult = this.runSimulation(simulator).get(); + if (simulationResult == null/* no result */ || this.interruptFlag.get() /* was interrupted */) { + this.traceLog(() -> "Simulation gave no result or was interrupted!"); + this.simulationsPerQuarterChannel.setNextValue(null); + this.applyBestQuickSchedule(simulator); + return; + } - // Find best Schedule - var schedule = Simulator.getBestSchedule(this.params, executionLimitSeconds); + this.traceLog(() -> "Calculate metrics"); - // Re-Simulate and keep best Schedule - var newSchedule = simulate(this.params, schedule); + // Calculate metrics + var stats = simulator.cache.stats(); + this.simulationsPerQuarterChannel.setNextValue(stats.loadCount()); - // Debug Log best Schedule - logSchedule(this.params, newSchedule); + // Apply simulation result to EnergyScheduleHandlers + this.applySimulationResult(simulator, simulationResult, false); + } - // Update Schedule from newly simulated Schedule - synchronized (this.schedule) { - updateSchedule(ZonedDateTime.now(globalContext.clock()), this.schedule, newSchedule); - } + private CompletableFuture runSimulation(Simulator simulator) { + this.traceLog(() -> "Run next Simulation"); + return CompletableFuture.supplyAsync(() -> { + this.traceLog(() -> "Executing async Simulation"); - // Send Schedule to Controller - globalContext.energyScheduleHandler().setSchedule(this.schedule.entrySet().stream()// - .collect(toImmutableMap(// - Entry::getKey, // - e -> new EnergyScheduleHandler.Period<>(e.getValue().state(), - e.getValue().op().essChargeInChargeGrid())))); - - // Sleep remaining time - if (!(globalContext.clock() instanceof TimeLeapClock)) { - var remainingExecutionLimit = Duration - .between(Instant.now(globalContext.clock()), start.plusSeconds(executionLimitSeconds)).getSeconds(); - if (remainingExecutionLimit > 0) { - this.traceLog(() -> "Sleep [" + remainingExecutionLimit + "s] till next run of Optimizer"); - sleep(remainingExecutionLimit * 1000); - } - } + final var executionLimit = byExecutionTime(ofSeconds(calculateExecutionLimitSeconds())); + + // Find best Schedule + var bestSchedule = simulator.getBestSchedule(this.simulationResult, null, // + stream -> stream // + // Stop on interruptFlag + .limit(ignore -> !this.interruptFlag.get()) // + // Stop till next quarter + .limit(executionLimit)); + + return bestSchedule; + }); } /** - * Try forever till all data is available (e.g. ESS Capacity) + * Create and apply the best quickly available Schedule. * - * @throws InterruptedException during sleep + * @param simulator the {@link Simulator} */ - private void createParams() throws InterruptedException { - while (true) { - try { - synchronized (this.schedule) { - this.params = createSimulatorParams(this.globalContext.get(), // - this.schedule.entrySet().stream() // - .collect(toImmutableSortedMap(// - ZonedDateTime::compareTo, // - Entry::getKey, e -> e.getValue().state()))); - return; - } - - } catch (OpenemsException e) { - this.traceLog(() -> "Stuck trying to get Params. " + e.getMessage()); - this.params = null; - synchronized (this.schedule) { - this.schedule.clear(); - } - sleep(30_000); - } + protected synchronized void applyBestQuickSchedule(Simulator simulator) { + // Find Genotype with lowest cost + var bestGt = findBestQuickSchedule(simulator, this.simulationResult); + if (bestGt == null) { + this.applyEmptySimulationResult(); + return; } + var simulationResult = SimulationResult.fromQuarters(simulator.gsc, bestGt); + + this.traceLog(() -> "Applying best quick Schedule"); + this.applySimulationResult(simulator, simulationResult, true); } - /** - * Gets the current {@link Params} or null. - * - * @return the {@link Params} or null - */ - public Params getParams() { - return this.params; + private void applyEmptySimulationResult() { + this.traceLog(() -> "Applying empty Schedule"); + this.applySimulationResult(null, SimulationResult.EMPTY, true); } /** - * Gets a copy of the Schedule. - * - * @return {@link ImmutableSortedMap} + * Applies the Schedule to all {@link EnergyScheduleHandler}s and stores the + * {@link SimulationResult} in `this.simulationResult`. + * + * @param simulator the {@link Simulator}, possibly null + * @param simulationResult the {@link SimulationResult} + * @param updateActiveQuarter should the currently active quarter also get + * updated */ - public ImmutableSortedMap getSchedule() { - synchronized (this.schedule) { - return ImmutableSortedMap.copyOf(this.schedule); + private void applySimulationResult(Simulator simulator, SimulationResult simulationResult, + boolean updateActiveQuarter) { + final Clock clock; + if (simulator != null) { + // Debug Log best Schedule + logSimulationResult(simulator, simulationResult); + clock = simulator.gsc.clock(); + } else { + clock = Clock.systemDefaultZone(); } + + final var thisQuarter = roundDownToQuarter(ZonedDateTime.now(clock)); + final var nextQuarter = thisQuarter.plusMinutes(15); + + // Store result + this.simulationResult = simulationResult; + + // Send Schedule to Controllers + simulationResult.schedules().forEach((esh, schedule) -> { + esh.applySchedule(schedule // + .tailMap(updateActiveQuarter // + ? thisQuarter // update also current quarter + : nextQuarter)); // otherwise -> start with next quarter + }); } private void traceLog(Supplier message) { @@ -155,4 +218,34 @@ private void traceLog(Supplier message) { case TRACE -> this.log.info("OPTIMIZER " + message.get()); } } + + /** + * Gets the {@link SimulationResult}. + * + * @return {@link SimulationResult} + */ + public SimulationResult getSimulationResult() { + return this.simulationResult; + } + + /** + * Output for Controller.Debug.Log. + * + * @return the debug log output + */ + public String debugLog() { + var b = new StringBuilder(); + if (this.simulationResult.periods().isEmpty()) { + b.append("No Schedule available"); + } else { + b.append("ScheduledPeriods:" + this.simulationResult.periods().size()); + } + var simulator = this.simulator; + if (simulator != null) { + var stats = simulator.cache.stats(); + b.append("|SimulationCounter:" + stats.loadCount()); + } + b.append("|PerQuarter:" + this.simulationsPerQuarterChannel.value()); + return b.toString(); + } } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/QuickSchedules.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/QuickSchedules.java new file mode 100644 index 00000000000..57dfa9cf80f --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/QuickSchedules.java @@ -0,0 +1,217 @@ +package io.openems.edge.energy.optimizer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableSortedMap; + +import io.jenetics.Genotype; +import io.jenetics.IntegerChromosome; +import io.jenetics.IntegerGene; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; + +/** + * This class helps finding good Schedules that are quickly available. + */ +public class QuickSchedules { + + private QuickSchedules() { + } + + /** + * Finds the best quick Schedule, i.e. the one with the lowest cost. + * + * @param simulator the {@link Simulator} + * @param simulationResult the existing {@link SimulationResult}, or null + * @return the winner {@link Genotype}; or null + */ + public static Genotype findBestQuickSchedule(Simulator simulator, SimulationResult simulationResult) { + double lowestCost = 0.; + Genotype bestGt = null; + for (var gt : generateQuickSchedules(simulator.gsc, simulationResult)) { + var cost = simulator.calculateCost(gt); + if (bestGt == null || cost < lowestCost) { + bestGt = gt; + lowestCost = cost; + } + } + return bestGt; + } + + /** + * Generate quick Schedules. + * + * @param gsc the {@link GlobalSimulationsContext} + * @param simulationResult the existing {@link SimulationResult}, or null + * @return a List of {@link Genotype}s, entries can be null + */ + public static List> generateQuickSchedules(GlobalSimulationsContext gsc, + SimulationResult simulationResult) { + return Stream // + .concat(// + variationsOfAllStatesDefault(gsc), // + variationsFromExistingSimulationResult(gsc, simulationResult)) // + .filter(Objects::nonNull) // + .distinct() // + .toList(); + } + + /** + * Builds {@link Genotype}s with all states default and all possible variations + * for first period. + * + * @param gsc the {@link GlobalSimulationsContext} + * @return a Stream of {@link Genotype}s or nulls + */ + protected static Stream> variationsOfAllStatesDefault(GlobalSimulationsContext gsc) { + return generateAllCombinations(gsc).stream() // + .map(combination -> toGenotypeOrNull(gsc.handlers().stream() // + .filter(EnergyScheduleHandler.WithDifferentStates.class::isInstance) // + .map(EnergyScheduleHandler.WithDifferentStates.class::cast) // + .map(esh -> { + final var defaultState = esh.getDefaultStateIndex(); + final var noOfStates = esh.getAvailableStates().length; + final var firstState = combination.get(esh); + return IntegerChromosome.of(IntStream.range(0, gsc.periods().size()) // + .map(i -> i == 0 // + ? firstState // first period + : defaultState) // remaining periods + .mapToObj(state -> IntegerGene.of(state, 0, noOfStates)) // + .toList()); + }) // + .toList())); + } + + /** + * Builds {@link Genotype}s with all states from an existing + * {@link SimulationResult} and all possible variations for first period. + * + * @param gsc the {@link GlobalSimulationsContext} + * @param simulationResult the {@link SimulationResult} + * @return a Stream of {@link Genotype}s or nulls + */ + protected static Stream> variationsFromExistingSimulationResult(GlobalSimulationsContext gsc, + SimulationResult simulationResult) { + return generateAllCombinations(gsc).stream() // + .map(combination -> toGenotypeOrNull(gsc.handlers().stream() // + .filter(EnergyScheduleHandler.WithDifferentStates.class::isInstance) // + .map(EnergyScheduleHandler.WithDifferentStates.class::cast) // + .map(esh -> { + final var firstState = combination.get(esh); + final var existingSchedule = simulationResult.schedules().getOrDefault(esh, + ImmutableSortedMap.of()); + final var defaultState = esh.getDefaultStateIndex(); + final var noOfStates = esh.getAvailableStates().length; + return IntegerChromosome.of(IntStream.range(0, gsc.periods().size()) // + .map(i -> { + if (i == 0) { // + return firstState; // first period + } + // remaining periods + var period = gsc.periods().get(i); + var previousState = existingSchedule.get(period.time()); + if (previousState != null) { + return previousState.stateIndex(); + } + return defaultState; + }) // + .mapToObj(state -> IntegerGene.of(state, 0, noOfStates)) // + .toList()); + }) // + .toList())); + } + + /** + * Builds a {@link Genotype} of an existing {@link SimulationResult}. + * + * @param gsc the {@link GlobalSimulationsContext} + * @param simulationResult the {@link SimulationResult} + * @return the {@link Genotype} or null + */ + protected static Genotype fromExistingSimulationResult(GlobalSimulationsContext gsc, + SimulationResult simulationResult) { + if (simulationResult == null) { + return null; + } + return toGenotypeOrNull(gsc.handlers().stream() // + .filter(EnergyScheduleHandler.WithDifferentStates.class::isInstance) // + .map(EnergyScheduleHandler.WithDifferentStates.class::cast) // + .map(esh -> { + final var existingSchedule = simulationResult.schedules().getOrDefault(esh, + ImmutableSortedMap.of()); + final var defaultState = esh.getDefaultStateIndex(); + final var noOfStates = esh.getAvailableStates().length; + return IntegerChromosome.of(gsc.periods().stream() // + .map(p -> { + var previousState = existingSchedule.get(p.time()); + var state = previousState == null // + ? defaultState // + : previousState.stateIndex(); + return IntegerGene.of(state, 0, noOfStates); + }) // + .toList()); + }) // + .toList()); + } + + private static Genotype toGenotypeOrNull(List cs) { + if (cs.isEmpty()) { + return null; + } + return Genotype.of(cs); + } + + /** + * Generates all possible combinations of + * {@link EnergyScheduleHandler.WithDifferentStates} and state-index for a + * Period. + * + * @param gsc {@link GlobalSimulationsContext} + * @return combinations + */ + @SuppressWarnings("rawtypes") + private static List> generateAllCombinations( + GlobalSimulationsContext gsc) { + if (gsc == null) { + return List.of(); + } + + var eshs = gsc.handlers().stream() // + .filter(EnergyScheduleHandler.WithDifferentStates.class::isInstance) // + .map(EnergyScheduleHandler.WithDifferentStates.class::cast) // + .toList(); + + List> result = new ArrayList<>(); + generateCombinationsRecursive(eshs, 0, new HashMap<>(), result); + return result; + } + + @SuppressWarnings("rawtypes") + private static void generateCombinationsRecursive(List inputList, + int index, Map currentCombination, + List> result) { + // Base case: If we've added a combination for each input, add the result to the + // list. + if (index == inputList.size()) { + result.add(new HashMap<>(currentCombination)); // Add a copy of the current map + return; + } + + // Get the current input + var currentInput = inputList.get(index); + + // Loop through all possible values for this input, from 0 to maxValue + for (int value = 0; value < currentInput.getAvailableStates().length; value++) { + currentCombination.put(currentInput, value); // Set this value in the map + // Recur to the next input + generateCombinationsRecursive(inputList, index + 1, currentCombination, result); + currentCombination.remove(currentInput); // Backtrack + } + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/SimulationResult.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/SimulationResult.java new file mode 100644 index 00000000000..ae7930cc03a --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/SimulationResult.java @@ -0,0 +1,177 @@ +package io.openems.edge.energy.optimizer; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Locale; +import java.util.function.BiConsumer; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; + +import io.jenetics.Genotype; +import io.jenetics.IntegerChromosome; +import io.jenetics.IntegerGene; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period.Hour; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period.Quarter; +import io.openems.edge.energy.optimizer.Simulator.EshToState; + +public record SimulationResult(// + double cost, // + ImmutableMap periods, // + ImmutableMap, // + ImmutableSortedMap> schedules) { + + /** + * A Period in a {@link SimulationResult}. Duration of one period is always one + * quarter. + */ + public record Period(// + GlobalSimulationsContext.Period context, // + EnergyFlow energyFlow, // + int essInitialEnergy // + ) { + + /** + * Constructor for {@link Period}. + * + * @param context the {@link GlobalSimulationsContext} + * @param energyFlow the {@link EnergyFlowV1.Solution} + * @param essInitialEnergy the initial ESS energy in the beginning of the period + * in [Wh] + * @return a {@link Period} + */ + public static Period from(GlobalSimulationsContext.Period context, EnergyFlow energyFlow, + int essInitialEnergy) { + return new Period(context, energyFlow, essInitialEnergy); + } + } + + /** + * An empty {@link SimulationResult}. + */ + public static final SimulationResult EMPTY = new SimulationResult(0., ImmutableMap.of(), ImmutableMap.of()); + + /** + * Re-Simulate a {@link Genotype} to create a {@link SimulationResult}. + * + * @param cache the {@link GenotypeCache} + * @param gsc the {@link GlobalSimulationsContext} + * @param gt the {@link Genotype} + * @return the {@link SimulationResult} + */ + private static SimulationResult from(GlobalSimulationsContext gsc, Genotype gt) { + var allPeriods = ImmutableMap.builder(); + var allEshToStates = new ArrayList(); + var cost = Simulator.simulate(gsc, gt, new Simulator.BestScheduleCollector(// + p -> allPeriods.put(p.context().time(), p), // + allEshToStates::add)); + + var schedules = allEshToStates.stream() // + .collect(toImmutableMap(EshToState::esh, // + eshToState -> ImmutableSortedMap.of(eshToState.period().context.time(), + new EnergyScheduleHandler.WithDifferentStates.Period.Transition( + eshToState.postProcessedStateIndex(), eshToState.period().context.price(), + eshToState.period().energyFlow, eshToState.period().essInitialEnergy)), + (a, b) -> ImmutableSortedMap.naturalOrder() + .putAll(a).putAll(b).build())); + + return new SimulationResult(cost, allPeriods.build(), schedules); + } + + /** + * Re-Simulate a {@link Genotype} to create a {@link SimulationResult}. + * + *

    + * This method re-simulates using the {@link Quarter} periods and not (only) the + * {@link Hour} periods. + * + * @param gsc the {@link GlobalSimulationsContext} + * @param gt the {@link Genotype} + * @return the {@link SimulationResult} + */ + public static SimulationResult fromQuarters(GlobalSimulationsContext gsc, Genotype gt) { + if (gsc == null || gt == null) { + return SimulationResult.EMPTY; + } + + // Convert to Quarters + final GlobalSimulationsContext quarterGsc; + final Genotype quarterGt; + { + final var quarterPeriods = ImmutableList.builder(); + final var quarterGenes = gt.stream().map(ignore -> ImmutableList.builder()).toList(); + final BiConsumer add = (j, p) -> { + quarterPeriods.add(p); + for (var i = 0; i < quarterGenes.size(); i++) { + quarterGenes.get(i).add(gt.get(i).get(j)); + } + }; + for (var i = 0; i < gsc.periods().size(); i++) { + var p = gsc.periods().get(i); + if (p instanceof GlobalSimulationsContext.Period.Quarter pq) { + add.accept(i, pq); + } else if (p instanceof GlobalSimulationsContext.Period.Hour ph) { + for (var j = 0; j < ph.quarterPeriods().size(); j++) { + var pq = ph.quarterPeriods().get(j); + add.accept(i, pq); + } + } + } + quarterGsc = new GlobalSimulationsContext(gsc.clock(), gsc.startTime(), gsc.handlers(), gsc.grid(), + gsc.ess(), quarterPeriods.build()); + quarterGt = Genotype.of(quarterGenes.stream() // + .map(gs -> IntegerChromosome.of(gs.build())) // + .toList()); + } + return from(quarterGsc, quarterGt); + } + + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); + + private static void log(StringBuilder b, String format, Object... args) { + b.append(String.format(Locale.ENGLISH, format, args).toString()); + } + + /** + * Builds a log string of this {@link SimulationResult}. + * + * @return log string + */ + public String toLogString(String prefix) { + var b = new StringBuilder(prefix) // + .append("Time Price Production Consumption Ess Grid ProdToCons ProdToGrid ProdToEss GridToCons GridToEss EssToCons EssInitial\n"); + this.periods.entrySet().forEach(e -> { + final var time = e.getKey(); + final var p = e.getValue(); + final var c = p.context; + final var ef = p.energyFlow; + log(b, "%s", prefix); + log(b, "%s ", time.format(TIME_FORMATTER)); + log(b, "%6.2f ", c.price()); + log(b, "%10d ", ef.getProd()); + log(b, "%10d ", ef.getCons()); + log(b, "%6d ", ef.getEss()); + log(b, "%6d ", ef.getGrid()); + log(b, "%10d ", ef.getProdToCons()); + log(b, "%10d ", ef.getProdToGrid()); + log(b, "%9d ", ef.getProdToEss()); + log(b, "%10d ", ef.getGridToCons()); + log(b, "%9d ", ef.getGridToEss()); + log(b, "%9d ", ef.getEssToCons()); + log(b, "%10d ", p.essInitialEnergy); + this.schedules.forEach((esh, schedule) -> { + log(b, "%-15s ", esh.toStateString(schedule.get(time).stateIndex())); + }); + b.append("\n"); + }); + return b.toString(); + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Simulator.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Simulator.java index 0b79a5f6cc2..7c184779768 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Simulator.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Simulator.java @@ -1,177 +1,279 @@ package io.openems.edge.energy.optimizer; +import static com.google.common.base.MoreObjects.toStringHelper; import static io.jenetics.engine.EvolutionResult.toBestGenotype; -import static io.jenetics.engine.Limits.byExecutionTime; -import static io.openems.edge.energy.optimizer.InitialPopulationUtils.buildInitialPopulation; -import static io.openems.edge.energy.optimizer.Utils.paramsAreValid; -import static io.openems.edge.energy.optimizer.Utils.postprocessSimulatorState; -import static java.lang.Math.max; -import static java.time.Duration.ofSeconds; - -import java.time.ZonedDateTime; -import java.util.concurrent.atomic.AtomicInteger; +import static io.openems.edge.energy.optimizer.QuickSchedules.fromExistingSimulationResult; +import static java.lang.Thread.currentThread; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import io.jenetics.Genotype; import io.jenetics.IntegerChromosome; import io.jenetics.IntegerGene; import io.jenetics.engine.Engine; -import io.jenetics.engine.EvolutionResult; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Params.Length; -import io.openems.edge.energy.optimizer.Params.OptimizePeriod; +import io.jenetics.engine.EvolutionStream; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.AbstractEnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.EnergyFlow; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.OneSimulationContext; public class Simulator { /** Used to incorporate charge/discharge efficiency. */ public static final double EFFICIENCY_FACTOR = 1.17; - public record Period(OptimizePeriod op, StateMachine state, int essInitial, EnergyFlow ef) { + private static final Logger LOG = LoggerFactory.getLogger(Simulator.class); + + public final GlobalSimulationsContext gsc; + + protected final LoadingCache, Double> cache; + + public Simulator(GlobalSimulationsContext gsc) { + this.gsc = gsc; + this.cache = CacheBuilder.newBuilder() // + .recordStats() // + .build(new CacheLoader, Double>() { + + @Override + /** + * Simulates a Schedule and calculates the cost. + * + *

    + * NOTE: do not throw an Exception here, because we use + * {@link LoadingCache#getUnchecked(Object)} below. + * + * @param gt the Schedule as a {@link Genotype} + * @return the cost, lower is better, always positive; {@link Double#NaN} on + * error + */ + public Double load(final Genotype gt) { + return simulate(Simulator.this.gsc, gt, null); + } + }); + + // Initialize the EnergyScheduleHandlers. + for (var esh : gsc.handlers()) { + ((AbstractEnergyScheduleHandler) esh /* this is safe */).initialize(gsc); + } } /** * Simulates a Schedule and calculates the cost. * - * @param p the {@link Params} - * @param schedule the {@link StateMachine} states of the Schedule - * @return the cost, lower is better; always positive + *

    + * This method internally uses a Cache for {@link Genotype}s. + * + * @param gt the Schedule as a {@link Genotype} + * @return the cost, lower is better, always positive; {@link Double#NaN} on + * error */ - protected static double calculateCost(Params p, StateMachine[] schedule) { - final var nextEssInitial = new AtomicInteger(p.essInitialEnergy()); - var sum = 0.; - for (var i = 0; i < p.optimizePeriods().size(); i++) { - sum += simulatePeriod(p, p.optimizePeriods().get(i), schedule[i], nextEssInitial, null); - } - return sum; + public double calculateCost(Genotype gt) { + return this.cache.getUnchecked(gt); } /** - * Simulates a Schedule in quarterly periods. + * Simulates a Schedule and calculates the cost. + * + *

    + * This method does not a Cache for {@link Genotype}s. * - * @param p the {@link Params} - * @param schedule the {@link StateMachine} states of the Schedule - * @return a Map of {@link Period}s + * @param gt the simulated {@link Genotype} + * @param bestScheduleCollector the {@link BestScheduleCollector} + * @return the cost, lower is better, always positive; + * {@link Double#POSITIVE_INFINITY} on error */ - protected static ImmutableSortedMap simulate(Params p, StateMachine[] schedule) { - final var nextEssInitial = new AtomicInteger(p.essInitialEnergy()); - var result = ImmutableSortedMap.naturalOrder(); - for (var i = 0; i < p.optimizePeriods().size(); i++) { - var state = schedule[i]; - var op = p.optimizePeriods().get(i); - var length = op.quarterPeriods().size() == 1 ? Length.QUARTER : Length.HOUR; - // Convert mixed OptimizePeriods to pure quarterly - for (var qp : op.quarterPeriods()) { - var quarterlyOp = new OptimizePeriod(qp.time(), length, qp.essMaxChargeEnergy(), - qp.essMaxDischargeEnergy(), qp.essChargeInChargeGrid(), qp.maxBuyFromGrid(), qp.production(), - qp.consumption(), qp.price(), ImmutableList.of(qp)); - simulatePeriod(p, quarterlyOp, state, nextEssInitial, period -> result.put(period.op().time(), period)); - } + public double simulate(Genotype gt, BestScheduleCollector bestScheduleCollector) { + return simulate(this.gsc, gt, bestScheduleCollector); + } + + protected static double simulate(GlobalSimulationsContext gsc, Genotype gt, + BestScheduleCollector bestScheduleCollector) { + final var osc = OneSimulationContext.from(gsc); + final var noOfPeriods = gsc.periods().size(); + + var sum = 0.; + for (var period = 0; period < noOfPeriods; period++) { + sum += simulatePeriod(osc, gt, period, bestScheduleCollector); } - return result.build(); + return sum; } /** * Calculates the cost of one Period under the given Schedule. * - * @param p the {@link Params} - * @param op the current {@link OptimizePeriod} - * @param state the {@link StateMachine} of the current period - * @param nextEssInitial the initial SoC-Energy; also used as return value - * @param collect a {@link Consumer} to collect the simulation results if - * required. We are not always collecting results to - * reduce workload during simulation. - * @return the cost, lower is better; always positive + * @param simulation the {@link OneSimulationContext} + * @param gt the simulated {@link Genotype} + * @param periodIndex the index of the simulated period + * @param bestScheduleCollector the {@link BestScheduleCollector}; or null + * @return the cost, lower is better, always positive; + * {@link Double#POSITIVE_INFINITY} on error */ - protected static double simulatePeriod(Params p, OptimizePeriod op, StateMachine state, - final AtomicInteger nextEssInitial, Consumer collect) { - // Constants - final var essInitial = max(0, nextEssInitial.get()); // always at least '0' + public static double simulatePeriod(OneSimulationContext simulation, Genotype gt, int periodIndex, + BestScheduleCollector bestScheduleCollector) { + final var period = simulation.global.periods().get(periodIndex); + final var handlers = simulation.global.handlers(); + final var model = EnergyFlow.Model.from(simulation, period); - // Calculate Energy-Flow - final var ef = switch (state) { - case BALANCING -> EnergyFlow.withBalancing(p, op, essInitial); - case DELAY_DISCHARGE -> EnergyFlow.withDelayDischarge(p, op, essInitial); - case CHARGE_GRID -> EnergyFlow.withChargeGrid(p, op, essInitial); - }; + var eshIndex = 0; + for (var esh : handlers) { + if (esh instanceof EnergyScheduleHandler.WithDifferentStates e) { + // Simulate with state given by Genotype + e.simulatePeriod(simulation, period, model, gt.get(eshIndex++).get(periodIndex).intValue()); + } else if (esh instanceof EnergyScheduleHandler.WithOnlyOneState e) { + e.simulatePeriod(simulation, period, model); + } + } - nextEssInitial.set(essInitial - ef.ess()); + final EnergyFlow energyFlow = model.solve(); + + if (energyFlow == null) { + LOG.error("Error while simulating period [" + periodIndex + "]"); + // TODO add configurable debug logging + // LOG.info(simulation.toString()); + // model.logConstraints(); + // model.logMinMaxValues(); + return Double.POSITIVE_INFINITY; + } // Calculate Cost + // TODO should be done also by ESH to enable this use-case: + // https://community.openems.io/t/limitierung-bei-negativen-preisen-und-lastgang-einkauf/2713/2 double cost; - if (ef.grid() > 0) { + if (energyFlow.getGrid() > 0) { // Filter negative prices - var price = max(0, op.price()); + var price = Math.max(0, period.price()); cost = // Cost for direct Consumption - ef.gridToConsumption() * price + energyFlow.getGridToCons() * price // Cost for future Consumption after storage - + ef.gridToEss() * price * EFFICIENCY_FACTOR; + + energyFlow.getGridToEss() * price * EFFICIENCY_FACTOR; } else { // Sell-to-Grid cost = 0.; } - if (collect != null) { - var postprocessedState = postprocessSimulatorState(state, // - EnergyFlow.withBalancing(p, op, essInitial), // - EnergyFlow.withDelayDischarge(p, op, essInitial), // - EnergyFlow.withChargeGrid(p, op, essInitial)); - collect.accept(new Period(op, postprocessedState, essInitial, ef)); + if (bestScheduleCollector != null) { + final var srp = SimulationResult.Period.from(period, energyFlow, simulation.getEssInitial()); + bestScheduleCollector.allPeriods.accept(srp); + eshIndex = 0; + for (var esh : handlers) { + if (esh instanceof EnergyScheduleHandler.WithDifferentStates e) { + bestScheduleCollector.eshStates.accept(new EshToState(e, srp, // + e.postProcessPeriod(period, simulation, energyFlow, + gt.get(eshIndex++).get(periodIndex).intValue()))); + } + } } + + // Prepare for next period + simulation.calculateEssInitial(energyFlow.getEss()); + return cost; } /** - * Runs the optimization with default settings. + * Runs the optimization and returns the "best" simulation result. * - * @param p the {@link Params} - * @param executionLimitSeconds limit.byExecutionTime.ofSeconds - * @return the best schedule + * @param previousResult the {@link SimulationResult} of the + * previous optimization run + * @param engineInterceptor an interceptor for the + * {@link Engine.Builder} + * @param evolutionStreamInterceptor an interceptor for the + * {@link EvolutionStream} + * @return the best Schedule */ - protected static StateMachine[] getBestSchedule(Params p, long executionLimitSeconds) { - return getBestSchedule(p, executionLimitSeconds, null, null); - } + public SimulationResult getBestSchedule(SimulationResult previousResult, + Function, Engine.Builder> engineInterceptor, + Function, EvolutionStream> evolutionStreamInterceptor) { + // Genotype: + // - Separate IntegerChromosome per EnergyScheduleHandler WithDifferentStates + // - Chromosome length = number of periods + // - Integer-Genes represent the state + final var chromosomes = this.gsc.handlers().stream() // + .filter(EnergyScheduleHandler.WithDifferentStates.class::isInstance) // + .map(EnergyScheduleHandler.WithDifferentStates.class::cast) // + .map(esh -> IntegerChromosome.of(0, esh.getAvailableStates().length, this.gsc.periods().size())) // + .toList(); + if (chromosomes.isEmpty()) { + return SimulationResult.EMPTY; + } + final var gtf = Genotype.of(chromosomes); - protected static StateMachine[] getBestSchedule(Params p, long executionLimitSeconds, Integer populationSize, - Integer limit) { - // Return pure BALANCING Schedule if no predictions are available - if (!paramsAreValid(p)) { - return p.optimizePeriods().stream() // - .map(op -> StateMachine.BALANCING) // - .toArray(StateMachine[]::new); + // Decide for single- or multi-threading + final Executor executor; + final var availableCores = Runtime.getRuntime().availableProcessors() - 1; + if (availableCores > 1) { + // Executor is a Thread-Pool with CPU-Cores minus one + executor = new ForkJoinPool(availableCores); + System.out.println("OPTIMIZER Executor runs on " + availableCores + " cores"); + } else { + // Executor is the current thread + executor = Runnable::run; + System.out.println("OPTIMIZER Executor runs on current thread"); } - var gtf = Genotype.of(IntegerChromosome.of(IntegerGene.of(0, p.states().length)), p.optimizePeriods().size()); // - var eval = (Function, Double>) (gt) -> { - var modes = new StateMachine[p.optimizePeriods().size()]; - for (var i = 0; i < modes.length; i++) { - modes[i] = p.states()[gt.get(i).get(0).intValue()]; - } - return calculateCost(p, modes); - }; + // Build the Jenetics Engine var engine = Engine // - .builder(eval, gtf) // - .executor(Runnable::run) // current thread + .builder(this.cache::getUnchecked, gtf) // + .executor(executor) // .minimizing(); - if (populationSize != null) { - engine.populationSize(populationSize); // + if (engineInterceptor != null) { + engine = engineInterceptor.apply(engine); + } + + // Start with previous simulation result as initial population if available + var initialPopulation = fromExistingSimulationResult(this.gsc, previousResult); + EvolutionStream stream; + if (previousResult != null) { + stream = engine.build().stream(List.of(initialPopulation)); + } else { + stream = engine.build().stream(); } - Stream> stream = engine.build() // - .stream(buildInitialPopulation(p)) // - .limit(byExecutionTime(ofSeconds(executionLimitSeconds))); // - if (limit != null) { - stream = stream.limit(limit); // apply optional limit + stream = stream.limit(result -> !currentThread().isInterrupted()); + if (evolutionStreamInterceptor != null) { + stream = evolutionStreamInterceptor.apply(stream); } + + // Start the evaluation var bestGt = stream // .collect(toBestGenotype()); - return IntStream.range(0, p.optimizePeriods().size()) // - .mapToObj(period -> p.states()[bestGt.get(period).get(0).intValue()]) // - .toArray(StateMachine[]::new); + + return SimulationResult.fromQuarters(this.gsc, bestGt); + } + + protected static record BestScheduleCollector(// + Consumer allPeriods, // + Consumer eshStates) { + } + + protected static record EshToState(// + EnergyScheduleHandler.WithDifferentStates esh, // + SimulationResult.Period period, // + int postProcessedStateIndex) { + } + + /** + * Builds a log string of this {@link Simulator}. + * + * @param prefix a line prefix + * @return log string + */ + public String toLogString(String prefix) { + return prefix + toStringHelper(this) // + .addValue(this.gsc) // + .addValue(this.cache.stats()) // + .toString(); } } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Utils.java b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Utils.java index 4bc3d97858c..e703ab1e0c6 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Utils.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Utils.java @@ -1,89 +1,44 @@ package io.openems.edge.energy.optimizer; -import static com.google.common.collect.Streams.concat; import static io.openems.common.utils.DateUtils.roundDownToQuarter; -import static io.openems.edge.common.type.TypeUtils.multiply; -import static io.openems.edge.common.type.TypeUtils.orElse; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController.PERIODS_PER_HOUR; -import static java.lang.Math.max; -import static java.lang.Math.round; -import static java.util.Arrays.stream; +import static java.lang.Thread.sleep; import java.time.Clock; import java.time.Duration; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.random.RandomGeneratorFactory; -import java.util.stream.IntStream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; -import com.google.common.collect.Streams; +import com.google.common.collect.Ordering; import io.jenetics.util.RandomRegistry; -import io.openems.common.exceptions.InvalidValueException; -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.timedata.Resolution; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingSupplier; import io.openems.common.types.ChannelAddress; -import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; -import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; -import io.openems.edge.energy.jsonrpc.GetScheduleResponse; -import io.openems.edge.energy.optimizer.ScheduleDatas.ScheduleData; -import io.openems.edge.energy.optimizer.Simulator.Period; -import io.openems.edge.ess.api.SymmetricEss; -import io.openems.edge.timedata.api.Timedata; -import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.scheduler.api.Scheduler; -/** - * Utils for {@link TimeOfUseTariffController}. - * - *

    - * All energy values are in [Wh] and positive, unless stated differently. - */ public final class Utils { private Utils() { } - /** Keep some buffer to avoid scheduling errors because of bad predictions. */ - public static final float ESS_MAX_SOC = 90F; - /** Limit Charge Power for §14a EnWG. */ public static final int ESS_LIMIT_14A_ENWG = -4200; - /** - * C-Rate (capacity divided by time) during {@link StateMachine#CHARGE_GRID}. - * With a C-Rate of 0.5 the battery gets fully charged within 2 hours. - */ - public static final float ESS_CHARGE_C_RATE = 0.5F; - - public static final ChannelAddress SUM_PRODUCTION = new ChannelAddress("_sum", "ProductionActivePower"); - public static final ChannelAddress SUM_CONSUMPTION = new ChannelAddress("_sum", "ConsumptionActivePower"); public static final ChannelAddress SUM_GRID = new ChannelAddress("_sum", "GridActivePower"); - public static final ChannelAddress SUM_UNMANAGED_CONSUMPTION = new ChannelAddress("_sum", - "UnmanagedConsumptionActivePower"); public static final ChannelAddress SUM_ESS_DISCHARGE_POWER = new ChannelAddress("_sum", "EssDischargePower"); public static final ChannelAddress SUM_ESS_SOC = new ChannelAddress("_sum", "EssSoc"); protected static final long EXECUTION_LIMIT_SECONDS_BUFFER = 30; protected static final long EXECUTION_LIMIT_SECONDS_MINIMUM = 60; - private static final Logger LOG = LoggerFactory.getLogger(Utils.class); - /** * Initializes the Jenetics {@link RandomRegistry} for production. */ @@ -121,308 +76,53 @@ private static void initializeRandomRegistry(boolean isUnitTest) { } /** - * Create {@link Params} for {@link Simulator}. + * Creates a {@link Simulator}. * - * @param globalContext the {@link GlobalContext} object - * @param existingSchedule the existing schedule, i.e. result of previous - * optimization - * @return {@link Params} - * @throws InvalidValueException on error - */ - public static Params createSimulatorParams(GlobalContext globalContext, - ImmutableSortedMap existingSchedule) throws InvalidValueException { - final var time = roundDownToQuarter(ZonedDateTime.now()); - - // Prediction values - final var predictionConsumption = joinConsumptionPredictions(4, // - globalContext.predictorManager().getPrediction(SUM_CONSUMPTION).asArray(), // - globalContext.predictorManager().getPrediction(SUM_UNMANAGED_CONSUMPTION).asArray()); - final var predictionProduction = generateProductionPrediction(// - globalContext.predictorManager().getPrediction(SUM_PRODUCTION).asArray(), // - predictionConsumption.length); - - // Prices contains the price values and the time it is retrieved. - final var prices = globalContext.timeOfUseTariff().getPrices(); - - // Ess information. - TimeOfUseTariffControllerImpl.Context context = globalContext.energyScheduleHandler().getContext(); - final var essTotalEnergy = context.ess().getCapacity().getOrError(); - final var essMinSocEnergy = getEssMinSocEnergy(context, essTotalEnergy); - final var essMaxSocEnergy = round(ESS_MAX_SOC / 100F * essTotalEnergy); - final var essSoc = context.ess().getSoc().getOrError(); - final var essSocEnergy = essTotalEnergy /* [Wh] */ / 100 * essSoc; - - // Power Values for scheduling battery for individual periods. - var maxDischargePower = globalContext.sum().getEssMaxDischargePower().orElse(1000 /* at least 1000 */); - var maxChargePower = globalContext.sum().getEssMaxDischargePower().orElse(-1000 /* at least 1000 */); - if (context.limitChargePowerFor14aEnWG()) { - maxChargePower = max(ESS_LIMIT_14A_ENWG, maxChargePower); // Apply §14a EnWG limit - } - - return Params.create() // - .setTime(time) // - .setEssTotalEnergy(essTotalEnergy) // - .setEssMinSocEnergy(essMinSocEnergy) // - .setEssMaxSocEnergy(essMaxSocEnergy) // - .setEssInitialEnergy(essSocEnergy) // - .setEssMaxChargeEnergy(toEnergy(Math.abs(maxChargePower))) // - .setEssMaxDischargeEnergy(toEnergy(maxDischargePower)) // - .seMaxBuyFromGrid(toEnergy(context.maxChargePowerFromGrid())) // - .setProductions(stream(interpolateArray(predictionProduction)).map(v -> toEnergy(v)).toArray()) // - .setConsumptions(stream(interpolateArray(predictionConsumption)).map(v -> toEnergy(v)).toArray()) // - .setPrices(interpolateArray(prices.asArray())) // - .setStates(context.controlMode().states) // - .setExistingSchedule(existingSchedule) // - .build(); - } - - /** - * Postprocesses production prediction; makes sure length is at least the same - * as consumption prediction - filling up with zeroes. - * - * @param prediction the production prediction - * @param minLength the min length (= consumption prediction length) - * @return new production prediction - */ - protected static Integer[] generateProductionPrediction(Integer[] prediction, int minLength) { - if (prediction.length >= minLength) { - return prediction; - } - return IntStream.range(0, minLength) // - .mapToObj(i -> i > prediction.length - 1 ? 0 : prediction[i]) // - .toArray(Integer[]::new); - } - - protected static Integer[] joinConsumptionPredictions(int splitAfterIndex, Integer[] totalConsumption, - Integer[] unmanagedConsumption) { - return Streams.concat(// - stream(totalConsumption) // - .limit(splitAfterIndex), // - stream(unmanagedConsumption) // - .skip(splitAfterIndex)) // - .toArray(Integer[]::new); - } - - protected static boolean paramsAreValid(Params p) { - if (p.optimizePeriods().isEmpty()) { - // No periods are available - LOG.warn("No periods are available"); - return false; - } - if (p.optimizePeriods().stream() // - .allMatch(pp -> pp.production() == 0 && pp.consumption() == 0)) { - // Production and Consumption predictions are all zero - LOG.warn("Production and Consumption predictions are all zero"); - return false; - } - if (p.optimizePeriods().stream() // - .mapToDouble(Params.OptimizePeriod::price) // - .distinct() // - .count() <= 1) { - // Prices are all the same - LOG.info("Prices are all the same"); - return false; - } - - return true; - } - - /** - * Returns the amount of energy that is not available for scheduling because of - * a configured minimum SoC. - * - * @param context the {@link TimeOfUseTariffControllerImpl.Context} - * @param essCapacity net {@link SymmetricEss.ChannelId#CAPACITY} - * @return the value in [Wh] - */ - protected static int getEssMinSocEnergy(TimeOfUseTariffControllerImpl.Context context, int essCapacity) { - return essCapacity /* [Wh] */ / 100 // - * getEssMinSocPercentage(// - context.ctrlLimitTotalDischarges(), // - context.ctrlEmergencyCapacityReserves()); - } + *

    + * This will possibly run forever and call the callbacks multiple times before + * returning. + * + * @param gscSupplier a {@link Supplier} for {@link GlobalSimulationsContext} + * @param interruptFlag a flag to interrupt the threads + * @param simulator a callback for a {@link Simulator}; possibly null + * @param error a callback for a error string + */ + public static synchronized void createSimulator( + ThrowingSupplier gscSupplier, AtomicBoolean interruptFlag, + Consumer simulator, Consumer> error) { + while (!interruptFlag.get()) { + try { + simulator.accept(new Simulator(gscSupplier.get())); + return; - /** - * Returns the configured minimum SoC, or zero. - * - * @param ctrlLimitTotalDischarges the list of - * {@link ControllerEssLimitTotalDischarge} - * @param ctrlEmergencyCapacityReserves the list of - * {@link ControllerEssEmergencyCapacityReserve} - * @return the value in [%] - */ - public static int getEssMinSocPercentage(List ctrlLimitTotalDischarges, - List ctrlEmergencyCapacityReserves) { - return concat(// - ctrlLimitTotalDischarges.stream() // - .map(ctrl -> ctrl.getMinSoc().get()) // - .filter(Objects::nonNull) // - .mapToInt(v -> max(0, v)), // only positives - ctrlEmergencyCapacityReserves.stream() // - .map(ctrl -> ctrl.getActualReserveSoc().get()) // - .filter(Objects::nonNull) // - .mapToInt(v -> max(0, v))) // only positives - .max().orElse(0); - } + } catch (OpenemsException | IllegalArgumentException e) { + e.printStackTrace(); - /** - * Interpolate an Array of {@link Double}s. - * - *

    - * Replaces nulls with previous value. If first entry is null, it is set to - * first available value. If all values are null, all are set to 0. - * - * @param values the values - * @return values without nulls - */ - protected static double[] interpolateArray(Double[] values) { - var firstNonNull = stream(values) // - .filter(Objects::nonNull) // - .findFirst(); - var lastNonNullIndex = IntStream.range(0, values.length) // - .filter(i -> values[i] != null) // - .reduce((first, second) -> second); - if (lastNonNullIndex.isEmpty()) { - return new double[0]; - } - var result = new double[lastNonNullIndex.getAsInt() + 1]; - if (firstNonNull.isEmpty()) { - // all null - return result; - } - double last = firstNonNull.get(); - for (var i = 0; i < result.length; i++) { - double value = orElse(values[i], last); - result[i] = last = value; - } - return result; - } + simulator.accept(null); + error.accept(() -> "Stuck trying to get GlobalSimulationsContext. " + e.getMessage()); - /** - * Interpolate an Array of {@link Integer}s. - * - *

    - * Replaces nulls with previous value. If first entry is null, it is set to - * first available value. If all values are null, all are set to 0. - * - * @param values the values - * @return values without nulls - */ - protected static int[] interpolateArray(Integer[] values) { - var firstNonNull = stream(values) // - .filter(Objects::nonNull) // - .findFirst(); - var lastNonNullIndex = IntStream.range(0, values.length) // - .filter(i -> values[i] != null) // - .reduce((first, second) -> second); // - if (lastNonNullIndex.isEmpty()) { - return new int[0]; - } - var result = new int[lastNonNullIndex.getAsInt() + 1]; - if (firstNonNull.isEmpty()) { - // all null - return result; - } - int last = firstNonNull.get(); - for (var i = 0; i < result.length; i++) { - int value = orElse(values[i], last); - result[i] = last = value; - } - return result; - } + try { + sleep(10_000); + } catch (InterruptedException e1) { + e.printStackTrace(); - protected static int findFirstPeakIndex(int fromIndex, double[] values) { - if (values.length <= fromIndex) { - return fromIndex; - } else { - var previous = values[fromIndex]; - for (var i = fromIndex + 1; i < values.length; i++) { - var value = values[i]; - if (value < previous) { - return i - 1; + simulator.accept(null); + error.accept(() -> "Unable to create global simulations context: " + e1.getMessage()); } - previous = value; } } - return values.length - 1; - } - protected static int findFirstValleyIndex(int fromIndex, double[] values) { - if (values.length <= fromIndex) { - return fromIndex; - } else { - var previous = values[fromIndex]; - for (var i = fromIndex + 1; i < values.length; i++) { - var value = values[i]; - if (value > previous) { - return i - 1; - } - previous = value; - } - } - return values.length - 1; + simulator.accept(null); + error.accept(() -> "Unable to create global simulations context -> abort"); } /** - * Utilizes the previous three hours' data and computes the next 21 hours data - * from the {@link Optimizer} provided, then concatenates them to generate a - * 24-hour {@link GetScheduleResponse}. + * Calculates the ExecutionLimitSeconds for the {@link Optimizer}. * - * @param optimizer the {@link Optimizer} - * @param requestId the JSON-RPC request-id - * @param timedata the{@link Timedata} - * @param timeOfUseTariff the {@link TimeOfUseTariff} - * @param componentId the Component-ID - * @param now the current {@link ZonedDateTime} (will get rounded - * down to 15 minutes) - * @return the {@link GetScheduleResponse} - * @throws OpenemsNamedException on error + * @return execution limit in [s] */ - public static GetScheduleResponse handleGetScheduleRequest(Optimizer optimizer, UUID requestId, Timedata timedata, - TimeOfUseTariff timeOfUseTariff, String componentId, ZonedDateTime now) { - final var b = ImmutableList.builder(); - now = roundDownToQuarter(now); - final var fromTime = now.minusHours(3); - - final var params = optimizer.getParams(); - if (params != null) { - // Process last three hours of historic data - final var channelQuarterlyPrices = new ChannelAddress(componentId, "QuarterlyPrices"); - final var channelStateMachine = new ChannelAddress(componentId, "StateMachine"); - try { - var queryResult = timedata.queryHistoricData(null, fromTime, now, // - Set.of(channelQuarterlyPrices, channelStateMachine, // - SUM_GRID, SUM_PRODUCTION, SUM_CONSUMPTION, SUM_ESS_DISCHARGE_POWER, SUM_ESS_SOC), - new Resolution(15, ChronoUnit.MINUTES)); - ScheduleData.fromHistoricDataQuery(// - params.essTotalEnergy(), channelQuarterlyPrices, channelStateMachine, queryResult) // - .forEach(b::add); - } catch (Exception e) { - LOG.warn("Unable to read historic data: " + e.getMessage()); - } - } - - // Process future schedule - final var schedule = optimizer.getSchedule(); - optimizer.getSchedule().values().stream() // - .flatMap(ScheduleData::fromPeriod) // - .forEach(b::add); - - // Find 'toTime' of result - final ZonedDateTime toTime; - if (!schedule.isEmpty()) { - toTime = schedule.lastKey(); - } else { - var pricesPerQuarter = timeOfUseTariff.getPrices().pricePerQuarter; - if (!pricesPerQuarter.isEmpty()) { - toTime = pricesPerQuarter.lastKey(); - } else { - toTime = fromTime; - } - } - - return new GetScheduleResponse(requestId, fromTime, toTime, - new ScheduleDatas(params.essTotalEnergy(), b.build())); + public static long calculateExecutionLimitSeconds() { + return calculateExecutionLimitSeconds(Clock.systemDefaultZone()); } /** @@ -442,64 +142,6 @@ public static long calculateExecutionLimitSeconds(Clock clock) { return Duration.between(now, nextQuarter.plusMinutes(15)).getSeconds(); } - /** - * Post-Process a state of a Period during Simulation, i.e. replace with - * 'better' state with the same behaviour. - * - *

    - * NOTE: heavy computation is ok here, because this method is called only at the - * end with the best Schedule. - * - * @param state the initial state - * @param efBalancing the {@link EnergyFlow} as it would be in - * {@link StateMachine#BALANCING} - * @param efDelayDischarge the {@link EnergyFlow} as it would be in - * {@link StateMachine#DELAY_DISCHARGE} - * @param efChargeGrid the {@link EnergyFlow} as it would be in - * {@link StateMachine#CHARGE_GRID} - * @return the new state - */ - public static StateMachine postprocessSimulatorState(StateMachine state, EnergyFlow efBalancing, - EnergyFlow efDelayDischarge, EnergyFlow efChargeGrid) { - if (state == CHARGE_GRID) { - // CHARGE_GRID,... - if (efChargeGrid.ess() >= efDelayDischarge.ess()) { - // but battery charge/discharge is the same as DELAY_DISCHARGE - state = DELAY_DISCHARGE; - } - } - - if (state == DELAY_DISCHARGE) { - // DELAY_DISCHARGE,... - if (efDelayDischarge.ess() >= efBalancing.ess()) { - // but battery charge/discharge is the same as BALANCING - state = BALANCING; - } - } - - return state; - } - - /** - * Converts power [W] to energy [Wh/15 min]. - * - * @param power the power value - * @return the energy value - */ - public static int toEnergy(int power) { - return power / PERIODS_PER_HOUR; - } - - /** - * Converts energy [Wh/15 min] to power [W]. - * - * @param energy the energy value - * @return the power value - */ - public static Integer toPower(Integer energy) { - return multiply(energy, PERIODS_PER_HOUR); - } - /** * Prints the Schedule to System.out. * @@ -507,36 +149,45 @@ public static Integer toPower(Integer energy) { * NOTE: The output format is suitable as input for "RunOptimizerFromLogApp". * This is useful to re-run a simulation. * - * @param params the {@link Params} - * @param periods the map of {@link Period}s + * @param simulator the {@link Simulator} + * @param simulationResult the {@link SimulationResult} */ - protected static void logSchedule(Params params, ImmutableSortedMap periods) { - System.out.println("OPTIMIZER " + params.toLogString()); - System.out.println(ScheduleDatas.fromSchedule(params.essTotalEnergy(), periods).toLogString("OPTIMIZER ")); + public static void logSimulationResult(Simulator simulator, SimulationResult simulationResult) { + final var prefix = "OPTIMIZER "; + System.out.println(simulator.toLogString(prefix)); + System.out.println(simulationResult.toLogString(prefix)); } /** - * Updates the active Schedule with a new Schedule. - * - *

    - *

      - *
    • Period of the currently active Quarter is never changed - *
    • Old Periods are removed from the Schedule - *
    • Remaining Schedules are updated from new Schedule - *
    + * Sorts the list of {@link EnergySchedulable}s by the order given by + * {@link Scheduler}. * - * @param now the current {@link ZonedDateTime} - * @param schedule the active Schedule - * @param newSchedule the new Schedule + * @param scheduler the {@link Scheduler} + * @param list the list of {@link EnergySchedulable}s + * @return sorted list of {@link EnergySchedulable}s */ - public static void updateSchedule(ZonedDateTime now, TreeMap schedule, - ImmutableSortedMap newSchedule) { - var thisQuarter = roundDownToQuarter(now); - var current = schedule.get(thisQuarter); - schedule.clear(); - schedule.putAll(newSchedule); - if (current != null) { - schedule.put(thisQuarter, current); - } + public static ImmutableList sortByScheduler(Scheduler scheduler, List list) { + var ref = scheduler.getControllers().stream().toList(); + + final Ordering byScheduler = new Ordering() { + public int compare(String left, String right) { + var leftIdx = ref.indexOf(left); + var rightIdx = ref.indexOf(right); + if (leftIdx < 0 && rightIdx < 0) { // both not found + return Objects.compare(left, right, String::compareTo); + } else if (leftIdx < 0) { // only right is in list + return 1; + } else if (rightIdx < 0) { // only left is in list + return -1; + } else { + return leftIdx - rightIdx; + } + } + }; + + return byScheduler // + .onResultOf(EnergySchedulable::id) // + .immutableSortedCopy(list); } + } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleResponse.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponse.java similarity index 90% rename from io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleResponse.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponse.java index e27461abc45..e20510d8fe3 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/jsonrpc/GetScheduleResponse.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/jsonrpc/GetScheduleResponse.java @@ -1,4 +1,4 @@ -package io.openems.edge.energy.jsonrpc; +package io.openems.edge.energy.v1.jsonrpc; import java.time.ZonedDateTime; import java.util.Map.Entry; @@ -9,8 +9,8 @@ import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.utils.JsonUtils; -import io.openems.edge.energy.optimizer.ScheduleDatas; -import io.openems.edge.energy.optimizer.ScheduleDatas.ScheduleData; +import io.openems.edge.energy.v1.optimizer.ScheduleDatas; +import io.openems.edge.energy.v1.optimizer.ScheduleDatas.ScheduleData; /** * Represents a JSON-RPC Response for 'getMeters'. @@ -34,6 +34,7 @@ * } * */ +@Deprecated public class GetScheduleResponse extends JsonrpcResponseSuccess { private final ZonedDateTime fromDate; diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EnergyFlow.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/EnergyFlowV1.java similarity index 76% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EnergyFlow.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/EnergyFlowV1.java index bf108379c74..3de095d0913 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/EnergyFlow.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/EnergyFlowV1.java @@ -1,16 +1,17 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static io.openems.edge.common.type.TypeUtils.fitWithin; import static java.lang.Math.max; import static java.lang.Math.min; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Params.OptimizePeriod; +import io.openems.edge.energy.v1.optimizer.ParamsV1.OptimizePeriod; /** * Simulates a detailed Energy-Flow. */ -public record EnergyFlow(// +@Deprecated +public record EnergyFlowV1(// int production, /* positive */ int consumption, /* positive */ int ess, /* charge negative, discharge positive */ @@ -24,49 +25,49 @@ public record EnergyFlow(// ) { /** - * Simulate {@link EnergyFlow} in {@link StateMachine#BALANCING}. + * Simulate {@link EnergyFlowV1} in {@link StateMachine#BALANCING}. * - * @param p the {@link Params} + * @param p the {@link ParamsV1} * @param op the {@link OptimizePeriod} * @param essInitial ESS Initially Available Energy (SoC in [Wh]) - * @return the {@link EnergyFlow} + * @return the {@link EnergyFlowV1} */ - public static EnergyFlow withBalancing(Params p, OptimizePeriod op, int essInitial) { + public static EnergyFlowV1 withBalancing(ParamsV1 p, OptimizePeriod op, int essInitial) { return create(p, op, essInitial, // p.essTotalEnergy(), // Allow Balancing till full battery op.consumption() - op.production()); } /** - * Simulate {@link EnergyFlow} in {@link StateMachine#DELAY_DISCHARGE}. + * Simulate {@link EnergyFlowV1} in {@link StateMachine#DELAY_DISCHARGE}. * - * @param p the {@link Params} + * @param p the {@link ParamsV1} * @param op the {@link OptimizePeriod} * @param essInitial ESS Initially Available Energy (SoC in [Wh]) - * @return the {@link EnergyFlow} + * @return the {@link EnergyFlowV1} */ - public static EnergyFlow withDelayDischarge(Params p, OptimizePeriod op, int essInitial) { + public static EnergyFlowV1 withDelayDischarge(ParamsV1 p, OptimizePeriod op, int essInitial) { return create(p, op, essInitial, // p.essTotalEnergy(), // Allow Delay-Discharge with full battery min(0, op.consumption() - op.production())); // Allow charge; no discharge } /** - * Simulate {@link EnergyFlow} in {@link StateMachine#CHARGE_GRID}. + * Simulate {@link EnergyFlowV1} in {@link StateMachine#CHARGE_GRID}. * - * @param p the {@link Params} + * @param p the {@link ParamsV1} * @param op the {@link OptimizePeriod} * @param essInitial ESS Initially Available Energy (SoC in [Wh]) - * @return the {@link EnergyFlow} + * @return the {@link EnergyFlowV1} */ - public static EnergyFlow withChargeGrid(Params p, OptimizePeriod op, int essInitial) { + public static EnergyFlowV1 withChargeGrid(ParamsV1 p, OptimizePeriod op, int essInitial) { return create(p, op, essInitial, // p.essMaxSocEnergy(), // Allow Charge-Grid only till Max-SoC // Same as Delay-Discharge + Charge-From-Grid min(0, op.consumption() - op.production()) - op.essChargeInChargeGrid()); } - protected static EnergyFlow create(Params p, OptimizePeriod op, int essInitial, int essMaxSocEnergy, + protected static EnergyFlowV1 create(ParamsV1 p, OptimizePeriod op, int essInitial, int essMaxSocEnergy, int essTarget) { var essMaxDischarge = max(0, essInitial - p.essMinSocEnergy()); var essMaxCharge = max(0, essMaxSocEnergy - essInitial); @@ -86,7 +87,7 @@ protected static EnergyFlow create(Params p, OptimizePeriod op, int essInitial, var essToConsumption = max(0, min(op.consumption() - productionToConsumption, ess - productionToGrid)); var gridToConsumption = max(0, op.consumption() - essToConsumption - productionToConsumption); var gridToEss = grid - gridToConsumption + productionToGrid; - return new EnergyFlow(// + return new EnergyFlowV1(// op.production(), /* production */ op.consumption(), /* consumption */ ess, /* ess */ diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/GlobalContext.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/GlobalContextV1.java similarity index 67% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/GlobalContext.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/GlobalContextV1.java index c7b889a6eff..3af5e879047 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/GlobalContext.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/GlobalContextV1.java @@ -1,25 +1,25 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import java.time.Clock; import io.openems.edge.common.sum.Sum; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1; import io.openems.edge.energy.api.EnergyScheduleHandler; import io.openems.edge.predictor.api.manager.PredictorManager; import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; -public record GlobalContext(// +@Deprecated +public record GlobalContextV1(// Clock clock, // - EnergyScheduleHandler energyScheduleHandler, // + EnergyScheduleHandlerV1 energyScheduleHandler, // Sum sum, // PredictorManager predictorManager, // TimeOfUseTariff timeOfUseTariff) { public static class Builder { private Clock clock; - private EnergyScheduleHandler energyScheduleHandler; + private EnergyScheduleHandlerV1 energyScheduleHandler; private Sum sum; private PredictorManager predictorManager; private TimeOfUseTariff timeOfUseTariff; @@ -41,8 +41,7 @@ public Builder setClock(Clock clock) { * @param energyScheduleHandler the {@link EnergyScheduleHandler} * @return myself */ - public Builder setEnergyScheduleHandler( - EnergyScheduleHandler energyScheduleHandler) { + public Builder setEnergyScheduleHandler(EnergyScheduleHandlerV1 energyScheduleHandler) { this.energyScheduleHandler = energyScheduleHandler; return this; } @@ -81,23 +80,23 @@ public Builder setTimeOfUseTariff(TimeOfUseTariff timeOfUseTariff) { } /** - * Builds the {@link GlobalContext}. + * Builds the {@link GlobalContextV1}. * - * @return the {@link GlobalContext} record + * @return the {@link GlobalContextV1} record */ - public GlobalContext build() { - return new GlobalContext(this.clock, this.energyScheduleHandler, this.sum, this.predictorManager, + public GlobalContextV1 build() { + return new GlobalContextV1(this.clock, this.energyScheduleHandler, this.sum, this.predictorManager, this.timeOfUseTariff); } } /** - * Create a {@link GlobalContext} {@link Builder}. + * Create a {@link GlobalContextV1} {@link Builder}. * * @return a {@link Builder} */ public static Builder create() { - return new GlobalContext.Builder(); + return new GlobalContextV1.Builder(); } } diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulationUtils.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/InitialPopulationV1Utils.java similarity index 91% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulationUtils.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/InitialPopulationV1Utils.java index b09e3e3036c..41b99551fcf 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/InitialPopulationUtils.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/InitialPopulationV1Utils.java @@ -1,10 +1,10 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.energy.optimizer.Utils.findFirstPeakIndex; -import static io.openems.edge.energy.optimizer.Utils.findFirstValleyIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; import java.util.Arrays; import java.util.List; @@ -19,11 +19,12 @@ import io.jenetics.IntegerChromosome; import io.jenetics.IntegerGene; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Params.OptimizePeriod; +import io.openems.edge.energy.v1.optimizer.ParamsV1.OptimizePeriod; -public class InitialPopulationUtils { +@Deprecated +public class InitialPopulationV1Utils { - private InitialPopulationUtils() { + private InitialPopulationV1Utils() { } /** @@ -40,10 +41,10 @@ private InitialPopulationUtils() { * sure, that this one wins in case there are other results with same cost, e.g. * when battery never gets empty anyway. * - * @param p the {@link Params} + * @param p the {@link ParamsV1} * @return the {@link Genotype} */ - public static ImmutableList> buildInitialPopulation(Params p) { + public static ImmutableList> buildInitialPopulation(ParamsV1 p) { var states = List.of(p.states()); if (!states.contains(BALANCING)) { throw new IllegalArgumentException("State option BALANCING is always required!"); diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java new file mode 100644 index 00000000000..77fda46f75b --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java @@ -0,0 +1,159 @@ +package io.openems.edge.energy.v1.optimizer; + +import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; +import static io.openems.edge.energy.optimizer.Utils.calculateExecutionLimitSeconds; +import static io.openems.edge.energy.optimizer.Utils.initializeRandomRegistryForProduction; +import static io.openems.edge.energy.v1.optimizer.SimulatorV1.simulate; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.createSimulatorParams; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.logSchedule; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.updateSchedule; +import static java.lang.Thread.sleep; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingSupplier; +import io.openems.common.test.TimeLeapClock; +import io.openems.common.utils.FunctionUtils; +import io.openems.common.worker.AbstractImmediateWorker; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1; +import io.openems.edge.energy.LogVerbosity; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; + +/** + * This task is executed once in the beginning and afterwards every full 15 + * minutes. + */ +@Deprecated +public class OptimizerV1 extends AbstractImmediateWorker { + + private final Logger log = LoggerFactory.getLogger(OptimizerV1.class); + + private final Supplier logVerbosity; + private final ThrowingSupplier globalContext; + private final TreeMap schedule = new TreeMap<>(); + + private ParamsV1 params = null; + + public OptimizerV1(Supplier logVerbosity, // + ThrowingSupplier globalContext) { + this.logVerbosity = logVerbosity; + this.globalContext = globalContext; + initializeRandomRegistryForProduction(); + + // Run Optimizer thread in LOW PRIORITY + this.setPriority(Thread.MIN_PRIORITY); + } + + @Override + public void forever() throws InterruptedException, OpenemsException { + this.traceLog(() -> "Start next run of Optimizer"); + + this.createParams(); // this possibly takes forever + + final var globalContext = this.globalContext.get(); + final var start = Instant.now(globalContext.clock()); + + long executionLimitSeconds; + + // Calculate max execution time till next quarter (with buffer) + executionLimitSeconds = calculateExecutionLimitSeconds(globalContext.clock()); + + // Find best Schedule + var schedule = SimulatorV1.getBestSchedule(this.params, executionLimitSeconds); + + // Re-Simulate and keep best Schedule + var newSchedule = simulate(this.params, schedule); + + // Debug Log best Schedule + logSchedule(this.params, newSchedule); + + // Update Schedule from newly simulated Schedule + synchronized (this.schedule) { + updateSchedule(ZonedDateTime.now(globalContext.clock()), this.schedule, newSchedule); + } + + // Send Schedule to Controller + globalContext.energyScheduleHandler().setSchedule(this.schedule.entrySet().stream()// + .collect(toImmutableSortedMap(// + ZonedDateTime::compareTo, // + Entry::getKey, // + e -> new EnergyScheduleHandlerV1.Period<>(e.getValue().state(), + e.getValue().op().essChargeInChargeGrid())))); + + // Sleep remaining time + if (!(globalContext.clock() instanceof TimeLeapClock)) { + var remainingExecutionLimit = Duration + .between(Instant.now(globalContext.clock()), start.plusSeconds(executionLimitSeconds)).getSeconds(); + if (remainingExecutionLimit > 0) { + this.traceLog(() -> "Sleep [" + remainingExecutionLimit + "s] till next run of Optimizer"); + sleep(remainingExecutionLimit * 1000); + } + } + } + + /** + * Try forever till all data is available (e.g. ESS Capacity) + * + * @throws InterruptedException during sleep + */ + private void createParams() throws InterruptedException { + while (true) { + try { + synchronized (this.schedule) { + this.params = createSimulatorParams(this.globalContext.get(), // + this.schedule.entrySet().stream() // + .collect(toImmutableSortedMap(// + ZonedDateTime::compareTo, // + Entry::getKey, e -> e.getValue().state()))); + return; + } + + } catch (OpenemsException e) { + this.traceLog(() -> "Stuck trying to get Params. " + e.getMessage()); + this.params = null; + synchronized (this.schedule) { + this.schedule.clear(); + } + sleep(30_000); + } + } + } + + /** + * Gets the current {@link ParamsV1} or null. + * + * @return the {@link ParamsV1} or null + */ + public ParamsV1 getParams() { + return this.params; + } + + /** + * Gets a copy of the Schedule. + * + * @return {@link ImmutableSortedMap} + */ + public ImmutableSortedMap getSchedule() { + synchronized (this.schedule) { + return ImmutableSortedMap.copyOf(this.schedule); + } + } + + private void traceLog(Supplier message) { + switch (this.logVerbosity.get()) { + case NONE, DEBUG_LOG -> FunctionUtils.doNothing(); + case TRACE -> this.log.info("OPTIMIZER " + message.get()); + } + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ParamsUtils.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1.java similarity index 88% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ParamsUtils.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1.java index cd1e73e75ec..8cca4e3bfdd 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ParamsUtils.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsUtilsV1.java @@ -1,10 +1,10 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static com.google.common.math.Quantiles.percentiles; -import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController.PERIODS_PER_HOUR; -import static io.openems.edge.energy.optimizer.Utils.ESS_CHARGE_C_RATE; -import static io.openems.edge.energy.optimizer.Utils.findFirstPeakIndex; -import static io.openems.edge.energy.optimizer.Utils.findFirstValleyIndex; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.ESS_CHARGE_C_RATE; +import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR; +import static io.openems.edge.energy.api.EnergyUtils.findFirstPeakIndex; +import static io.openems.edge.energy.api.EnergyUtils.findFirstValleyIndex; import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.Math.round; @@ -16,11 +16,12 @@ import com.google.common.primitives.ImmutableIntArray; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Simulator.Period; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; -public class ParamsUtils { +@Deprecated +public class ParamsUtilsV1 { - private ParamsUtils() { + private ParamsUtilsV1() { } /** diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Params.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsV1.java similarity index 94% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Params.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsV1.java index f49fe773eb9..92ce404372a 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/Params.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ParamsV1.java @@ -1,8 +1,8 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.openems.edge.energy.optimizer.ParamsUtils.calculateChargeEnergyInChargeGrid; -import static io.openems.edge.energy.optimizer.ParamsUtils.calculatePeriodLengthHourFromIndex; +import static io.openems.edge.energy.v1.optimizer.ParamsUtilsV1.calculateChargeEnergyInChargeGrid; +import static io.openems.edge.energy.v1.optimizer.ParamsUtilsV1.calculatePeriodLengthHourFromIndex; import static java.lang.Math.min; import java.time.ZonedDateTime; @@ -16,7 +16,8 @@ import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -public record Params(// +@Deprecated +public record ParamsV1(// /** Start-Timestamp of the Schedule */ ZonedDateTime time, /** ESS Total Energy (Capacity) [Wh] */ @@ -206,15 +207,15 @@ private ImmutableList generatePeriods() { return result.build(); } - public Params build() { - return new Params(this.time, this.essTotalEnergy, this.essMinSocEnergy, this.essMaxSocEnergy, + public ParamsV1 build() { + return new ParamsV1(this.time, this.essTotalEnergy, this.essMinSocEnergy, this.essMaxSocEnergy, this.essInitialEnergy, this.states, // this.existingSchedule, this.generatePeriods()); } } protected static Builder create() { - return new Params.Builder(); + return new ParamsV1.Builder(); } @Override diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ScheduleDatas.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ScheduleDatas.java similarity index 94% rename from io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ScheduleDatas.java rename to io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ScheduleDatas.java index 053dac39d13..ed0f95c1600 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/optimizer/ScheduleDatas.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/ScheduleDatas.java @@ -1,4 +1,4 @@ -package io.openems.edge.energy.optimizer; +package io.openems.edge.energy.v1.optimizer; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; @@ -7,13 +7,13 @@ import static io.openems.common.utils.JsonUtils.getAsDouble; import static io.openems.common.utils.JsonUtils.getAsInt; import static io.openems.common.utils.JsonUtils.toJson; -import static io.openems.edge.energy.optimizer.Utils.SUM_CONSUMPTION; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.SUM_PRODUCTION; import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_DISCHARGE_POWER; import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_SOC; import static io.openems.edge.energy.optimizer.Utils.SUM_GRID; -import static io.openems.edge.energy.optimizer.Utils.SUM_PRODUCTION; -import static io.openems.edge.energy.optimizer.Utils.toEnergy; -import static io.openems.edge.energy.optimizer.Utils.toPower; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.SUM_CONSUMPTION; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.toEnergy; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.toPower; import static java.lang.Double.parseDouble; import static java.lang.Integer.parseInt; import static java.lang.Math.round; @@ -43,24 +43,25 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.types.ChannelAddress; import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Params.Length; -import io.openems.edge.energy.optimizer.Simulator.Period; +import io.openems.edge.energy.v1.optimizer.ParamsV1.Length; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; /** * Data for JSONRPC-Response. Values are in [W]. */ +@Deprecated public record ScheduleDatas(int essTotalEnergy, ImmutableList entries) { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); /** - * Creates {@link ScheduleDatas} from an {@link Optimizer}. + * Creates {@link ScheduleDatas} from an {@link OptimizerV1}. * - * @param optimizer the {@link Optimizer} + * @param optimizer the {@link OptimizerV1} * @return a {@link ScheduleDatas}a * @throws OpenemsException on error */ - public static ScheduleDatas fromSchedule(Optimizer optimizer) throws OpenemsException { + public static ScheduleDatas fromSchedule(OptimizerV1 optimizer) throws OpenemsException { final var schedule = optimizer.getSchedule(); if (schedule == null) { throw new OpenemsException("Has no Schedule"); diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/SimulatorV1.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/SimulatorV1.java new file mode 100644 index 00000000000..52145544f2e --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/SimulatorV1.java @@ -0,0 +1,178 @@ +package io.openems.edge.energy.v1.optimizer; + +import static io.jenetics.engine.EvolutionResult.toBestGenotype; +import static io.jenetics.engine.Limits.byExecutionTime; +import static io.openems.edge.energy.v1.optimizer.InitialPopulationV1Utils.buildInitialPopulation; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.paramsAreValid; +import static io.openems.edge.energy.v1.optimizer.UtilsV1.postprocessSimulatorState; +import static java.lang.Math.max; +import static java.time.Duration.ofSeconds; + +import java.time.ZonedDateTime; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; + +import io.jenetics.Genotype; +import io.jenetics.IntegerChromosome; +import io.jenetics.IntegerGene; +import io.jenetics.engine.Engine; +import io.jenetics.engine.EvolutionResult; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.energy.v1.optimizer.ParamsV1.Length; +import io.openems.edge.energy.v1.optimizer.ParamsV1.OptimizePeriod; + +@Deprecated +public class SimulatorV1 { + + /** Used to incorporate charge/discharge efficiency. */ + public static final double EFFICIENCY_FACTOR = 1.17; + + public record Period(OptimizePeriod op, StateMachine state, int essInitial, EnergyFlowV1 ef) { + } + + /** + * Simulates a Schedule and calculates the cost. + * + * @param p the {@link ParamsV1} + * @param schedule the {@link StateMachine} states of the Schedule + * @return the cost, lower is better; always positive + */ + protected static double calculateCost(ParamsV1 p, StateMachine[] schedule) { + final var nextEssInitial = new AtomicInteger(p.essInitialEnergy()); + var sum = 0.; + for (var i = 0; i < p.optimizePeriods().size(); i++) { + sum += simulatePeriod(p, p.optimizePeriods().get(i), schedule[i], nextEssInitial, null); + } + return sum; + } + + /** + * Simulates a Schedule in quarterly periods. + * + * @param p the {@link ParamsV1} + * @param schedule the {@link StateMachine} states of the Schedule + * @return a Map of {@link Period}s + */ + protected static ImmutableSortedMap simulate(ParamsV1 p, StateMachine[] schedule) { + final var nextEssInitial = new AtomicInteger(p.essInitialEnergy()); + var result = ImmutableSortedMap.naturalOrder(); + for (var i = 0; i < p.optimizePeriods().size(); i++) { + var state = schedule[i]; + var op = p.optimizePeriods().get(i); + var length = op.quarterPeriods().size() == 1 ? Length.QUARTER : Length.HOUR; + // Convert mixed OptimizePeriods to pure quarterly + for (var qp : op.quarterPeriods()) { + var quarterlyOp = new OptimizePeriod(qp.time(), length, qp.essMaxChargeEnergy(), + qp.essMaxDischargeEnergy(), qp.essChargeInChargeGrid(), qp.maxBuyFromGrid(), qp.production(), + qp.consumption(), qp.price(), ImmutableList.of(qp)); + simulatePeriod(p, quarterlyOp, state, nextEssInitial, period -> result.put(period.op().time(), period)); + } + } + return result.build(); + } + + /** + * Calculates the cost of one Period under the given Schedule. + * + * @param p the {@link ParamsV1} + * @param op the current {@link OptimizePeriod} + * @param state the {@link StateMachine} of the current period + * @param nextEssInitial the initial SoC-Energy; also used as return value + * @param collect a {@link Consumer} to collect the simulation results if + * required. We are not always collecting results to + * reduce workload during simulation. + * @return the cost, lower is better; always positive + */ + protected static double simulatePeriod(ParamsV1 p, OptimizePeriod op, StateMachine state, + final AtomicInteger nextEssInitial, Consumer collect) { + // Constants + final var essInitial = max(0, nextEssInitial.get()); // always at least '0' + + // Calculate Energy-Flow + final var ef = switch (state) { + case BALANCING -> EnergyFlowV1.withBalancing(p, op, essInitial); + case DELAY_DISCHARGE -> EnergyFlowV1.withDelayDischarge(p, op, essInitial); + case CHARGE_GRID -> EnergyFlowV1.withChargeGrid(p, op, essInitial); + }; + + nextEssInitial.set(essInitial - ef.ess()); + + // Calculate Cost + double cost; + if (ef.grid() > 0) { + // Filter negative prices + var price = max(0, op.price()); + + cost = // Cost for direct Consumption + ef.gridToConsumption() * price + // Cost for future Consumption after storage + + ef.gridToEss() * price * EFFICIENCY_FACTOR; + + } else { + // Sell-to-Grid + cost = 0.; + } + if (collect != null) { + var postprocessedState = postprocessSimulatorState(state, // + EnergyFlowV1.withBalancing(p, op, essInitial), // + EnergyFlowV1.withDelayDischarge(p, op, essInitial), // + EnergyFlowV1.withChargeGrid(p, op, essInitial)); + collect.accept(new Period(op, postprocessedState, essInitial, ef)); + } + return cost; + } + + /** + * Runs the optimization with default settings. + * + * @param p the {@link ParamsV1} + * @param executionLimitSeconds limit.byExecutionTime.ofSeconds + * @return the best schedule + */ + protected static StateMachine[] getBestSchedule(ParamsV1 p, long executionLimitSeconds) { + return getBestSchedule(p, executionLimitSeconds, null, null); + } + + protected static StateMachine[] getBestSchedule(ParamsV1 p, long executionLimitSeconds, Integer populationSize, + Integer limit) { + // Return pure BALANCING Schedule if no predictions are available + if (!paramsAreValid(p)) { + return p.optimizePeriods().stream() // + .map(op -> StateMachine.BALANCING) // + .toArray(StateMachine[]::new); + } + + var gtf = Genotype.of(IntegerChromosome.of(IntegerGene.of(0, p.states().length)), p.optimizePeriods().size()); // + var eval = (Function, Double>) (gt) -> { + var modes = new StateMachine[p.optimizePeriods().size()]; + for (var i = 0; i < modes.length; i++) { + modes[i] = p.states()[gt.get(i).get(0).intValue()]; + } + return calculateCost(p, modes); + }; + var engine = Engine // + .builder(eval, gtf) // + .executor(Runnable::run) // current thread + .minimizing(); + if (populationSize != null) { + engine.populationSize(populationSize); // + } + Stream> stream = engine.build() // + .stream(buildInitialPopulation(p)) // + .limit(byExecutionTime(ofSeconds(executionLimitSeconds))); // + if (limit != null) { + stream = stream.limit(limit); // apply optional limit + } + var bestGt = stream // + .collect(toBestGenotype()); + return IntStream.range(0, p.optimizePeriods().size()) // + .mapToObj(period -> p.states()[bestGt.get(period).get(0).intValue()]) // + .toArray(StateMachine[]::new); + } +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/UtilsV1.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/UtilsV1.java new file mode 100644 index 00000000000..d43f90fcff6 --- /dev/null +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/UtilsV1.java @@ -0,0 +1,382 @@ +package io.openems.edge.energy.v1.optimizer; + +import static io.openems.common.utils.DateUtils.roundDownToQuarter; +import static io.openems.edge.common.type.TypeUtils.multiply; +import static io.openems.edge.common.type.TypeUtils.orElse; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; +import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; +import static io.openems.edge.controller.ess.timeofusetariff.Utils.ESS_MAX_SOC; +import static io.openems.edge.controller.ess.timeofusetariff.v1.UtilsV1.getEssMinSocPercentage; +import static io.openems.edge.energy.api.EnergyConstants.PERIODS_PER_HOUR; +import static io.openems.edge.energy.api.EnergyUtils.interpolateArray; +import static io.openems.edge.energy.optimizer.Utils.ESS_LIMIT_14A_ENWG; +import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_DISCHARGE_POWER; +import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_SOC; +import static io.openems.edge.energy.optimizer.Utils.SUM_GRID; +import static java.lang.Math.max; +import static java.lang.Math.round; +import static java.util.Arrays.stream; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; +import java.util.stream.IntStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Streams; + +import io.openems.common.exceptions.InvalidValueException; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.edge.controller.ess.emergencycapacityreserve.statemachine.Context; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffController; +import io.openems.edge.controller.ess.timeofusetariff.v1.EnergyScheduleHandlerV1.ContextV1; +import io.openems.edge.energy.v1.jsonrpc.GetScheduleResponse; +import io.openems.edge.energy.v1.optimizer.ScheduleDatas.ScheduleData; +import io.openems.edge.energy.v1.optimizer.SimulatorV1.Period; +import io.openems.edge.ess.api.SymmetricEss; +import io.openems.edge.timedata.api.Timedata; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; + +/** + * Utils for {@link TimeOfUseTariffController}. + * + *

    + * All energy values are in [Wh] and positive, unless stated differently. + */ +@Deprecated +public final class UtilsV1 { + + private UtilsV1() { + } + + public static final ChannelAddress SUM_PRODUCTION = new ChannelAddress("_sum", "ProductionActivePower"); + public static final ChannelAddress SUM_CONSUMPTION = new ChannelAddress("_sum", "ConsumptionActivePower"); + public static final ChannelAddress SUM_UNMANAGED_CONSUMPTION = new ChannelAddress("_sum", + "UnmanagedConsumptionActivePower"); + + private static final Logger LOG = LoggerFactory.getLogger(UtilsV1.class); + + /** + * Create {@link ParamsV1} for {@link SimulatorV1}. + * + * @param globalContext the {@link GlobalContextV1} object + * @param existingSchedule the existing schedule, i.e. result of previous + * optimization + * @return {@link ParamsV1} + * @throws InvalidValueException on error + */ + public static ParamsV1 createSimulatorParams(GlobalContextV1 globalContext, + ImmutableSortedMap existingSchedule) throws InvalidValueException { + final var time = roundDownToQuarter(ZonedDateTime.now()); + + // Prediction values + final var predictionConsumption = joinConsumptionPredictions(4, // + globalContext.predictorManager().getPrediction(SUM_CONSUMPTION).asArray(), // + globalContext.predictorManager().getPrediction(SUM_UNMANAGED_CONSUMPTION).asArray()); + final var predictionProduction = generateProductionPrediction(// + globalContext.predictorManager().getPrediction(SUM_PRODUCTION).asArray(), // + predictionConsumption.length); + + // Prices contains the price values and the time it is retrieved. + final var prices = globalContext.timeOfUseTariff().getPrices(); + + // Ess information. + var context = globalContext.energyScheduleHandler().getContext(); + final var essTotalEnergy = context.ess().getCapacity().getOrError(); + final var essMinSocEnergy = getEssMinSocEnergy(context, essTotalEnergy); + final var essMaxSocEnergy = round(ESS_MAX_SOC / 100F * essTotalEnergy); + final var essSoc = context.ess().getSoc().getOrError(); + final var essSocEnergy = essTotalEnergy /* [Wh] */ / 100 * essSoc; + + // Power Values for scheduling battery for individual periods. + var maxDischargePower = globalContext.sum().getEssMaxDischargePower().orElse(1000 /* at least 1000 */); + var maxChargePower = globalContext.sum().getEssMaxDischargePower().orElse(-1000 /* at least 1000 */); + if (context.limitChargePowerFor14aEnWG()) { + maxChargePower = max(ESS_LIMIT_14A_ENWG, maxChargePower); // Apply §14a EnWG limit + } + + return ParamsV1.create() // + .setTime(time) // + .setEssTotalEnergy(essTotalEnergy) // + .setEssMinSocEnergy(essMinSocEnergy) // + .setEssMaxSocEnergy(essMaxSocEnergy) // + .setEssInitialEnergy(essSocEnergy) // + .setEssMaxChargeEnergy(toEnergy(Math.abs(maxChargePower))) // + .setEssMaxDischargeEnergy(toEnergy(maxDischargePower)) // + .seMaxBuyFromGrid(toEnergy(context.maxChargePowerFromGrid())) // + .setProductions(stream(interpolateArray(predictionProduction)).map(v -> toEnergy(v)).toArray()) // + .setConsumptions(stream(interpolateArray(predictionConsumption)).map(v -> toEnergy(v)).toArray()) // + .setPrices(interpolateDoubleArray(prices.asArray())) // + .setStates(context.controlMode().states) // + .setExistingSchedule(existingSchedule) // + .build(); + } + + /** + * Postprocesses production prediction; makes sure length is at least the same + * as consumption prediction - filling up with zeroes. + * + * @param prediction the production prediction + * @param minLength the min length (= consumption prediction length) + * @return new production prediction + */ + protected static Integer[] generateProductionPrediction(Integer[] prediction, int minLength) { + if (prediction.length >= minLength) { + return prediction; + } + return IntStream.range(0, minLength) // + .mapToObj(i -> i > prediction.length - 1 ? 0 : prediction[i]) // + .toArray(Integer[]::new); + } + + protected static Integer[] joinConsumptionPredictions(int splitAfterIndex, Integer[] totalConsumption, + Integer[] unmanagedConsumption) { + return Streams.concat(// + stream(totalConsumption) // + .limit(splitAfterIndex), // + stream(unmanagedConsumption) // + .skip(splitAfterIndex)) // + .toArray(Integer[]::new); + } + + protected static boolean paramsAreValid(ParamsV1 p) { + if (p.optimizePeriods().isEmpty()) { + // No periods are available + LOG.warn("No periods are available"); + return false; + } + if (p.optimizePeriods().stream() // + .allMatch(pp -> pp.production() == 0 && pp.consumption() == 0)) { + // Production and Consumption predictions are all zero + LOG.warn("Production and Consumption predictions are all zero"); + return false; + } + if (p.optimizePeriods().stream() // + .mapToDouble(ParamsV1.OptimizePeriod::price) // + .distinct() // + .count() <= 1) { + // Prices are all the same + LOG.info("Prices are all the same"); + return false; + } + + return true; + } + + /** + * Returns the amount of energy that is not available for scheduling because of + * a configured minimum SoC. + * + * @param context the {@link Context} + * @param essCapacity net {@link SymmetricEss.ChannelId#CAPACITY} + * @return the value in [Wh] + */ + protected static int getEssMinSocEnergy(ContextV1 context, int essCapacity) { + return essCapacity /* [Wh] */ / 100 // + * getEssMinSocPercentage(// + context.ctrlLimitTotalDischarges(), // + context.ctrlEmergencyCapacityReserves()); + } + + /** + * Interpolate an Array of {@link Double}s. + * + *

    + * Replaces nulls with previous value. If first entry is null, it is set to + * first available value. If all values are null, all are set to 0. + * + * @param values the values + * @return values without nulls + */ + protected static double[] interpolateDoubleArray(Double[] values) { + var firstNonNull = stream(values) // + .filter(Objects::nonNull) // + .findFirst(); + var lastNonNullIndex = IntStream.range(0, values.length) // + .filter(i -> values[i] != null) // + .reduce((first, second) -> second); + if (lastNonNullIndex.isEmpty()) { + return new double[0]; + } + var result = new double[lastNonNullIndex.getAsInt() + 1]; + if (firstNonNull.isEmpty()) { + // all null + return result; + } + double last = firstNonNull.get(); + for (var i = 0; i < result.length; i++) { + double value = orElse(values[i], last); + result[i] = last = value; + } + return result; + } + + /** + * Utilizes the previous three hours' data and computes the next 21 hours data + * from the {@link OptimizerV1} provided, then concatenates them to generate a + * 24-hour {@link GetScheduleResponse}. + * + * @param optimizer the {@link OptimizerV1} + * @param requestId the JSON-RPC request-id + * @param timedata the{@link Timedata} + * @param timeOfUseTariff the {@link TimeOfUseTariff} + * @param componentId the Component-ID + * @param now the current {@link ZonedDateTime} (will get rounded + * down to 15 minutes) + * @return the {@link GetScheduleResponse} + */ + public static GetScheduleResponse handleGetScheduleRequest(OptimizerV1 optimizer, UUID requestId, Timedata timedata, + TimeOfUseTariff timeOfUseTariff, String componentId, ZonedDateTime now) { + final var b = ImmutableList.builder(); + now = roundDownToQuarter(now); + final var fromTime = now.minusHours(3); + + final var params = optimizer.getParams(); + if (params != null) { + // Process last three hours of historic data + final var channelQuarterlyPrices = new ChannelAddress(componentId, "QuarterlyPrices"); + final var channelStateMachine = new ChannelAddress(componentId, "StateMachine"); + try { + var queryResult = timedata.queryHistoricData(null, fromTime, now, // + Set.of(channelQuarterlyPrices, channelStateMachine, // + SUM_GRID, SUM_PRODUCTION, SUM_CONSUMPTION, SUM_ESS_DISCHARGE_POWER, SUM_ESS_SOC), + new Resolution(15, ChronoUnit.MINUTES)); + ScheduleData.fromHistoricDataQuery(// + params.essTotalEnergy(), channelQuarterlyPrices, channelStateMachine, queryResult) // + .forEach(b::add); + } catch (Exception e) { + LOG.warn("Unable to read historic data: " + e.getMessage()); + } + } + + // Process future schedule + final var schedule = optimizer.getSchedule(); + optimizer.getSchedule().values().stream() // + .flatMap(ScheduleData::fromPeriod) // + .forEach(b::add); + + // Find 'toTime' of result + final ZonedDateTime toTime; + if (!schedule.isEmpty()) { + toTime = schedule.lastKey(); + } else { + var pricesPerQuarter = timeOfUseTariff.getPrices().pricePerQuarter; + if (!pricesPerQuarter.isEmpty()) { + toTime = pricesPerQuarter.lastKey(); + } else { + toTime = fromTime; + } + } + + return new GetScheduleResponse(requestId, fromTime, toTime, + new ScheduleDatas(params.essTotalEnergy(), b.build())); + } + + /** + * Post-Process a state of a Period during Simulation, i.e. replace with + * 'better' state with the same behaviour. + * + *

    + * NOTE: heavy computation is ok here, because this method is called only at the + * end with the best Schedule. + * + * @param state the initial state + * @param efBalancing the {@link EnergyFlowV1} as it would be in + * {@link StateMachine#BALANCING} + * @param efDelayDischarge the {@link EnergyFlowV1} as it would be in + * {@link StateMachine#DELAY_DISCHARGE} + * @param efChargeGrid the {@link EnergyFlowV1} as it would be in + * {@link StateMachine#CHARGE_GRID} + * @return the new state + */ + public static StateMachine postprocessSimulatorState(StateMachine state, EnergyFlowV1 efBalancing, + EnergyFlowV1 efDelayDischarge, EnergyFlowV1 efChargeGrid) { + if (state == CHARGE_GRID) { + // CHARGE_GRID,... + if (efChargeGrid.ess() >= efDelayDischarge.ess()) { + // but battery charge/discharge is the same as DELAY_DISCHARGE + state = DELAY_DISCHARGE; + } + } + + if (state == DELAY_DISCHARGE) { + // DELAY_DISCHARGE,... + if (efDelayDischarge.ess() >= efBalancing.ess()) { + // but battery charge/discharge is the same as BALANCING + state = BALANCING; + } + } + + return state; + } + + /** + * Converts power [W] to energy [Wh/15 min]. + * + * @param power the power value + * @return the energy value + */ + public static int toEnergy(int power) { + return power / PERIODS_PER_HOUR; + } + + /** + * Converts energy [Wh/15 min] to power [W]. + * + * @param energy the energy value + * @return the power value + */ + public static Integer toPower(Integer energy) { + return multiply(energy, PERIODS_PER_HOUR); + } + + /** + * Prints the Schedule to System.out. + * + *

    + * NOTE: The output format is suitable as input for "RunOptimizerFromLogApp". + * This is useful to re-run a simulation. + * + * @param params the {@link ParamsV1} + * @param periods the map of {@link Period}s + */ + protected static void logSchedule(ParamsV1 params, ImmutableSortedMap periods) { + System.out.println("OPTIMIZER " + params.toLogString()); + System.out.println(ScheduleDatas.fromSchedule(params.essTotalEnergy(), periods).toLogString("OPTIMIZER ")); + } + + /** + * Updates the active Schedule with a new Schedule. + * + *

    + *

      + *
    • Period of the currently active Quarter is never changed + *
    • Old Periods are removed from the Schedule + *
    • Remaining Schedules are updated from new Schedule + *
    + * + * @param now the current {@link ZonedDateTime} + * @param schedule the active Schedule + * @param newSchedule the new Schedule + */ + public static void updateSchedule(ZonedDateTime now, TreeMap schedule, + ImmutableSortedMap newSchedule) { + var thisQuarter = roundDownToQuarter(now); + var current = schedule.get(thisQuarter); + schedule.clear(); + schedule.putAll(newSchedule); + if (current != null) { + schedule.put(thisQuarter, current); + } + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java index aa2a426c22c..c5598a34647 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/EnergySchedulerImplTest.java @@ -2,19 +2,21 @@ import static io.openems.common.utils.DateUtils.roundDownToQuarter; import static io.openems.edge.energy.LogVerbosity.TRACE; -import static io.openems.edge.energy.TestData.CONSUMPTION_PREDICTION_QUARTERLY; -import static io.openems.edge.energy.TestData.HOURLY_PRICES_SUMMER; -import static io.openems.edge.energy.TestData.PRODUCTION_PREDICTION_QUARTERLY; -import static io.openems.edge.energy.optimizer.Utils.SUM_CONSUMPTION; -import static io.openems.edge.energy.optimizer.Utils.SUM_PRODUCTION; +import static io.openems.edge.energy.api.EnergyConstants.SUM_PRODUCTION; +import static io.openems.edge.energy.api.EnergyConstants.SUM_UNMANAGED_CONSUMPTION; +import static io.openems.edge.energy.api.EnergyUtils.toEnergy; +import static io.openems.edge.energy.api.Version.V2_ENERGY_SCHEDULABLE; +import static io.openems.edge.energy.optimizer.TestData.CONSUMPTION_PREDICTION_QUARTERLY; +import static io.openems.edge.energy.optimizer.TestData.HOURLY_PRICES_SUMMER; +import static io.openems.edge.energy.optimizer.TestData.PRODUCTION_PREDICTION_QUARTERLY; +import static io.openems.edge.ess.power.api.Relationship.GREATER_OR_EQUALS; import static java.time.temporal.ChronoUnit.DAYS; import java.time.Clock; import java.time.Instant; +import java.time.LocalTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.List; -import java.util.function.Supplier; import org.junit.Test; @@ -24,12 +26,19 @@ import io.openems.edge.common.test.ComponentTest; import io.openems.edge.common.test.DummyComponentManager; import io.openems.edge.common.test.DummyConfigurationAdmin; +import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserveImpl; +import io.openems.edge.controller.ess.fixactivepower.ControllerEssFixActivePowerImpl; +import io.openems.edge.controller.ess.gridoptimizedcharge.ControllerEssGridOptimizedChargeImpl; +import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischargeImpl; +import io.openems.edge.controller.ess.timeofusetariff.ControlMode; import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; -import io.openems.edge.energy.optimizer.GlobalContext; +import io.openems.edge.energy.api.test.DummyEnergySchedulable; import io.openems.edge.energy.optimizer.Optimizer; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; import io.openems.edge.predictor.api.prediction.Prediction; import io.openems.edge.predictor.api.test.DummyPredictor; import io.openems.edge.predictor.api.test.DummyPredictorManager; +import io.openems.edge.scheduler.api.test.DummyScheduler; import io.openems.edge.timedata.test.DummyTimedata; import io.openems.edge.timeofusetariff.test.DummyTimeOfUseTariffProvider; @@ -50,30 +59,60 @@ public void test() throws Exception { * @throws Exception on error */ public static EnergySchedulerImpl create(Clock clock) throws Exception { - var now = roundDownToQuarter(ZonedDateTime.now(clock)); + final var now = roundDownToQuarter(ZonedDateTime.now(clock)); final var midnight = now.truncatedTo(DAYS); - var componentManager = new DummyComponentManager(clock); - var sum = new DummySum(); - var predictor0 = new DummyPredictor("predictor0", componentManager, + final var componentManager = new DummyComponentManager(clock); + final var sum = new DummySum() // + .withEssCapacity(10000) // + .withEssSoc(50); + final var ess = new DummyManagedSymmetricEss("ess0"); + final var predictor0 = new DummyPredictor("predictor0", componentManager, Prediction.from(sum, SUM_PRODUCTION, midnight, PRODUCTION_PREDICTION_QUARTERLY), SUM_PRODUCTION); - var predictor1 = new DummyPredictor("predictor0", componentManager, - Prediction.from(sum, SUM_CONSUMPTION, midnight, CONSUMPTION_PREDICTION_QUARTERLY), SUM_CONSUMPTION); - var timeOfUseTariff = DummyTimeOfUseTariffProvider.fromHourlyPrices(clock, HOURLY_PRICES_SUMMER); - var ctrl = new TimeOfUseTariffControllerImpl(); // this is not fully activated; config is null + final var predictor1 = new DummyPredictor("predictor1", componentManager, + Prediction.from(sum, SUM_UNMANAGED_CONSUMPTION, midnight, CONSUMPTION_PREDICTION_QUARTERLY), + SUM_UNMANAGED_CONSUMPTION); + final var timeOfUseTariff = DummyTimeOfUseTariffProvider.fromHourlyPrices(clock, HOURLY_PRICES_SUMMER); - var sut = new EnergySchedulerImpl(); + final var sut = new EnergySchedulerImpl(); new ComponentTest(sut) // .addReference("cm", new DummyConfigurationAdmin()) // .addReference("componentManager", componentManager) // .addReference("predictorManager", new DummyPredictorManager(predictor0, predictor1)) // .addReference("timedata", new DummyTimedata("timedata0")) // .addReference("timeOfUseTariff", timeOfUseTariff) // - .addReference("schedulables", List.of(ctrl)) // + .addReference("scheduler", new DummyScheduler("scheduler0")) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlEmergencyCapacityReserve0", + ControllerEssEmergencyCapacityReserveImpl.buildEnergyScheduleHandler(// + () -> /* reserveSoc */ 10))) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlLimitTotalDischarge0", + ControllerEssLimitTotalDischargeImpl.buildEnergyScheduleHandler(// + () -> /* minSoc */ 12))) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlFixActivePower0", + ControllerEssFixActivePowerImpl.buildEnergyScheduleHandler(// + () -> new ControllerEssFixActivePowerImpl.EshContext( + io.openems.edge.controller.ess.fixactivepower.Mode.MANUAL_ON, // + toEnergy(-1000), GREATER_OR_EQUALS)))) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlGridOptimizedCharge0", + ControllerEssGridOptimizedChargeImpl.buildEnergyScheduleHandler(// + () -> io.openems.edge.controller.ess.gridoptimizedcharge.Mode.MANUAL, // + () -> LocalTime.of(10, 00)))) // + .addReference("addSchedulable", + new DummyEnergySchedulable("ctrlEssTimeOfUseTariff0", + TimeOfUseTariffControllerImpl.buildEnergyScheduleHandler(// + () -> ess, // + () -> ControlMode.CHARGE_CONSUMPTION, // + () -> /* maxChargePowerFromGrid */ 20_000, // + () -> /* limitChargePowerFor14aEnWG */ false))) .addReference("sum", sum) // .activate(MyConfig.create() // - .setId("ctrl0") // + .setId("_energy") // .setEnabled(false) // .setLogVerbosity(TRACE) // + .setVersion(V2_ENERGY_SCHEDULABLE) // .build()) // .next(new TestCase()); return sut; @@ -92,31 +131,4 @@ public static Optimizer getOptimizer(EnergySchedulerImpl energyScheduler) throws return (Optimizer) field.get(energyScheduler); } - /** - * Calls the 'createParams()' method in the {@link Optimizer} via Java - * Reflection. - * - * @param optimizer the {@link Optimizer} - * @throws Exception on error - */ - public static void callCreateParams(Optimizer optimizer) throws Exception { - var method = Optimizer.class.getDeclaredMethod("createParams"); - method.setAccessible(true); - method.invoke(optimizer); - } - - /** - * Gets the {@link GlobalContext} via Java Reflection. - * - * @param energyScheduler the {@link EnergySchedulerImpl} - * @return the object - * @throws Exception on error - */ - @SuppressWarnings("unchecked") - public static GlobalContext getGlobalContext(EnergySchedulerImpl energyScheduler) throws Exception { - var optimizer = getOptimizer(energyScheduler); - var field = Optimizer.class.getDeclaredField("globalContext"); - field.setAccessible(true); - return ((Supplier) field.get(optimizer)).get(); - } } diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java b/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java index 1fbea17e506..e85ab323e37 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/MyConfig.java @@ -1,14 +1,16 @@ package io.openems.edge.energy; import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.energy.api.Version; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { - protected static class Builder { + public static class Builder { private String id; private boolean enabled; private LogVerbosity logVerbosity; + private Version version; private Builder() { } @@ -28,6 +30,11 @@ public Builder setLogVerbosity(LogVerbosity logVerbosity) { return this; } + public Builder setVersion(Version version) { + this.version = version; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -58,4 +65,9 @@ public boolean enabled() { public LogVerbosity logVerbosity() { return this.builder.logVerbosity; } + + @Override + public Version version() { + return this.builder.version; + } } \ No newline at end of file diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/api/simulation/EnergyFlowTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/api/simulation/EnergyFlowTest.java new file mode 100644 index 00000000000..b5eb2f38034 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/api/simulation/EnergyFlowTest.java @@ -0,0 +1,468 @@ +package io.openems.edge.energy.api.simulation; + +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyBalancing; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyChargeGrid; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyDelayDischarge; +import static io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl.applyDischargeGrid; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class EnergyFlowTest { + + /* + * BALANCING + */ + + @Test + public void testBalancingAndCharge() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(-2000, ef.getEss()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(0, ef.getGrid()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndChargeFull() { + var m = new EnergyFlow.Model(// + /* production */ 3000, // + /* consumption */ 100, // + /* essMaxCharge */ 2400, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(100, ef.getCons()); + assertEquals(100, ef.getProdToCons()); + + assertEquals(3000, ef.getProd()); + assertEquals(100, ef.getProdToCons()); + assertEquals(2400, ef.getProdToEss()); + assertEquals(500, ef.getProdToGrid()); + + assertEquals(-2400, ef.getEss()); + assertEquals(2400, ef.getProdToEss()); + + assertEquals(-500, ef.getGrid()); + assertEquals(500, ef.getProdToGrid()); + + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndDischarge() { + var m = new EnergyFlow.Model(// + /* production */ 500, // + /* consumption */ 2500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(2500, ef.getCons()); + assertEquals(2000, ef.getEssToCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2000, ef.getEss()); + assertEquals(2000, ef.getEssToCons()); + + assertEquals(0, ef.getGrid()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndDischargeEmpty() { + var m = new EnergyFlow.Model(// + /* production */ 500, // + /* consumption */ 4500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 1800, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(4500, ef.getCons()); + assertEquals(2200, ef.getGridToCons()); + assertEquals(1800, ef.getEssToCons()); + + assertEquals(500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(1800, ef.getEss()); + assertEquals(1800, ef.getEssToCons()); + + assertEquals(2200, ef.getGrid()); + assertEquals(2200, ef.getGridToCons()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndChargeMoreThanEssMaxEnergy() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 900, // + /* essMaxDischarge */ 900, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + assertEquals(900, ef.getProdToEss()); + assertEquals(1100, ef.getProdToGrid()); + + assertEquals(-900, ef.getEss()); + assertEquals(900, ef.getProdToEss()); + + assertEquals(-1100, ef.getGrid()); + assertEquals(1100, ef.getProdToGrid()); + + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndDischargeAboveEssMaxEnergy() { + var m = new EnergyFlow.Model(// + /* production */ 500, // + /* consumption */ 2500, // + /* essMaxCharge */ 900, // + /* essMaxDischarge */ 900, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(2500, ef.getCons()); + assertEquals(900, ef.getEssToCons()); + assertEquals(1100, ef.getGridToCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(900, ef.getEss()); + assertEquals(900, ef.getEssToCons()); + + assertEquals(1100, ef.getGrid()); + assertEquals(1100, ef.getGridToCons()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testBalancingAndAboveGridMaxEnergy() { + var m = new EnergyFlow.Model(// + /* production */ 1000, // + /* consumption */ 4900, // + /* essMaxCharge */ 1600, // + /* essMaxDischarge */ 2000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + var ef = m.solve(); + + assertEquals(4900, ef.getCons()); + assertEquals(2000, ef.getEssToCons()); + assertEquals(1900, ef.getGridToCons()); + assertEquals(1000, ef.getProdToCons()); + + assertEquals(1000, ef.getProd()); + assertEquals(1000, ef.getProdToCons()); + + assertEquals(2000, ef.getEss()); + assertEquals(2000, ef.getEssToCons()); + + assertEquals(1900, ef.getGrid()); + assertEquals(1900, ef.getGridToCons()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getGridToEss()); + } + + /* + * DELAY DISCHARGE + */ + + @Test + public void testDelayDischargeAndCharge() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyDelayDischarge(m); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(2000, ef.getProdToEss()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(-2000, ef.getEss()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(0, ef.getGrid()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testDelayDischargeAndChargeFull() { + var m = new EnergyFlow.Model(// + /* production */ 3000, // + /* consumption */ 100, // + /* essMaxCharge */ 2400, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyDelayDischarge(m); + var ef = m.solve(); + + assertEquals(100, ef.getCons()); + assertEquals(100, ef.getProdToCons()); + + assertEquals(3000, ef.getProd()); + assertEquals(100, ef.getProdToCons()); + assertEquals(500, ef.getProdToGrid()); + assertEquals(2400, ef.getProdToEss()); + + assertEquals(-2400, ef.getEss()); + assertEquals(2400, ef.getProdToEss()); + + assertEquals(-500, ef.getGrid()); + assertEquals(500, ef.getProdToGrid()); + + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + @Test + public void testDelayDischargeAndWouldDischarge() { + var m = new EnergyFlow.Model(// + /* production */ 500, // + /* consumption */ 2500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyDelayDischarge(m); + var ef = m.solve(); + + assertEquals(2500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + assertEquals(2000, ef.getGridToCons()); + + assertEquals(500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2000, ef.getGrid()); + assertEquals(2000, ef.getGridToCons()); + + assertEquals(0, ef.getEss()); + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToEss()); + } + + /* + * CHARGE GRID + */ + + @Test + public void testChargeGridAndCharge() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyChargeGrid(m, 2500); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(2000, ef.getProdToEss()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(-4500, ef.getEss()); + assertEquals(2500, ef.getGridToEss()); + assertEquals(2000, ef.getProdToEss()); + + assertEquals(2500, ef.getGrid()); + assertEquals(2500, ef.getGridToEss()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + } + + @Test + public void testChargeGridAndChargeFull() { + var m = new EnergyFlow.Model(// + /* production */ 3000, // + /* consumption */ 100, // + /* essMaxCharge */ 3400, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyChargeGrid(m, 2500); + var ef = m.solve(); + + assertEquals(100, ef.getCons()); + assertEquals(100, ef.getProdToCons()); + + assertEquals(3000, ef.getProd()); + assertEquals(100, ef.getProdToCons()); + assertEquals(2900, ef.getProdToEss()); + + assertEquals(-3400, ef.getEss()); + assertEquals(500, ef.getGridToEss()); + assertEquals(2900, ef.getProdToEss()); + + assertEquals(500, ef.getGrid()); + assertEquals(500, ef.getGridToEss()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getGridToCons()); + assertEquals(0, ef.getEssToCons()); + } + + @Test + public void testChargeGridAndAboveGridMaxEnergy() { + var m = new EnergyFlow.Model(// + /* production */ 1000, // + /* consumption */ 2000, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 1600, // + /* gridMaxSell */ 10000); + applyChargeGrid(m, 2500); + var ef = m.solve(); + + assertEquals(2000, ef.getCons()); + assertEquals(1000, ef.getProdToCons()); + assertEquals(1000, ef.getGridToCons()); + + assertEquals(1000, ef.getProd()); + assertEquals(1000, ef.getGridToCons()); + + assertEquals(-600, ef.getEss()); + assertEquals(600, ef.getGridToEss()); + + assertEquals(1600, ef.getGrid()); + assertEquals(1000, ef.getGridToCons()); + assertEquals(600, ef.getGridToEss()); + + assertEquals(0, ef.getProdToGrid()); + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getEssToCons()); + } + + /* + * DISCHARGE GRID - just for completeness + */ + + @Test + public void testDischargeGridAndCharge() { + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 5000, // + /* gridMaxBuy */ 1600, // + /* gridMaxSell */ 10000); + applyDischargeGrid(m, 2500); + var ef = m.solve(); + + assertEquals(500, ef.getCons()); + assertEquals(500, ef.getProdToCons()); + + assertEquals(2500, ef.getProd()); + assertEquals(500, ef.getProdToCons()); + assertEquals(2000, ef.getProdToGrid()); + + assertEquals(2500, ef.getEss()); + assertEquals(-2500, ef.getGridToEss()); + + assertEquals(-4500, ef.getGrid()); + assertEquals(2000, ef.getProdToGrid()); + assertEquals(-2500, ef.getGridToEss()); + + assertEquals(0, ef.getProdToEss()); + assertEquals(0, ef.getEssToCons()); + assertEquals(0, ef.getGridToCons()); + } + + @Test + public void testLog() { + // No actual test. Would have to mock Logger + var m = new EnergyFlow.Model(// + /* production */ 2500, // + /* consumption */ 500, // + /* essMaxCharge */ 5000, // + /* essMaxDischarge */ 0, // + /* gridMaxBuy */ 4000, // + /* gridMaxSell */ 10000); + applyBalancing(m); + m.logConstraints(); + m.logMinMaxValues(); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/OptimizerTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/OptimizerTest.java index 1ab693e4e03..6dc5d0c1c3a 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/OptimizerTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/OptimizerTest.java @@ -2,20 +2,74 @@ import static io.openems.edge.energy.EnergySchedulerImplTest.CLOCK; import static io.openems.edge.energy.EnergySchedulerImplTest.getOptimizer; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.function.ThrowingSupplier; +import io.openems.edge.controller.ess.timeofusetariff.StateMachine; import io.openems.edge.energy.EnergySchedulerImplTest; +import io.openems.edge.energy.LogVerbosity; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; public class OptimizerTest { @Test - public void testEmpty() throws Exception { - var sut = getOptimizer(EnergySchedulerImplTest.create(CLOCK)); - assertNull(sut.getParams()); - assertTrue(sut.getSchedule().isEmpty()); + public void test() throws Exception { + var sut = EnergySchedulerImplTest.create(CLOCK); + var optimizer = getOptimizer(sut); + assertEquals("No Schedule available|PerQuarter:UNDEFINED", optimizer.debugLog()); + + var gscSupplier = getGlobalSimulationContextSupplier(optimizer); + var simulator = new Simulator(gscSupplier.get()); + optimizer.runOnce(simulator); + + assertEquals("ScheduledPeriods:96|PerQuarter:UNDEFINED", optimizer.debugLog()); + + var sr = optimizer.getSimulationResult(); + assertEquals(1375977.5150000001, sr.cost(), 0.001); + assertEquals(96, sr.periods().size()); + + var ctrlEssTimeOfUseTariff0 = sr.schedules().entrySet().asList().get(0); + var p = ctrlEssTimeOfUseTariff0.getKey().getCurrentPeriod(); + assertEquals(StateMachine.CHARGE_GRID, p.state()); + } + + @Test + public void test2() { + var simulator = SimulatorTest.DUMMY_SIMULATOR; + var o = new Optimizer(// + () -> LogVerbosity.NONE, // + () -> simulator.gsc, // + null); + o.applyBestQuickSchedule(simulator); + + var schedule = ((EnergyScheduleHandler.WithDifferentStates) simulator.gsc.handlers().get(1)) + .getSchedule(); + + assertEquals(52, schedule.size()); + + assertTrue(schedule.values().stream() // + .allMatch(p -> p.state() == StateMachine.BALANCING)); + } + + /** + * Gets the {@link GlobalSimulationsContext} {@link ThrowingSupplier} via Java + * Reflection. + * + * @param optimizer the {@link Optimizer} + * @return the object + * @throws Exception on error + */ + @SuppressWarnings("unchecked") + public static ThrowingSupplier getGlobalSimulationContextSupplier( + Optimizer optimizer) throws Exception { + var field = Optimizer.class.getDeclaredField("gscSupplier"); + field.setAccessible(true); + return (ThrowingSupplier) field.get(optimizer); } } diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/QuickSchedulesTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/QuickSchedulesTest.java new file mode 100644 index 00000000000..d9ad2be088d --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/QuickSchedulesTest.java @@ -0,0 +1,65 @@ +package io.openems.edge.energy.optimizer; + +import static io.openems.edge.energy.optimizer.QuickSchedules.fromExistingSimulationResult; +import static io.openems.edge.energy.optimizer.QuickSchedules.variationsFromExistingSimulationResult; +import static io.openems.edge.energy.optimizer.QuickSchedules.variationsOfAllStatesDefault; +import static io.openems.edge.energy.optimizer.SimulatorTest.ESH_TIME_OF_USE_TARIFF_CTRL; +import static org.junit.Assert.assertEquals; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.EnergyScheduleHandler.WithDifferentStates.Period.Transition; + +public class QuickSchedulesTest { + + private static final ZonedDateTime TIME = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); + + @Test + public void testAllStatesDefault() { + final var simulator = SimulatorTest.DUMMY_SIMULATOR; + var gts = variationsOfAllStatesDefault(simulator.gsc).toList(); + assertEquals(6, gts.size()); + var gt = gts.get(0); + assertEquals(0, gt.get(0).get(1).allele().intValue()); + } + + @Test + public void testFromExistingSimulationResult() { + final var simulator = SimulatorTest.DUMMY_SIMULATOR; + final var previousResult = new SimulationResult(0., ImmutableMap.of(), // + ImmutableMap., ImmutableSortedMap>builder() // + .put(ESH_TIME_OF_USE_TARIFF_CTRL, ImmutableSortedMap.naturalOrder() // + .put(TIME.plusHours(0).plusMinutes(00), state(2)) // + .build()) // + .build()); + var gt = fromExistingSimulationResult(simulator.gsc, previousResult); + assertEquals(2, gt.get(0).get(0).allele().intValue()); + } + + @Test + public void testVariationsFromExistingSimulationResult() { + final var simulator = SimulatorTest.DUMMY_SIMULATOR; + final var previousResult = new SimulationResult(0., ImmutableMap.of(), // + ImmutableMap., ImmutableSortedMap>builder() // + .put(ESH_TIME_OF_USE_TARIFF_CTRL, ImmutableSortedMap.naturalOrder() // + .put(TIME.plusHours(0).plusMinutes(00), state(2)) // + .put(TIME.plusHours(0).plusMinutes(15), state(2)) // + .build()) // + .build()); + var gts = variationsFromExistingSimulationResult(simulator.gsc, previousResult).toList(); + assertEquals(6, gts.size()); + var gt = gts.get(0); + assertEquals(2, gt.get(0).get(1).allele().intValue()); + } + + protected static Transition state(int state) { + return new Transition(state, 0., null, 0); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulationResultTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulationResultTest.java new file mode 100644 index 00000000000..bb4f49f9cd3 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulationResultTest.java @@ -0,0 +1,56 @@ +package io.openems.edge.energy.optimizer; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.stream.IntStream; + +import org.junit.Test; + +import io.jenetics.Genotype; +import io.jenetics.IntegerChromosome; +import io.jenetics.IntegerGene; + +public class SimulationResultTest { + + @Test + public void test() { + final var simulator = SimulatorTest.DUMMY_SIMULATOR; + + var result = SimulationResult.fromQuarters(simulator.gsc, Genotype.of(// + // ESH1 (BALANCING, DELAY_DISCHARGE, CHARGE_GRID) + integerChromosomeOf(// + 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 1, 2), // + // ESH2 (FOO, BAR) + integerChromosomeOf(// + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 1, 0))); + + assertEquals(1_126_154.844, result.cost(), 0.001); + } + + /** + * Creates a {@link IntegerChromosome} from the given values. + * + * @param values the int values + * @return the {@link IntegerChromosome} + */ + public static IntegerChromosome integerChromosomeOf(int... values) { + if (values.length == 0) { + return IntegerChromosome.of(); + } + var max = IntStream.of(values).max().getAsInt(); + return IntegerChromosome.of(Arrays.stream(values) // + .mapToObj(value -> IntegerGene.of(value, 0, max)) // + .toList()); + } +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulatorTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulatorTest.java index 73cae4f79cb..06e1e98d859 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulatorTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/SimulatorTest.java @@ -1,40 +1,56 @@ package io.openems.edge.energy.optimizer; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.CHARGE_GRID; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.energy.TestData.CONSUMPTION_888_20231106; -import static io.openems.edge.energy.TestData.PRICES_888_20231106; -import static io.openems.edge.energy.TestData.PRODUCTION_888_20231106; -import static io.openems.edge.energy.optimizer.Simulator.getBestSchedule; -import static io.openems.edge.energy.optimizer.Simulator.simulate; -import static io.openems.edge.energy.optimizer.Utils.interpolateArray; -import static io.openems.edge.energy.optimizer.Utils.toEnergy; -import static java.util.Arrays.stream; -import static org.junit.Assert.assertArrayEquals; +import static io.openems.edge.energy.api.EnergyUtils.socToEnergy; import static org.junit.Assert.assertEquals; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Arrays; import java.util.Random; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.DoubleStream; import org.junit.Before; import org.junit.Test; -import com.google.common.collect.ImmutableSortedMap; - +import io.jenetics.engine.Limits; import io.jenetics.util.RandomRegistry; import io.openems.edge.controller.ess.timeofusetariff.ControlMode; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.energy.optimizer.Simulator.Period; +import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.test.DummyGlobalSimulationsContext; +import io.openems.edge.ess.api.ManagedSymmetricEss; +import io.openems.edge.ess.test.DummyManagedSymmetricEss; public class SimulatorTest { - public static final ZonedDateTime TIME = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); + public static final EnergyScheduleHandler.WithOnlyOneState ESH0 = EnergyScheduleHandler.of(// + simContext -> simContext.ess().totalEnergy(), // + (simContext, period, energyFlow, ctrlContext) -> { + var minEnergy = socToEnergy(simContext.global.ess().totalEnergy(), 10 /* [%] */); + energyFlow.setEssMaxDischarge(Math.max(0, simContext.getEssInitial() - minEnergy)); + }); + + public static final ManagedSymmetricEss ESS = new DummyManagedSymmetricEss("ess0") // + .withMaxApparentPower(10_000) // + .withAllowedChargePower(8_000) // + .withAllowedDischargePower(8_000) // + .withCapacity(22_000); + public static final EnergyScheduleHandler.WithDifferentStates ESH_TIME_OF_USE_TARIFF_CTRL = TimeOfUseTariffControllerImpl + .buildEnergyScheduleHandler(// + () -> ESS, // + () -> ControlMode.CHARGE_CONSUMPTION, // + () -> 20_000 /* maxChargePowerFromGrid */, // + () -> false /* limitChargePowerFor14aEnWG */); + + private static enum Esh2State { + FOO, BAR; + } + + public static final EnergyScheduleHandler.WithDifferentStates ESH2 = EnergyScheduleHandler.of(// + Esh2State.BAR, // + () -> Esh2State.values(), // + simContext -> null, // + (simContext, period, energyFlow, ctrlContext, state) -> { + }); + + public static final Simulator DUMMY_SIMULATOR = new Simulator(// + DummyGlobalSimulationsContext.fromHandlers(ESH0, ESH_TIME_OF_USE_TARIFF_CTRL, ESH2)); @Before public void before() { @@ -43,164 +59,32 @@ public void before() { RandomRegistry.random(new Random(123)); } - private static Period simulatePeriod(StateMachine state, int production, int consumption, double price, - int essInitial) { - var result = new AtomicReference(); - var params = Params.create() // - .setTime(TIME) // - .setEssTotalEnergy(22000) // - .setEssMinSocEnergy(0) // - .setEssMaxSocEnergy(20000) // - .setEssInitialEnergy(essInitial) // - .setEssMaxChargeEnergy(3000 /* [Wh/15 Minutes] */) // - .setEssMaxDischargeEnergy(3000 /* [Wh/15 Minutes] */) // - .seMaxBuyFromGrid(4000 /* [Wh/15 Minutes] */) // - .setProductions(new int[] { production }) // - .setConsumptions(new int[] { consumption }) // - .setPrices(new double[] { price }) // - .setStates(new StateMachine[] { state }) // - .setExistingSchedule(ImmutableSortedMap.of()) // - .build(); - Simulator.simulatePeriod(params, params.optimizePeriods().get(0), state, new AtomicInteger(essInitial), - result::set); - - return result.get(); - } - - private static void assertPeriod(String message, Period period, int essChargeDischarge, int grid, double cost) { - assertEquals(period.state() + "-essChargeDischarge: " + message, essChargeDischarge, period.ef().ess()); - assertEquals(period.state() + "-grid: " + message, grid, period.ef().grid()); - } - - @Test - public void testCalculatePeriodCostBalancing() { - assertPeriod("Consumption > Production; SoC ok", // - simulatePeriod(BALANCING, 200, 300, 0.1, 10000), // - 100, 0, 0); - assertPeriod("Consumption > Production; discharge limited by essMaxEnergyPerPeriod", // - simulatePeriod(BALANCING, 1000, 5000, 0.1, 10000), // - 3000, 1000, 100); - assertPeriod("Consumption > Production; discharge limited by essMinSocEnergy", // - simulatePeriod(BALANCING, 1000, 5000, 0.1, 2500), // - 2500, 1500, 150); - - assertPeriod("Production > Consumption; SoC ok", // - simulatePeriod(BALANCING, 300, 200, 0.1, 10000), // - -100, 0, 0); - assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // - simulatePeriod(BALANCING, 5000, 1000, 0.1, 10000), // - -3000, -1000, 0); - assertPeriod("Production > Consumption; charge limited by essTotalEnergy", // - simulatePeriod(BALANCING, 5000, 1000, 0.1, 19500), // - -2500, -1500, 0); - } - - @Test - public void testCalculatePeriodCostDelayDischarge() { - assertPeriod("Consumption > Production", // - simulatePeriod(DELAY_DISCHARGE, 200, 300, 0.1, 10000), // - 0, 100, 10); - - assertPeriod("Production > Consumption; SoC ok", // - simulatePeriod(DELAY_DISCHARGE, 300, 200, 0.1, 10000), // - -100, 0, 0); - assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // - simulatePeriod(DELAY_DISCHARGE, 5000, 1000, 0.1, 10000), // - -3000, -1000, 0); - assertPeriod("Production > Consumption; charge limited by essTotalEnergy", // - simulatePeriod(DELAY_DISCHARGE, 5000, 1000, 0.1, 19500), // - -2500, -1500, 0); - } - - @Test - public void testCalculatePeriodCostChargeGrid() { - assertPeriod("Consumption > Production", // - simulatePeriod(CHARGE_GRID, 200, 300, 0.1, 10000), // - -842, 942 /* 842 + 100 */, 302.5); - - assertPeriod("Consumption > Production; charge limited by maxBuyFromGrid", // - simulatePeriod(CHARGE_GRID, 0, 4500, 0.1, 10000), // - 500, 4000, 450.12); - - assertPeriod("Production > Consumption", // - simulatePeriod(CHARGE_GRID, 300, 200, 0.1, 10000), // - -2600 /* 2500 + 100 */, 2500, 292.5); - - assertPeriod("Production > Consumption; charge limited by essMaxEnergyPerPeriod", // - simulatePeriod(CHARGE_GRID, 3000, 900, 0.1, 10000), // - -3000, 900, 105.3); - - assertPeriod("Production > Consumption", // - simulatePeriod(CHARGE_GRID, 2000, 1700, 0.1, 10000), // - -2800, 2500, 292.5); - - assertPeriod("Production > Consumption; battery nearly full", // - simulatePeriod(CHARGE_GRID, 3000, 100, 0.1, 19600), // - -400 /* 400 from PV; then full */, -2500 /* sell-to-grid */, 292.5); - } - - @Test - public void testGetFirstSchedule0() { - var existingSchedule = new StateMachine[] { CHARGE_GRID, DELAY_DISCHARGE, CHARGE_GRID, BALANCING }; - - var p = Params.create() // - .setTime(TIME) // - .setEssTotalEnergy(22000) // - .setEssMinSocEnergy(0) // - .setEssMaxSocEnergy(22000) // - .setEssInitialEnergy((int) (22000 * 0.1)) // - .setEssMaxChargeEnergy(toEnergy(10000)) // - .setEssMaxDischargeEnergy(toEnergy(10000)) // - .seMaxBuyFromGrid(toEnergy(24_000)) // - .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setPrices(hourlyToQuarterly(interpolateArray(PRICES_888_20231106))) // - .setStates(ControlMode.CHARGE_CONSUMPTION.states) // - .setExistingSchedule(UtilsTest.prepareExistingSchedule(TIME, existingSchedule)) // - .build(); - var s = getBestSchedule(p, // - /* executionLimitSeconds */ 30, // - /* populationSize */ 2, // - /* limit */ 1); - - assertArrayEquals(existingSchedule, Arrays.copyOfRange(s, 0, existingSchedule.length)); - } - /** - * Creates dummy {@link Params}. + * Generates a dummy {@link SimulationResult}. * - * @param states the allowed states - * @return {@link Params} + * @return the {@link SimulationResult} */ - public static Params createParams888d20231106(StateMachine... states) { - return Params.create() // - .setTime(TIME) // - .setEssTotalEnergy(22000) // - .setEssMinSocEnergy(0) // - .setEssMaxSocEnergy(22000) // - .setEssMaxChargeEnergy(toEnergy(10000)) // - .setEssMaxDischargeEnergy(toEnergy(10000)) // - .seMaxBuyFromGrid(toEnergy(24_000)) // - .setProductions(stream(interpolateArray(PRODUCTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setConsumptions(stream(interpolateArray(CONSUMPTION_888_20231106)).map(v -> toEnergy(v)).toArray()) // - .setPrices(hourlyToQuarterly(interpolateArray(PRICES_888_20231106))) // - .setStates(states) // - .build(); + public static SimulationResult generateDummySimulationResult() { + final var simulator = DUMMY_SIMULATOR; + + return simulator.getBestSchedule(SimulationResult.EMPTY, // + engine -> engine // + .populationSize(1), // + stream -> stream // + .limit(Limits.byFixedGeneration(1))); } - protected static void logSchedule(Params p, StateMachine[] schedule) { - Utils.logSchedule(p, simulate(p, schedule)); - } + @Test + public void testGetBestSchedule() { + var simulationResult = generateDummySimulationResult(); - /** - * Convert hourly values to quarterly. - * - * @param values hourly values - * @return quarterly values - */ - protected static double[] hourlyToQuarterly(double[] values) { - return DoubleStream.of(values) // - .flatMap(v -> DoubleStream.of(v, v, v, v)) // - .toArray(); + assertEquals(2, simulationResult.schedules().size()); + + simulationResult.schedules().forEach((esh, schedule) -> { + esh.applySchedule(schedule); + }); + + assertEquals("BALANCING", ESH_TIME_OF_USE_TARIFF_CTRL.getCurrentPeriod().state().toString()); + assertEquals("BAR", ESH2.getCurrentPeriod().state().toString()); } } \ No newline at end of file diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/TestData.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/TestData.java new file mode 100644 index 00000000000..920ff079c13 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/TestData.java @@ -0,0 +1,67 @@ +package io.openems.edge.energy.optimizer; + +public class TestData { + + public static final Integer[] PRODUCTION_PREDICTION_QUARTERLY = { + /* 00:00-03:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 04:00-07:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 297, 610, // + /* 08:00-11:45 */ + 913, 1399, 1838, 2261, 2662, 3052, 3405, 3708, 4011, 4270, 4458, 4630, 4794, 4908, 4963, 4960, // + /* 12:00-15:45 */ + 4973, 4940, 4859, 4807, 4698, 4530, 4348, 4147, 1296, 1399, 1838, 1261, 1662, 1052, 1405, 1402, + /* 16:00-19:45 */ + 1662, 1052, 1405, 1630, 1285, 1520, 1250, 910, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 20:00-23:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 00:00-03:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 04:00-07:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 130, 402, 667, // + /* 08:00-11:45 */ + 1023, 1631, 2020, 2420, 2834, 3237, 3638, 4006, 4338, 4597, 4825, 4965, 5111, 5213, 5268, 5317, // + /* 12:00-15:45 */ + 5321, 5271, 5232, 5193, 5044, 4915, 4738, 4499, 3702, 3226, 3046, 2857, 2649, 2421, 2184, 1933, // + /* 16:00-19:45 */ + 1674, 1364, 1070, 754, 447, 193, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + /* 20:00-23:45 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // + }; + + public static final Integer[] CONSUMPTION_PREDICTION_QUARTERLY = { + /* 00:00-03:450 */ + 1021, 1208, 713, 931, 2847, 2551, 1558, 1234, 433, 633, 1355, 606, 430, 1432, 1121, 502, // + /* 04:00-07:45 */ + 294, 1048, 1194, 914, 1534, 1226, 1235, 977, 578, 1253, 1983, 1417, 513, 929, 1102, 445, // + /* 08:00-11:45 */ + 1208, 2791, 2729, 2609, 2086, 1454, 848, 816, 2610, 3150, 2036, 1180, 359, 1316, 3447, 2104, // + /* 12:00-15:45 */ + 905, 802, 828, 812, 863, 633, 293, 379, 1250, 2296, 2436, 2140, 2135, 1196, 2230, 1725, + /* 16:00-19:45 */ + 2365, 1758, 2325, 2264, 2181, 2167, 2228, 1082, 777, 417, 798, 1268, 409, 830, 1191, 417, // + /* 20:00-23:45 */ + 1087, 2958, 2946, 2235, 1343, 483, 796, 1201, 567, 395, 989, 1066, 370, 989, 1255, 660, // + /* 00:00-03:45 */ + 349, 880, 1186, 580, 327, 911, 1135, 553, 265, 938, 1165, 567, 278, 863, 1239, 658, // + /* 04:00-07:45 */ + 236, 816, 1173, 1131, 498, 550, 1344, 1226, 874, 504, 1733, 1809, 1576, 369, 771, 2583, // + /* 08:00-11:45 */ + 3202, 2174, 1878, 2132, 2109, 1895, 1565, 1477, 1613, 1716, 1867, 1726, 1700, 1787, 1755, 1734, // + /* 12:00-15:45 */ + 1380, 691, 338, 168, 199, 448, 662, 205, 183, 70, 169, 276, 149, 76, 195, 168, // + /* 16:00-19:45 */ + 159, 266, 135, 120, 224, 979, 2965, 1337, 1116, 795, 334, 390, 433, 369, 762, 2908, // + /* 20:00-23:45 */ + 3226, 2358, 1778, 1002, 455, 654, 534, 1587, 1638, 459, 330, 258, 368, 728, 1096, 878 // + }; + + public static final Double[] HOURLY_PRICES_SUMMER = { // + 70.95, 71.98, 71.95, 74.96, // + 78.93, 80., 84.01, 111.03, // + 105.04, 105., 74.23, 73.28, // + 67.97, 72.53, 89.66, 150.01, // + 173.54, 178.4, 158.91, 140.01, // + 149.99, 157.43, 130.9, 120.14 // + }; +} diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java index b9b321d8366..58daf14c3ca 100644 --- a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/UtilsTest.java @@ -1,336 +1,56 @@ package io.openems.edge.energy.optimizer; -import static io.openems.common.utils.DateUtils.roundDownToQuarter; -import static io.openems.edge.common.test.TestUtils.createDummyClock; -import static io.openems.edge.common.test.TestUtils.withValue; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.BALANCING; -import static io.openems.edge.controller.ess.timeofusetariff.StateMachine.DELAY_DISCHARGE; -import static io.openems.edge.energy.EnergySchedulerImplTest.getOptimizer; -import static io.openems.edge.energy.TestData.CONSUMPTION_PREDICTION_QUARTERLY; -import static io.openems.edge.energy.TestData.PAST_HOURLY_PRICES; -import static io.openems.edge.energy.TestData.PAST_SOC; -import static io.openems.edge.energy.TestData.PAST_STATES; -import static io.openems.edge.energy.TestData.PRODUCTION_888_20231106; -import static io.openems.edge.energy.TestData.PRODUCTION_PREDICTION_QUARTERLY; -import static io.openems.edge.energy.optimizer.EnergyFlowTest.NO_FLOW; -import static io.openems.edge.energy.optimizer.SimulatorTest.TIME; -import static io.openems.edge.energy.optimizer.Utils.SUM_CONSUMPTION; -import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_DISCHARGE_POWER; -import static io.openems.edge.energy.optimizer.Utils.SUM_ESS_SOC; -import static io.openems.edge.energy.optimizer.Utils.SUM_GRID; -import static io.openems.edge.energy.optimizer.Utils.SUM_PRODUCTION; import static io.openems.edge.energy.optimizer.Utils.calculateExecutionLimitSeconds; -import static io.openems.edge.energy.optimizer.Utils.findFirstPeakIndex; -import static io.openems.edge.energy.optimizer.Utils.findFirstValleyIndex; -import static io.openems.edge.energy.optimizer.Utils.generateProductionPrediction; -import static io.openems.edge.energy.optimizer.Utils.getEssMinSocEnergy; -import static io.openems.edge.energy.optimizer.Utils.interpolateArray; -import static io.openems.edge.energy.optimizer.Utils.joinConsumptionPredictions; -import static io.openems.edge.energy.optimizer.Utils.paramsAreValid; -import static io.openems.edge.energy.optimizer.Utils.toEnergy; -import static io.openems.edge.energy.optimizer.Utils.toPower; -import static io.openems.edge.energy.optimizer.Utils.updateSchedule; -import static java.time.temporal.ChronoUnit.MINUTES; -import static java.time.temporal.ChronoUnit.SECONDS; +import static io.openems.edge.energy.optimizer.Utils.sortByScheduler; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import java.time.Duration; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.TreeMap; -import java.util.stream.IntStream; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.stream.Stream; import org.junit.Test; -import com.google.common.collect.ImmutableSortedMap; - -import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.types.ChannelAddress; -import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.test.AbstractDummyOpenemsComponent; -import io.openems.edge.controller.ess.emergencycapacityreserve.ControllerEssEmergencyCapacityReserve; -import io.openems.edge.controller.ess.limittotaldischarge.ControllerEssLimitTotalDischarge; -import io.openems.edge.controller.ess.timeofusetariff.StateMachine; -import io.openems.edge.controller.ess.timeofusetariff.TimeOfUseTariffControllerImpl; -import io.openems.edge.energy.EnergySchedulerImplTest; -import io.openems.edge.energy.optimizer.Simulator.Period; -import io.openems.edge.timedata.test.DummyTimedata; +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.energy.api.EnergySchedulable; +import io.openems.edge.energy.api.test.DummyEnergySchedulable; +import io.openems.edge.scheduler.api.test.DummyScheduler; public class UtilsTest { - protected static ImmutableSortedMap prepareExistingSchedule(ZonedDateTime fromDate, - StateMachine... existingSchedule) { - return IntStream.range(0, existingSchedule.length) // - .mapToObj(Integer::valueOf) // - .collect(ImmutableSortedMap.toImmutableSortedMap( - ZonedDateTime::compareTo, // - i -> fromDate.plusMinutes(i * 15), // - i -> existingSchedule[i])); - } - - @Test - public void testInterpolateArrayFloat() { - assertArrayEquals(new double[] { 123, 123, 234, 234, 345 }, // - interpolateArray(new Double[] { null, 123., 234., null, 345., null }), // - 0.0001F); - - assertArrayEquals(new double[] {}, // - interpolateArray(new Double[] { null }), // - 0.0001F); - } - - @Test - public void testInterpolateArrayInteger() { - assertArrayEquals(new int[] { 123, 123, 234, 234, 345 }, // - interpolateArray(new Integer[] { null, 123, 234, null, 345, null })); - - assertArrayEquals(new int[] {}, // - interpolateArray(new Integer[] { null })); - - assertArrayEquals(new int[] { 123, 123 }, // - interpolateArray(new Integer[] { null, 123 })); - - assertArrayEquals(new int[] { 123 }, // - interpolateArray(new Integer[] { 123, null })); - } - - @Test - public void testToPower() { - assertEquals(2000, (int) toPower(500)); - assertNull(toPower(null)); - } - - @Test - public void testGenerateProductionPrediction() { - final var arr = new Integer[] { 1, 2, 3 }; - assertArrayEquals(arr, generateProductionPrediction(arr, 2)); - assertArrayEquals(new Integer[] { 1, 2, 3, 0 }, generateProductionPrediction(arr, 4)); - } - - @Test - public void testJoinConsumptionPredictions() { - assertArrayEquals(// - new Integer[] { 1, 2, 3, 4, 55, 66, 77, 88, 99 }, // - joinConsumptionPredictions(4, // - new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, // - new Integer[] { 11, 22, 33, 44, 55, 66, 77, 88, 99 })); - } - - @Test - public void testFindFirstPeakIndex() { - assertEquals(0, findFirstPeakIndex(0, new double[0])); - assertEquals(0, findFirstPeakIndex(0, new double[] { 1 })); - assertEquals(0, findFirstPeakIndex(0, new double[] { 1, 0 })); - assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0 })); - assertEquals(1, findFirstPeakIndex(0, new double[] { 0, 1, 0, 1 })); - assertEquals(5, findFirstPeakIndex(5, new double[0])); - } - - @Test - public void testFindFirstValleyIndex() { - assertEquals(0, findFirstValleyIndex(0, new double[0])); - assertEquals(0, findFirstValleyIndex(0, new double[] { 1 })); - assertEquals(1, findFirstValleyIndex(0, new double[] { 1, 0 })); - assertEquals(0, findFirstValleyIndex(0, new double[] { 0, 1, 0 })); - assertEquals(2, findFirstValleyIndex(1, new double[] { 0, 1, 0, 1 })); - assertEquals(5, findFirstValleyIndex(5, new double[0])); - } - - @Test - public void testParamsAreValid() throws Exception { - var builder = Params.create() // - .setTime(TIME) // - .setEssInitialEnergy(0) // - .setEssTotalEnergy(22000) // - .setEssMinSocEnergy(2_000) // - .setEssMaxSocEnergy(20_000) // - .seMaxBuyFromGrid(toEnergy(24_000)) // - .seMaxBuyFromGrid(0) // - .setStates(new StateMachine[0]); - - // No periods are available - assertFalse(paramsAreValid(builder // - .setProductions() // - .setConsumptions() // - .setPrices() // - .build())); - - // Production and Consumption predictions are all zero - assertFalse(paramsAreValid(builder // - .setProductions(0, 0, 0) // - .setConsumptions(0, 0) // - .setPrices(123F) // - .build())); - - // Prices are all the same - assertFalse(paramsAreValid(builder // - .setProductions(0, 1, 3) // - .setConsumptions(0, 2) // - .setPrices(123F, 123F) // - .build())); - - // Finally got it right... - assertTrue(paramsAreValid(builder // - .setProductions(0, 1, 3) // - .setConsumptions(0, 2) // - .setPrices(123F, 124F) // - .build())); - assertEquals(2, builder.build().optimizePeriods().size()); - } - - private static class MyControllerEssLimitTotalDischarge - extends AbstractDummyOpenemsComponent - implements ControllerEssLimitTotalDischarge { - - protected MyControllerEssLimitTotalDischarge(Integer minSoc) { - super("ctrl0", // - OpenemsComponent.ChannelId.values(), // - ControllerEssLimitTotalDischarge.ChannelId.values() // - ); - withValue(this.getMinSocChannel(), minSoc); - } - - @Override - public void run() throws OpenemsNamedException { - } - - @Override - protected MyControllerEssLimitTotalDischarge self() { - return this; - } - } - - private static class MyControllerEssEmergencyCapacityReserve - extends AbstractDummyOpenemsComponent - implements ControllerEssEmergencyCapacityReserve { - - protected MyControllerEssEmergencyCapacityReserve(Integer reserveSoc) { - super("ctrl0", // - OpenemsComponent.ChannelId.values(), // - ControllerEssEmergencyCapacityReserve.ChannelId.values() // - ); - withValue(this.getActualReserveSocChannel(), reserveSoc); - } - - @Override - public void run() throws OpenemsNamedException { - } - - @Override - protected MyControllerEssEmergencyCapacityReserve self() { - return this; - } - } - - @Test - public void testGetEssMinSocEnergy() { - var t1 = new MyControllerEssLimitTotalDischarge(50); - var t2 = new MyControllerEssLimitTotalDischarge(null); - var t3 = new MyControllerEssEmergencyCapacityReserve(30); - assertEquals(5000, getEssMinSocEnergy( - new TimeOfUseTariffControllerImpl.Context(List.of(t3), List.of(t1, t2), null, null, 10000, false), - 10000)); - } - - @Test - public void testHandleScheduleRequest() throws Exception { - final var clock = createDummyClock(); - final var energyScheduler = EnergySchedulerImplTest.create(clock); - - // Simulate historic data - var now = roundDownToQuarter(ZonedDateTime.now(clock)); - final var fromDate = now.minusHours(3); - var timedata = new DummyTimedata("timedata0"); - for (var i = 0; i < 12; i++) { - var quarter = fromDate.plusMinutes(i * 15); - timedata.add(quarter, new ChannelAddress("ctrl0", "QuarterlyPrices"), PAST_HOURLY_PRICES[i]); - timedata.add(quarter, new ChannelAddress("ctrl0", "StateMachine"), PAST_STATES[i]); - timedata.add(quarter, SUM_PRODUCTION, PRODUCTION_PREDICTION_QUARTERLY[i]); - timedata.add(quarter, SUM_CONSUMPTION, CONSUMPTION_PREDICTION_QUARTERLY[i]); - timedata.add(quarter, SUM_ESS_SOC, PAST_SOC[i]); - timedata.add(quarter, SUM_ESS_DISCHARGE_POWER, PRODUCTION_888_20231106[i]); - timedata.add(quarter, SUM_GRID, PRODUCTION_888_20231106[i]); - } - - var optimizer = getOptimizer(energyScheduler); - System.out.println(optimizer); - // TODO this requires a fully configured TimeOfUseTariffControllerImpl - // callCreateParams(optimizer); - // - // // Testing only past data. For full data, optimizer has to be created as - // well. - // var result = handleGetScheduleRequest(optimizer, getNilUuid(), timedata, - // "ctrl0", clock.now()).getResult(); - // - // // JsonUtils.prettyPrint(result); - // - // var schedule = getAsJsonArray(result, "schedule"); - // assertEquals(11, schedule.size()); - // { - // var period = getAsJsonObject(schedule.get(0)); - // assertEquals(PAST_HOURLY_PRICES[0], getAsFloat(period, "price"), 0.00F); - // assertEquals(PRODUCTION_PREDICTION_QUARTERLY[0] / 4, getAsInt(period, - // "production")); - // } - } - @Test public void testCalculateExecutionLimitSeconds() { - final var clock = createDummyClock(); + final var clock = new TimeLeapClock(Instant.parse("2022-01-01T00:00:00.00Z"), ZoneOffset.UTC); assertEquals(Duration.ofMinutes(14).plusSeconds(30).toSeconds(), calculateExecutionLimitSeconds(clock)); - clock.leap(11, MINUTES); + clock.leap(11, ChronoUnit.MINUTES); assertEquals(Duration.ofMinutes(3).plusSeconds(30).toSeconds(), calculateExecutionLimitSeconds(clock)); - clock.leap(150, SECONDS); + clock.leap(150, ChronoUnit.SECONDS); assertEquals(60, calculateExecutionLimitSeconds(clock)); - clock.leap(1, SECONDS); + clock.leap(1, ChronoUnit.SECONDS); assertEquals(Duration.ofMinutes(15).plusSeconds(59).toSeconds(), calculateExecutionLimitSeconds(clock)); } @Test - public void testUpdateSchedule() { - final ZonedDateTime t = ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); - final Period pOld = new Period(null, DELAY_DISCHARGE, 0, NO_FLOW); - final Period pNew = new Period(null, BALANCING, 0, NO_FLOW); + public void testSortByScheduler() { + final var scheduler = new DummyScheduler("scheduler0") // + .setControllers("d", "f", null, "b"); + final var list = Stream.of("a", "b", "c", "d", "e") // + .map(id -> new DummyEnergySchedulable(id, null)) // + .toList(); - var schedule = new TreeMap(); - schedule.put(t.minusMinutes(15), pOld); // old entry is removed - schedule.put(t, pOld); // current entry stays - schedule.put(t.plusMinutes(15), pOld); // is overridden - schedule.put(t.plusMinutes(30), pOld); // is overridden - schedule.put(t.plusMinutes(45), pOld); // timestamp is missing in new Schedule -> remove + var result = sortByScheduler(scheduler, list).stream() // + .map(EnergySchedulable::id) // + .toArray(); - var newSchedule = ImmutableSortedMap.naturalOrder() // - .put(t, pNew) // - .put(t.plusMinutes(15), pNew) // - .put(t.plusMinutes(30), pNew) // - .build(); - - updateSchedule(t, schedule, newSchedule); - - // One old entry - assertEquals(1, schedule.values().stream().filter(v -> v == pOld).count()); - - // Two new entries - assertEquals(2, schedule.values().stream().filter(v -> v == pNew).count()); - - // No old entry - assertEquals(0, schedule.keySet().stream().filter(tz -> tz.isBefore(t)).count()); - - // Details - assertEquals(pOld, schedule.get(t)); - assertEquals(pNew, schedule.get(t.plusMinutes(15))); - assertEquals(pNew, schedule.get(t.plusMinutes(30))); - - // No current entry -> handle null - schedule.remove(t); - updateSchedule(t, schedule, newSchedule); + assertArrayEquals(// + new String[] { // + "d", "b", // by Scheduler + "a", "c", "e" // remaining alphabetically + }, result); } } diff --git a/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/AppUtils.java b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/AppUtils.java new file mode 100644 index 00000000000..cbfbbdd1508 --- /dev/null +++ b/io.openems.edge.energy/test/io/openems/edge/energy/optimizer/app/AppUtils.java @@ -0,0 +1,108 @@ +package io.openems.edge.energy.optimizer.app; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.lang.Double.parseDouble; +import static java.lang.Integer.parseInt; + +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.collect.ImmutableList; + +import io.openems.common.test.TimeLeapClock; +import io.openems.edge.energy.api.EnergyScheduleHandler; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext; +import io.openems.edge.energy.api.simulation.GlobalSimulationsContext.Period; +import io.openems.edge.energy.optimizer.SimulationResult; + +public class AppUtils { + + private AppUtils() { + } + + /** + * Creates {@link GlobalSimulationsContext} from the log output of + * {@link SimulationResult#toLogString()}. + * + * @param log the log output of {@link SimulationResult#toLogString()} + * @param eshs the {@link EnergyScheduleHandler}s + * @return a {@link GlobalSimulationsContext} + */ + public static GlobalSimulationsContext parseGlobalSimulationsContextFromLogString(String log, + ImmutableList eshs) throws IllegalArgumentException { + var headerMatcher = log.lines() // + .filter(l -> l.contains("OPTIMIZER ") && l.contains("GlobalSimulationsContext")) // + .map(l -> applyPattern(HEADER_PATTERN, l)) // + .findFirst().get(); + final var startDateTime = ZonedDateTime.parse(headerMatcher.group("startTime")); + final var clock = new TimeLeapClock(startDateTime.toInstant(), ZoneId.of("UTC")); + + var gridMatcher = applyPattern(GRID_PATTERN, headerMatcher.group("grid")); + final var grid = new GlobalSimulationsContext.Grid(// + parseInt(gridMatcher.group("maxBuy")), // + parseInt(gridMatcher.group("maxSell"))); + + var essMatcher = applyPattern(ESS_PATTERN, headerMatcher.group("ess")); + final var ess = new GlobalSimulationsContext.Ess(// + parseInt(essMatcher.group("currentEnergy")), // + parseInt(essMatcher.group("totalEnergy")), // + parseInt(essMatcher.group("maxChargeEnergy")), // + parseInt(essMatcher.group("maxDischargeEnergy"))); + + var nextTime = new AtomicReference<>(startDateTime); + var periods = log.lines() // + .filter(l -> l.contains("OPTIMIZER ") && !l.contains("GlobalSimulationsContext") && !l.contains("Time")) // + .map(l -> applyPattern(PERIOD_PATTERN, l)) // + .map(m -> { + var time = nextTime.get(); + if (!nextTime.get().toLocalTime().equals(LocalTime.parse(m.group("time"), HOURS_MINUTES))) { + throw new IllegalArgumentException("Times do not match: " + time); + } + nextTime.set(time.plusMinutes(15)); + return (Period) new Period.Quarter(time, // + parseInt(m.group("production")), // + parseInt(m.group("consumption")), // + parseDouble(m.group("price"))); + }) // + .collect(toImmutableList()); + + return new GlobalSimulationsContext(clock, startDateTime, eshs, grid, ess, periods); + } + + private static Matcher applyPattern(Pattern pattern, String line) { + var matcher = pattern.matcher(line); + if (!matcher.find()) { + throw new IllegalArgumentException("Pattern [" + pattern + "] does not match line [" + line + "]"); + } + return matcher; + } + + private static final DateTimeFormatter HOURS_MINUTES = DateTimeFormatter.ofPattern("HH:mm"); + + private static final Pattern HEADER_PATTERN = Pattern.compile("" // + + "startTime=(?\\S*), " // + + "Grid\\[(?.*)\\], " // + + "Ess\\[(?.*)\\]"); + + private static final Pattern GRID_PATTERN = Pattern.compile("" // + + "maxBuy=(?\\d+), " // + + "maxSell=(?\\d+)"); + + private static final Pattern ESS_PATTERN = Pattern.compile("" // + + "currentEnergy=(?\\d+), " // + + "totalEnergy=(?\\d+), " // + + "maxChargeEnergy=(?\\d+), " // + + "maxDischargeEnergy=(?\\d+)"); + + private static final Pattern PERIOD_PATTERN = Pattern.compile("" // + + "(?