From 13082f67de1752d307e0f98475b6fda221e13114 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:32:55 +0100 Subject: [PATCH 01/10] remove unneeded files, update dockerfile and install script --- CMakeLists.txt | 42 +----------------------- Dockerfile | 44 ++++++++++++++++++------- ecui-llserver.service | 15 --------- img/llserver.pdf | Bin 14486 -> 0 bytes img/llserver.png | Bin 30174 -> 0 bytes img/{stAscii.txt => logo_short.txt} | 0 img/txvAscii.txt | 49 ---------------------------- img/txvLogo.txt | 37 --------------------- img/txvLogoSquashed.txt | 29 ---------------- influxCsvExport/events.csv | 0 influxCsvExport/main | Bin 40208 -> 0 bytes install.sh | 23 +++++++------ scripts/plot.sh | 11 ------- scripts/pressure_v1.8.plt | 18 ---------- scripts/pressure_v1.9.plt | 16 --------- scripts/temp_v1.8.plt | 17 ---------- scripts/temp_v1.9.plt | 15 --------- scripts/thrust_v1.8.plt | 24 -------------- scripts/thrust_v1.9.plt | 22 ------------- src/LLController.cpp | 2 +- 20 files changed, 45 insertions(+), 319 deletions(-) delete mode 100644 ecui-llserver.service delete mode 100644 img/llserver.pdf delete mode 100644 img/llserver.png rename img/{stAscii.txt => logo_short.txt} (100%) delete mode 100644 img/txvAscii.txt delete mode 100644 img/txvLogo.txt delete mode 100644 img/txvLogoSquashed.txt delete mode 100644 influxCsvExport/events.csv delete mode 100755 influxCsvExport/main delete mode 100755 scripts/plot.sh delete mode 100644 scripts/pressure_v1.8.plt delete mode 100644 scripts/pressure_v1.9.plt delete mode 100644 scripts/temp_v1.8.plt delete mode 100644 scripts/temp_v1.9.plt delete mode 100644 scripts/thrust_v1.8.plt delete mode 100644 scripts/thrust_v1.9.plt diff --git a/CMakeLists.txt b/CMakeLists.txt index 813fb571..32ce4839 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,44 +47,4 @@ if(LINUX) endif() else() target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) -endif() - - -##----WITH BOOST -# cmake_minimum_required(VERSION 3.13) -# project(llserver_ecui_houbolt) - -# find_package(Threads REQUIRED) - -# set(CMAKE_CXX_STANDARD 17) -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") - -# #if(NOT TARGET spdlog) -# # # Stand-alone build -# # find_package(spdlog REQUIRED) -# #endif() - -# include_directories(include) - -# file(GLOB sources ./*.cpp src/*.cpp include/*.h) - -# set(Boost_USE_STATIC_LIBS OFF) -# set(Boost_USE_MULTITHREADED ON) -# set(Boost_USE_STATIC_RUNTIME OFF) -# find_package(Boost 1.45.0 COMPONENTS system) - -# if(Boost_FOUND) -# add_executable(${PROJECT_NAME} ${sources}) -# #spdlog_enable_warnings(${PROJECT_NAME}) - - -# if(UNIX AND NOT APPLE) -# set(LINUX TRUE) -# endif() - -# if(LINUX) -# target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads -lstdc++fs Boost::system)#spdlog::spdlog) -# else() -# target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads Boost::system)#spdlog::spdlog) -# endif() -# endif() +endif() \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8587575a..5c6ad145 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,37 @@ -FROM ubuntu:20.04 +# specify the node base image with your desired version node: +FROM ubuntu -# Replace shell with bash so we can source files -RUN rm /bin/sh && ln -s /bin/bash /bin/sh -# Set debconf to run non-interactively -RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections +WORKDIR /home -# Install base dependencies -RUN apt-get update && apt-get install -y -q --no-install-recommends \ - git \ - cmake \ - build-essential -WORKDIR /llserver_ecui_houbolt +### KVASER Driver -CMD ["bash"] +RUN apt-get update +RUN apt-get install -y build-essential +RUN apt-get install -y linux-headers-`uname -r` +RUN apt-get install -y cmake make +RUN apt-get install -y wget + +RUN wget --content-disposition "https://www.kvaser.com/downloads-kvaser/?utm_source=software&utm_ean=7330130980754&utm_status=latest" +RUN tar xvzf linuxcan.tar.gz +WORKDIR /home/linuxcan/canlib +RUN make +RUN make install +RUN /usr/doc/canlib/examples/listChannels + +WORKDIR /home/ + +# Clone the conf files into the docker container +ADD ./ /home/llserver_ecui_houbolt +WORKDIR /home/llserver_ecui_houbolt + +RUN mkdir -p build +WORKDIR /home/llserver_ecui_houbolt/build + +RUN cmake -D NO_PYTHON=true -S ../ -B ./ +RUN make -j + +ENV ECUI_CONFIG_PATH=/home/config_ecui + +ENTRYPOINT ./llserver_ecui_houbolt \ No newline at end of file diff --git a/ecui-llserver.service b/ecui-llserver.service deleted file mode 100644 index 7ea5b0d4..00000000 --- a/ecui-llserver.service +++ /dev/null @@ -1,15 +0,0 @@ -Description=Service for ECUI Low Level Server - -Wants=network.target -After=syslog.target network-online.target - -[Service] -Type=simple -ExecStart=/home/pi/llserver_ecui_houbolt/llserver_ecui_houbolt -WorkingDirectory=/home/pi/llserver_ecui_houbolt/ -Restart=always -RestartSec=10 -KillMode=process - -[Install] -WantedBy=multi-user.target diff --git a/img/llserver.pdf b/img/llserver.pdf deleted file mode 100644 index d05b87919948c739723250537ee798242112f1ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14486 zcmbumbzD?!*ES4-v~+`XN(?zP(h?HV4Z|>W4nucKsdR%VNF&|dEdtV=N=is0@1WOp z-9FF#eE0kQzGwcK^W1x_W34mxI@j92V>7DB$Z>FU3ScmH26P5?26YBwaMN(nIGWjD zh>8M~tj%2vc;S6@BY>u-69k|JvV^z*q#a!yU^Lv~;usJI^LtUgf2qh>+rc1EfSer& z29be)9nB$t=MV==m=%p6j{p~3#RUe1fb20mGWLyz9N2}Ri|0C4>zkzttvspiro;z8 zEPw^~88e~jIYM5K2lr3kcxRP2&$S8@WQ__HZQgOa@M;d|aFM?5qM4xlkbkp`LyzN! z{%v{v1b|Atad_o;6~9`1b-4L-(?2VFSadU-fNGd<>(r3S?{1S?UGwg|?e64sx$P## zfAVS%*1xF#Iom&X`R;Un`R?*``{~`ck8MBu991VwejcBGY`abKYxlSRJR1_M9MdB$ zF^hS-@bmL!*Ug6ERh#Hp1UTE6?D}=*hbS$_JI$q*8FZ0QB)wX8&-N%W^VX48+65mP zfgj7Pci+3NSlTF_*5tafElqMenskxX-!{EW>PczH)$ zIX!q4tfrUwaYCctAEV&~C}zaps2!M4{D{dDhoc7LX-0m*r+vrttONW3&mC|CnvzXF zrqW)wgG?-aYWu@am_@2$UuwehsZSCnU*tqH#>)?uP=sseN_7D(*u}#KNI+SkjK`$a zV#OnR%asgvtbD}0MCEc@dd$48bY_ekKT7)>a1rfN%asto)OFJq8=vNca_ToAx2t+f zVsUSDLK&Ziv=#KL_ZFNy->mMkA*RL9gubKklV_audss4Asdh30!3#!z$8r2#^IE!y zU)>^9n6^(=5UY{?s?!ud1}O3=hSCW|6oz2F0Gch1{u<jm+Q%TbDqM5ewy9Y~1K|30{@X69R?1QO3b z9eGPiXFIpK_mG?0B)6(cuTi^twh_$*tW}6hX4n^VjXj9yz-}?GX}}4Q-7~j)Fg2Vv zj3-CzVJ|XQj5RpNsp&Q%;fnUS+YraQd1|RTr@NZ4!k^P25)1K6@~Ydi@gX@~JIN76 z00uH%Si={wj8WmQL~IP2D}*hz%F1qSCSyX=(%v}xw^aM~OjQeqxN4IkavB9SixjmpCPD6saW;cKK+$Cqoyl>?Bk$w*8R#XYph)Gue^`h&cDo zyYtP@ljZE6yT=((rjhmgU2Ab4z5`vBu-F|;owp|+mkW+m=QXHpOAR^8Xnx2#@r?99(XT`W@~H)&ij!Wm@7wjBIX z>M5!+*X`1{{Me|7STM~i^bAi>osr_X@4o*KXOmd)TXTeV2{&{QaBq%4UUr!tgMK#I zc}SY^nlm$gzzWn$V_0!3N*>~3Z^W#_a5g4uktT~GH^v`d#})p4u;v-tC*4>>VyoA+ zZMWDxJ{>vb)(J|^2oi;;E9wHtYltjAJ}7D6;(^ca1ryMuU>NWG3h
#tl?g*2e(TPS3M1SQzL*^tDv5>%6Z%QgYF|=xG4INuWxtc|# zbI;nKw7Et6(g+V@Jj6?E8nYGg+|-R4v$CltH;MH^^O|SSEnLK>8fW>!pZFo!#8O+C zO$-&G$7sXO;pn-&>7w%?PzFE8_~oGhfq}zT@iuwCXVLi|RB^dXvR{*E97}t)zKJtw zs#F+QuBn;fUwn~wM;4ytV)9i{`hESAx@U>Y!PwF&xjf_)Nd4b1=vCo zmUEmiv%jf7S(#u8$Lm1^M}AnFq5zN(yjJsH|AG66ngT6?q8GzN*D3WtF<93w;N2t3 zx70&an-AZzXzr9YQ9wJ?n#NCUvZFbScz_w#sIUY3Y__Yug@&l~%qG%}t1-Td<`D}f z0{IMyuSpb38asonM*g8qZu@5BEkwSR?$ey%Qs)B?q2!{*nBPnEIAa+-E#phy>m=4_ zo}EbLH47sTF^)?mB=EJCT1j)*ml7l9*Q{nJ4)GD%5KQ|mXwSEI9YD)V55vAx)Ilef z1{eA^57Qh3KHB1l$n|zxT0Ac)bBr_gnphx94(&lF6e|vgkt(-;{vcg)MHI^>PXvAau@9H8eD=*7ZSL*pnlzQ^k`q*tX&YMJAbllA`DgCeFx@5N~!BdE3nMH zLvF>HeDu@<-`5DkTUD{Y%6uWpUQ;V5hS#q*AY6b+PVlWoS&55Tgf32L_xuRdNwhmK z%ajzr61-d0^O5=^ zbs7CV&z#zSWM0j8?&5R!rbu|6b_#YNwynsU&5Ut@ZCMfTU|Fnn zuxob8Y~87|_e=>+rCyf^<52KFy_e!^Bn?|chV};?9!~wqT~m9?aSOG}ToCG$ z{r)|1J_|E*H~&#(@78B~C@~o>9v*NRH7mukx1aSIHQt=ZfysR#|Snztm9{& zYkDG~L5CQ{6-H7KgRpQnMtlaH9q)1wFsc0<{kKUR`wn&|w)a}y{LQ@{Rmw+rGz#?mN4CTiG;c^ooASx#Dgk zn*y5q-ebZ0MC~d!S)rHM9XdNQPk6cz9ee9ESe7(4IZxXM6!jexa^%y@8dQPbF%T7_ zkX!Xv?8aTs@mXgT^(6y?I9w6V)FV=EZFqN%O$&q{M(HNuKdTWdGa@HaTSSdOBQN|vgb7jpZ<_qwe z`8?)1R}F^EY{SDE3D)RP0rwU2!$K&DN?6sax)pQ1&V!Kn7>$$gZ$p`B)bL|t((5q4 zvNgxpKNd0*4)C96yF?-6@0=6~DXdUE5N89Jh6AkXl9?qx7OdB%C}zqR7KaJAUj)G+ zEqiN9>L`3Vno|xEoc9G!vc2BSo>DD!D`IewFuhBD7a9Gbia)0{M}!2?;a%5)7GcZ7 zd^tPEk@8UyvsyLgJjJk@O~IHC`d|TE;VNLsg{m5(q>J(T#QoO(OmUc362H4Oyw=7RiAtn=+-rMQ(n$)pNJ|N zZ7kdOG+k_ex(JtZsr;;dLGr=FX<85%g;5szK`JvrUt7I4$9Pp&zHG`cjPe72-6$=a zYbe+|%H5eg!x`&)JT{f)%g&12K^Jv_!s+x7Rqdj7a>hIPSg_9t|Ig5kAeq|ai2*x@ zOlmo%M7x8YcWMH4gokU(KAg^GA=GcUviDT_nW1~jSf(yt zEU}!RFJJn|6=)}78LDgWdR$m0Z1O6yvRl0|&x53It>GpD6FhPH^0>3EUniye>#>P~ zQLLauA|iE2YUs1KDxrgO%rAThCdmls=sc_esZ^H*(aKWfd4Q_Zo=C)&{*U)pfZns;agUCH6{sWHKOy1eB1={i|Ua8}1>=Kmi=WKz~o@>1& z-7*iIMEL@<=0kk@&0zYPhKEG2Vn4qL=6arCFn%?b0Grq{>T?R%$U+G1A{(0vm=Y(@ z$IOJD@8{g5T3gfK?eiWln^D(M-(o!Ay&MB=w0=8_RXCZbcqdmo+Yg-2w%=+rFVV{R zBpVtj)KnzJ&9$hDk^ZGVGhI<^OPkFf(zm>r>I1(lQS5=2am8+*{Of~qM_&RbWM zlhrEidPC`Brcl(OPWZ?2GW?9-LHVmMFa4B@YdJPBU7u(&@C!`pwapqsr&~1n^4gaf z2Wr&$kW>|oqQ5$_EfIWfIx#|JDOsEYt%TWn^7r2$ab^N?ataAw% zny!S{SelYuTyP0rLP59IU4%&;ZU%KG;I{~a!IEQzusU3jY(fAc+q+ysE(YHdBAJMN zS-_h&L^IsiU&q$S;xu=NWDb(nj20FSB;R(pS_`GFt+LEJ0=*>y(O(~@;PgPaQ2nz9 z=RIWjpXDKU^j;U^nW{eNJc;E9FcZj?na^A2|GuMi>T3xs(QZ8-ZOEkx8v3~{tPS2-0;3(zUO9 z07;k@ZidJnA4biU9$6)Pu2_QMnNFW##S1;(^1JXoh^?459TD4B9_gshSi=<;BZ6#% zQGdW0esq_T;!B#`6;7Hw%H@-*G|3N>Zj`aS5II{(+Y&!`_dU;@qx{)2F#ids?{;`N z{#`B_=t@c_G#vobs(-8#>Q6yA@VdD3oH;9lx~Jns9JCyr5GSluV{~pt-I3ZZa!U;d zxM~_fWk;8Xn*M#u7Bzi_N|mXHpU*OOY9L=q2M4&Q-OR?ia+8A110tpfNq-wOo7h`5 zlN#y+-xwIVTVY;|G0Y549TOvYz)09@JZ#Sj*{ntHgNvOv7qfM+LRpS6GI*dn?$aF8 z=0Ut!zrTQ6)&)z%h2&km^yXj_yv-b@eB<;aW#- z&lWb+CEAb19VH_bv@@G0XkWK<6-P~y2^d8qw}JgJ)?(N3RBRa+4t#d{-fcL3Ut6** zyFoal;mR6)hSw9wy_&5K zhEX69a3ffZUyL|nqE?^@ofujK!TiZ_p!xSoCPK5HI(UYTGHQV~ops$r40UatMqI^TpS~s> z^3AqMIdBsLluUFP&x8E&)wrAn;B)0XFnUQ=I82}D>Hg$IG{g>VHk_J!w>g<|#HWek z-5O8j%cn>0-%s#hBR>ucx^BR5P|bDq>k^i7Zy~1kZfTy#)^xinTf#7JlQl>f){dIVo~oL>cLD=qvP>@6ejEz*2+}?L%BI6K*jV zkPJU@uL@kEQs6%Q9=W-bqg@_yO}D0(H8AK7P{!ammM3Lfl`K1B7Q-jrIXRQp^^17c z`IelFStF`j>WDANO3v2S$6eEA^?;#(NF~tvmHvxJy2B`)Oq#?jl@RJa+@LL= ztT8Qww4T}j?Dli~HWA;tC|GeeMk94drDS`VJw$~lz|K`U_B_b}IRHOBTBwv0gOm-@ z;}VCCf9}i)dBxR97m{{d*)<^$gK*-OLO{{({-HJ*i%&XU8U0{=M^i^Jf38jZi<$~W za;i11Q|h)FtC552*kl1sr{;`VdfM41*TaYs=cdLRpo7j~vyrcF-?{YDyM*z{6X!#B zhW(9%R43}E&KrCEX+B>wGGmLU`Ocjiyc`%-Zx+%Y-bzZVA(kL}yp4$O+1Po5NmvC5 zur17Elu;e#uLyO*BJE;;=>k7!)V+KWng)!a`$E2Ilp^$|Y%=-=glc@1vExjF{S?X9wZN1^Y7{oL_TYX6vH*!ulLreadu|voNBIbjch`gqJZ~ z`hpcZhhkQi!tE47vWOb#D4<+blZ(6|HGsz46fY&cJQf{z*k#Cnr;^qyvz*8i)4~W*)}kBl1GGFR>A) zv?k_TgStDV6~Zo%?(?@QR2M%~bLYygNx|7v78N%?``4eA^cKal7cb|1&iI;EUp@2k z)IZld3E{wY^H_VJ=Kh?_!iK+^THHXg`auDSCTHm0oY3m>{6zaFS#+BGhXE1ApYm2C zFv%z$JZ~qqer6v{zNH*?vbAt@F}^`h!+}JwdouT7$EA5i25((qym`ZV&H{T$ z*CJ|2neUVbWqZ`kq-ixksefG4er6s)L$1XSf+k$5!NA5AqURB~K31di%uq~(P z6?R$EfSF^+3-|z;cHF@xe2uifHzq-sVTw zOMp5o%ZnkwuKS{Japu!POw6wQu;0d_E^p~$m0LlnquW<%3`Uh$3Z!u~oWlz_m3>S- zGr1n0m_j3r%mmt-3z3GaG;pfG^j|-bBRwe!H6|#rSt!8hz|-D(w$E1hLNF*c>GGvk zvK$KHa)ohuYwvsGOR2{iqnH_<-i8f(77o4qsQ7cvRG9T)v;I@5^P%Apteszb#ELD| zCh>XTZ)v8npho&zpclnjgpdl^Z)eXN_w_;RoH01fO7ta`)ZFMmONBnZteZ_jb%l~L zzfXL+eNx4QJfZONgozDflYFOS*~(Ele#J$$vo<$%lY*NzG^9+8B@uiA z>*dZ)E2gCXLEP8O{iyGz8c0bmOLa6dEf{tN>o-PLMNMt6phwS=RMx;ni=D-CQEv(JY8aurJ^}D{sktU&U3*nf;pU zCP1^5l%IHq&Jv!+_vpF*{LFO0>ypqBueb#neT7gU7}X!!3H4|}+M#I(j+MFuYd9N+ zfc4_9rHp<;zbN;7JOe4Gbz#P__h|chU}}|p^zkS@134a*Ma=m5MpGDByY+nKn^Y)7 z8+0tNRfs(-fSTy%)>F=|^xcZs7LWMlxXc(B+*J2}99a*ycxJYava;#2_&7GOL;^d| zR=YV@ut&uxUyZee@Y9&TtJAA`T6)A2Lfd$VfX+ga?s<5UQ$2YZyM=Ve`!4&&ZW3rb zviO4x=CuWag(hM^M6EAQJtuezzXKh6oGN-0Tg+Me*=@n+Osy25L|vu3=`f?*1f#0~ z9#BH(oIJagrPXqx46%2n!1Wg%()

4;mhfSp^9CM2$&VbTc9>f&qQ)reR|eixyIu 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 diff --git a/img/llserver.png b/img/llserver.png deleted file mode 100644 index ffe6ce545ef3a1232e2897669e9c09b2efcd41f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30174 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 z 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 Date: Tue, 17 Jan 2023 13:36:28 +0100 Subject: [PATCH 02/10] update update script --- install.sh | 2 +- update.sh | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index d97b5d77..83b0dd56 100644 --- a/install.sh +++ b/install.sh @@ -13,4 +13,4 @@ sudo docker run \ -v /dev:/dev \ -v /lib/modules:/lib/modules \ -e "ECUI_CONFIG_PATH=/home/config_ecui" \ - -it --name llserver-ecui llserver_ecui + --rm -it --name llserver-ecui llserver_ecui diff --git a/update.sh b/update.sh index 887bbc5e..d525f4f7 100755 --- a/update.sh +++ b/update.sh @@ -4,8 +4,4 @@ cd "$(dirname "$0")" git pull -cmake . -DCMAKE_BUILD_TYPE:STRING=Debug -DNO_CANLIB:BOOL=True -DNO_PYTHON:BOOL=True - -make -j3 - -sudo systemctl restart ecui-llserver.service +exec ./install.sh From d02b85ea9138491375fadeec59b543d6c40e2d2e Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:47:53 +0100 Subject: [PATCH 03/10] add submodule init to dockerfile --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5c6ad145..0ffa6f66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,9 @@ WORKDIR /home/ ADD ./ /home/llserver_ecui_houbolt WORKDIR /home/llserver_ecui_houbolt +RUN git submodule init +RUN git submodule update + RUN mkdir -p build WORKDIR /home/llserver_ecui_houbolt/build From 00cf89457ffea0215bdcb799b427b5157687778e Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:54:35 +0100 Subject: [PATCH 04/10] add git to dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 0ffa6f66..2af58395 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ WORKDIR /home ### KVASER Driver RUN apt-get update +RUN apt-get install -y git RUN apt-get install -y build-essential RUN apt-get install -y linux-headers-`uname -r` RUN apt-get install -y cmake make From c912cadc76bd641c4426e793382451fada225daa Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sat, 21 Jan 2023 01:12:26 +0100 Subject: [PATCH 05/10] Wrote Documentation --- README.md | 656 ++++++++++++++++++++++++++++++++++++++++++++++++++++- install.sh | 3 + 2 files changed, 650 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e2b4ed3e..82f6c5e8 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,657 @@ -# Low Level Server for the Engine Control User Interface +

Low Level Server for the SpaceTeam Mission Control System

-## Links +

Table of Contents

-Documentation of whole ECUI and Setup Guide -### [TXV_ECUI_WEB](https://github.com/SpaceTeam/TXV_ECUI_WEB/tree/dev) +- [Overview](#overview) +- [Requirements](#requirements) +- [Installation](#installation) + - [Build options](#build-options) + - [Supported CAN Drivers](#supported-can-drivers) +- [CAN Protocol](#can-protocol) +- [The importance of States](#the-importance-of-states) +- [Events](#events) + - [State to CAN Command](#state-to-can-command) + - [State to State](#state-to-state) + - [Trigger Types](#trigger-types) +- [Configuration](#configuration) + - [config.json](#configjson) + - [mapping.json](#mappingjson) + - [CANMapping](#canmapping) + - [DefaultEventMapping](#defaulteventmapping) + - [EventMapping](#eventmapping) + - [GUIMapping](#guimapping) +- [Autonomous Control Test Sequence](#autonomous-control-test-sequence) + - [`globals` section](#globals-section) + - [`data` section](#data-section) + - [Abort Sequence Format](#abort-sequence-format) +- [TCP Socket Message Types](#tcp-socket-message-types) +- [UDP Socket Endpoint for LoRa](#udp-socket-endpoint-for-lora) + - [LoRa Config](#lora-config) +- [Troubleshooting](#troubleshooting) -Temperature Sensors over Ethernet with Siliconsystems TMP01 +## Overview -### [TXV_ECUI_TMPoE](https://github.com/SpaceTeam/TXV_ECUI_TMPoE/tree/master) +The SpaceTeam Mission Control System (STMC) Suite consists of multiple programms that +form a system for Monitoring and Remote Control Purposes. Historically it was developed +for Testing Rocket Engines and has then been further extended to be usable as a MissionControl +Interface. For further information follow [SpaceTeam Mission Control System](...) -![LLServer Diagram](img/llserver.png) +**The Low Level Server is responsible for handling and processing all time critical tasks** -# Notes: +This includes the implementation of +- our own [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) +- a possible UDP endpoint for using our CAN Protocol in an optimized way using + our custom LoRa shield with a custom LoRa Driver (will get public soon) +- the Database Interface (influxDB) +- the Control Sequence Logic +- the Interface to our own [Webserver](https://github.com/SpaceTeam/web_ecui_houbolt) + +The complexity of this program lies in the various configurable +options for initialization and during runtime. They are split into two files that +**must** must reside in the same directory: + +- config.json - the config file for socket endpoints, sampling rates, CAN bus params, etc. +- mapping.json + - [CANMapping](#canmapping) + - [DefaultEventMapping](#defaulteventmapping) + - [EventMapping](#eventmapping) + - [GUIMapping](#guimapping) + +In our setup these files can be found in [config_ecui](https://github.com/SpaceTeam/config_ecui), but this is not mandatory. +The config file directory path can be set either by passing it on start as an argument +or by setting the environment variable **ECUI_CONFIG_PATH**. The argument has +priority over the environment variable. + +## Requirements + +>Note: If you would like to use the llserver out of the box you also need to +install our webserver and a bunch of other tools. But don't worry, **we've written +an easy to setup guide in our [config_ecui](https://github.com/SpaceTeam/config_ecui) +repository.** Otherwise you first need to implement a tcp server with our communication +protocol in order to start receiving data. + +The simplest way to use the llserver is by setting it up inside a docker container +in that case docker engine needs to be preinstalled. + +First you need to install an influxdb (docker container recommended) + +## Installation + +To install the llserver using docker you can run +``` +sudo chmod +x install.sh +./install.sh +``` + +This script generates and mounts a config folder in the parent directory named +config_ecui where the [config.json](#configjson) and [mapping.json](#mappingjson) files must be present. + +### Build options +The project uses cmake for compiling. You can provide different build options to +cmake using the `-D` option: + +- `-D NO_PYTHON=[true | false]` +- `-D NO_CANLIB=[true | false]` + +Setting one of these build flags result in not binding the corresponding libraries +to avoid a compile error. If you would like to change one of these settings checkout +the Entrypoint line in the Dockerfile + +>NOTE: different build modes require different config variables, make sure you +have definded all of them properly. See [config_ecui](https://github.com/SpaceTeam/config_ecui). + +### Supported CAN Drivers +Currently two drivers are supported namely +- [Kvaser CANLIB](https://www.kvaser.com/canlib-webhelp/page_canlib.html) +- [SocketCAN](https://docs.kernel.org/networking/can.html) + +the preferred driver can be selected inside the [config.json](#configjson). +The Dockerfile also installs all required kvaser canlib library files automatically. + +## CAN Protocol +As many terms from the [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) +are also used to describe many functionalities +in the llserver, it is recommended to read through this documentation first before +you continue. + +The llserver keeps a hashmap of all commands that are supported of the connected +channels. These are used in the event mapping and control sequences to +send commands to the hardware. + +## The importance of States +The llserver manages a hashmap filled with states which is called the +**State Controller**. Basically every variable inside the system is represented +as a double value inside the state controller. This includes user inputs. + +Each entry consists of a + +- state name - as key +- value - as double +- timestamp - 0 when uninitialized else timestamp of the last change +- dirty flag - used for transmitting periodic status updates to the web server + +We follow a naming convention which mostly boils down to + +`prefix:channel_name:state` + +as the `prefix` it is often used `gui` as all user inputs are also tracked in the +state controller and logged to the database. In case if something goes wrong we can +analyze whether a wrong user input was made or another event caused the trouble. +**User inputs must be prefixed with `gui` in order to be processed correctly.** + +Despite our naming convetion of state names, one can input a state name +with as many `":"` dividers as desired. + +>WARNING: Although a `:` is used to make the state names more readable and +processable it shall be pointed out that this notation is not supported in +html. Hence all `:` are translated to `-` when received at the web client. + +## Events +Another key feature of the llserver is the event system. An event can be triggered +when a specific state changes its value. This is used for translating user inputs +to CAN commands that are transmitted over the CAN bus for example. But it is also +possible to trigger an event based on a sensor value or actuator state +of a specific hardware channel (since they +are all represented as states). For this behaviour to work there are two types of events + +### State to CAN Command + +``` +"gui:Flash:Clear": [ + { + "command": "ecu:RequestFlashClear", + "parameters": [] + }, + { + "command": "pmu:RequestFlashClear", + "parameters": [] + }, + { + "command": "rcu:RequestFlashClear", + "parameters": [] + } +] +``` +In this example the `gui:Flash:Clear` state indicates a button press on the user interface. When it is pressed, a request to clear the flash of each electronics board +(ecu, pmu, rcu) shall be transmitted. The `parameters` entry allows for additional +arguments that may be required depending on the specified command. When +a string is located inside the parameters list, the llserver tries to resolve it +as a state inside the state controller and use its value instead. This is especially +useful for analog user inputs. + +### State to State +``` +"ecu:FlashStatus": [ + { + "state": "gui:Flash:Clear", + "triggerType": "!=", + "triggerValue": 0, + "value": 0 + } +] +``` + +In bot cases it is possible to trigger multiple actions in one go as seen in the +example of `gui:Flash:Clear`. Also in both cases it is possible to specify a triggerType. + +### Trigger Types +Each event entry can include a `triggerType` with an additional `triggerValue`. +This is needed if the event shall only +be triggered when certain conditions are met, not everytime the state gets changed. +Possible trigger types are: + +- `==` --> triggers when the state value equals the `triggerValue` +- `!=` --> triggers when the state value not equals the `triggerValue` +- `>=` --> triggers when the state value is greater or equal than the `triggerValue` +- `<=` --> triggers when the state value is smaller or equal than the `triggerValue` +- `>` --> triggers when the state value greater than the `triggerValue` +- `<` --> triggers when the state value smaller than the `triggerValue` + +>NOTE: An event with a triggerType is only triggered when the previous value +was outside the trigger range! In this way the event only gets triggered when +the value changes from outside the trigger range to the inside of the trigger range which +means that if the value remains inside the trigger range over a long period the event +only gets triggered once. + +Example: +``` +"gui:fuel_main_valve:checkbox": [ + { + "command": "fuel_main_valve:SetTargetPosition", + "triggerType": "==", + "triggerValue": 0, + "parameters": [0] + }, + { + "command": "fuel_main_valve:SetTargetPosition", + "triggerType": "!=", + "triggerValue": 0, + "parameters": [65535] + } +] +``` +In this case the gui element for the fuel main valve is represented as a checkbox. +when the checkbox gets pressed, the fuel main valve opens completely. Otherwise +the valve closes completely. When multiple actions per state exist, the program +processes them step by step, as defined in the json array. **So be reminded that +a different order can cause different behaviours for more complex event mappings.** + +>NOTE: the fuel_main_valve gui button and hardware channel have no relation at the +beginning. Only an entry in the EventMapping links them together. + +## Configuration + +### config.json + +In the config.json file all variables are defined that are important for the +initialization process of the llserver. A complete example config file with +all possible entries can be viewed in the [config_ecui](https://github.com/SpaceTeam/config_ecui) repo. + +### mapping.json + +The mapping.json file consists of four different parts. Each one must be at least +declared as an empty object, i.e. + +``` +{ + "CANMapping": {}, + "DefaultEventMapping": {}, + "EventMapping": {}, + "GUIMapping": {} +} +``` +#### CANMapping + +``` +"CANMapping": { + "7": { + "0": { + "offset": 0, + "slope": 0.00010071, + "stringID": "pmu_5V_voltage" + }, + "1": { + "offset": 0, + "slope": 0.00010071, + "stringID": "pmu_5V_high_load_voltage" + }, + "2": { + "offset": 0, + "slope": 0.00033234, + "stringID": "pmu_12V_voltage" + } + "stringID": "pmu" + } + }, +``` +Since in a CAN Network multiple **Nodes** comunicate with each other using IDs, +we want to assign readable names to each node and each channel, so we +can work with them more easily. +A node entry starts with its unique identifier as a key and contains an object, +with arbitrary many channel entries. Each channel has a `:sensor` state that +can be scaled with the two entries `offset` and `slope`. The readable name +is defined with `stringID`. +A specification of the channel type is not needed since this information is loaded +when the nodes get initialized. + + +#### DefaultEventMapping + +The default event mapping can be used to define generic behaviour for gui elements +acting on hardware channels. +``` +"DefaultEventMapping": { + "DigitalOut": [ + { + "command": "DigitalOut:SetState", + "parameters": [ + "DigitalOut" + ] + } + ], + "Servo": [ + { + "command": "Servo:SetTargetPosition", + "parameters": [ + "Servo" + ] + } + ] +} +``` +The default behaviour for a gui element can be overwritten by an entry +in the event mapping. The key of an action may be an hardware channel type +which defines an action for each hardware channel of this type. The +channel type name can be also used to describe the command and parameters. +This gets then replaced with the actual channel name that is currently processed. + +#### EventMapping +``` +"EventMapping": { + "gui:Flash:Clear": [ + { + "command": "ecu:RequestFlashClear", + "parameters": [] + }, + { + "command": "pmu:RequestFlashClear", + "parameters": [] + }, + { + "command": "rcu:RequestFlashClear", + "parameters": [] + } + ] +} +``` +An entry in the event mapping prevents default behaviours defined in the +DefaultEventMapping from being triggered. For further information about +events go to the [Events](#events) section. +#### GUIMapping +``` +"GUIMapping": [ + { + "label": "Fuel Tank Pressure", + "state": "fuel_tank_pressure:sensor" + }, + { + "label": "Ox Tank Pressure", + "state": "ox_tank_pressure:sensor" + }, + { + "label": "Supercharge Valve", + "state": "supercharge_valve:sensor" + } +] +``` + +The GUI Mapping is a config option to tell the user interface, how a gui element +with a certain state shall be named in an even more human readable way as state names. +This information only gets transmitted to the user interface and has no implications +on the llserver. + +## Autonomous Control Test Sequence + +To be able to test a liquid engine it is normally required to execute multiple commands +in a very short amount of time (pressurization, ignition, engine ramp-up, etc.). + +For this purpose a sequence manager has been developed for automatic test sequences. + +Test Sequences are defined inside the `$ECUI_CONFIG_PATH/sequences/` folder. +Each sequence uses the JSON format and consists of two main objects: +- `globals` - general definitions used for the sequence processing +- `data` - the actual sequence + +### `globals` section +Inside the `globals` section following entries are needed + +| key | type | value | +| ----------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| `"startTime"` | float | relative start of the squence (may also be negative) | +| `"endTime"` | float | relative end of the sequence | +| `"interpolation"` | object | object with command name as key and either `"none"`(step function behaviour) or `"linear"`(linear interpolation) as values | +| `"interval"` | float | time precision of each iteration step | +| `"ranges"` | array | list of state names used for range checking | + +The interpolation entry specifies the behaviour between datapoints of the sequence. +The range entry is used to specify each state used for range checking. This means that at each datapoint a range can be +set in which the specified sensor must reside. If the sensor value gets out of range and **autoabort** is active inside the config.json +the sequence gets aborted instantly. + +### `data` section + +The data section includes a list of **command group**. These can be +used to group multiple datapoints relatively to one timestamp in order +to be moved more easily along the time axis if for example the burntime changes. + +Each group command includes + | key | type | value | + | ------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------- | + | `"timestamp"` | string or float | if timestamp is string only `"START"` or `"END"` are allowed which get replace by `"startTime"` and `"endTime"` respectively. | + | `"name"` | string | name of the command group | + | `"desc"` | string | description | + | `"actions"` | array | list of datapoints with relative timestamp to command group | + | | + + +Each datapoint may include + | key | type | value | + | ---------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------- | + | `"timestamp"` | float | timestamp relative to command group (here **only floats are valid!**) | + | `""` | array of floats | parameters for the specific command | + | `"sensorNominalRange"` | object | states as keys and array of two elements with range as values (must be defined in the global section `"ranges"`) | + + +The first datapoint of the first command group **HAS TO** include all commands used in the sequence for proper initialization! +Each number in actions except the timestamp is double on the LLServer. + +> Note: The keywords "START" or "END" are only allowed in the Group Commands (objects inside data array). +> +Example: +``` +{ + "globals": + { + "endTime": 15, + "interpolation": + { + "fuel_main_valve:SetTargetPosition": "linear", + "ox_main_valve:SetTargetPosition": "linear", + "igniter:SetState": "none" + }, + "interval": 0.01, + "ranges": + [ + "chamberPressure:sensor" + ], + "startTime": -10 + }, + "data": + [ + { + "timestamp": "START", + "name": "start", + "desc": "set all to initial state", + "actions": + [ + { + "timestamp": 0.0, + "fuel_main_valve:SetTargetPosition": [0], + "ox_main_valve:SetTargetPosition": [0], + "igniter:SetState": [0], + "sensorsNominalRange": + { + "chamberPressure:sensor": [-5, 20] + } + } + ] + }, + { + "timestamp": -3.0, + "name": "ignition", + "desc": "lets light this candle", + "actions": + [ + { + "timestamp": 0, + "igniter:SetState": [65000] + } + ] + }, + { + "timestamp": 0.0, + "name": "engine startup", + "desc": "ramp up engine", + "actions": + [ + { + "timestamp": 0, + "fuel_main_valve:SetTargetPosition": [0], + "ox_main_valve:SetTargetPosition": [0] + }, + { + "timestamp": 0.5, + "ox_main_valve:SetTargetPosition": [15000], + "fuel_main_valve:SetTargetPosition": [15000] + }, + { + "timestamp": 1.2, + "fuel_main_valve:SetTargetPosition": [25000], + "ox_main_valve:SetTargetPosition": [25000] + }, + { + "timestamp": 1.5, + "igniter:SetState": [0] + }, + { + "timestamp": 1.7, + "fuel_main_valve:SetTargetPosition": [65000], + "ox_main_valve:SetTargetPosition": [65000] + } + ] + }, + { + "timestamp": 10.0, + "name": "shutdown", + "desc": "engine shutdown", + "actions": + [ + { + "timestamp": 0, + "fuel_main_valve:SetTargetPosition": [65000], + "ox_main_valve:SetTargetPosition": [65000] + }, + { + "timestamp": 0.7, + "fuel_main_valve:SetTargetPosition": [0], + "ox_main_valve:SetTargetPosition": [0] + } + ] + }, + { + "timestamp": "END", + "name": "end", + "desc": "the end", + "actions": + [ + { + "timestamp": 0.0, + "fuel_main_valve:SetTargetPosition": [0], + "ox_main_valve:SetTargetPosition": [0] + } + ] + } + ] +} +``` + +### Abort Sequence Format + +Another feature is the ability to define a **"safe state"** to be +executed in order the test control sequence fails or gets aborted. + +Test Abort Sequence is defined inside the `$ECUI_CONFIG_PATH/sequences/abort_sequences/` folder. Currently only one abort sequence is supported and +must be named `AbortSequence.json`. + +For now exactly one object with all commands to be executed. +The `"endTime"` key in `"globals"` is used to describe for how long the logging should continue +in case of an abort. + + { + "globals": { + "endTime": 3.2 //double + }, + "actions" : { + "fuel_main_valve:SetTargetPosition": [0], //array[double] + "igniter:SetState": [0], //array[double] + "ox_main_valve:SetTargetPosition": [0] //array[double] + } + } + +## TCP Socket Message Types + +In order to communicate with the webserver, a tcp socket connection is +established. **It is mandatory to send the llserver specific messages +to initate state transmission and start test control sequences.** + +For the whole API documentation refer to [Webserver](https://github.com/SpaceTeam/web_ecui_houbolt) + +## UDP Socket Endpoint for LoRa + +This protocol is based on our [CAN Protocol](#can-protocol). + +This implementation uses LoRa as an unidirectional method for receiving +data from the rocket during flight. For receiving LoRa messages a custom +LoRa shield is used on a raspberry pi. (Repo coming soon) +A UDP socket connection is used for data transmission between llserver and +raspberry pi. + +After the internal control +of the rocket takes over and no further commands are sent to the rocket +the only message type that the rocket sends automatically and periodically +is the `DataMsg_t` of each Generic Channel. This means we can strip the LoRa +message down to only data messages and can even combine them into one large +message. This is done on our RCU (Radio Control Unit) which listens to the +CAN FD bus for any data messages and updates a buffer which is split into +as many regions as generic channels exists inside the rocket (number of CAN +nodes). + +Since the data message length is highly variable and considered to be +specified within the channel_mask, the header alone takes up an unnessesary +large amount of bytes that can be considered "constant" during +flight. Therefore the CAN header + data message header gets removed +for the LoRa messages +and is statically entered inside the [config.json](#configjson). + +There is only one additional byte for each generic channel which indicates if the +section contains valid data. **THIS BYTE IS NOT INCLUDED IN THE CONFIG canMsgSize ENTRY!** + +### LoRa Config + +The `LORA` section in the [config.json](#configjson) includes + +``` +"LORA": { + "ip": "192.168.1.7", + "port": 5001, + "nodeIDsRef": [6, 8], + "nodeIDs": [16, 18], + "canMsgSizes": [54, 39] +} +``` + +The `ip` and `port` describe the udp endpoint to the raspberry pi. + +The `nodeIDsRef` array defines the corresponding node ids on the CAN FD bus. This +is needed since the LoRa messages are injected in the CAN Manager as normal CAN messages +and therefore need to load the correct CAN header before the message is injected. This +means that the hardware **MUST** be connected over the CAN FD bus for the initialization +of the llserver in order to initialize the nodes correctly. Otherwise the LoRa messages cannot +be decoded and are lost! + +The `nodeIDs` array contains the node ids for the LoRa channels (in this case CAN FD bus node ids +prepended with 1) + +The `canMsgSizes` array contains the message size of each can data message (**HEADER BYTE EXCLUDED**) + + +>NOTE: that depending on the total message length, the LoRa driver on +the raspberrypi must be configured correctly as well. See (LoRa doc coming +soon...) + + +## Troubleshooting - Every Sequence needs to define each device at the "START" timestamp - **Make sure every "sensorsNominalRange" object in the sequence contains ALL sensors, with a range -specified** \ No newline at end of file +specified** + + +- if the llserver fails to connect to the web server or carshes instantly try +to check if the correct ip addresses are used in the config file with +`sudo docker network inspect bridge` + +- if either or both web and llserver refuse to start, try and check the docker environment variable for the correct config path + +- if the llserver crashes instantly check if influx is correctly installed \ No newline at end of file diff --git a/install.sh b/install.sh index 83b0dd56..33b3045b 100644 --- a/install.sh +++ b/install.sh @@ -2,6 +2,9 @@ cd "$(dirname "$0")" +CONFIG_DIR="../config_ecui" + +mkdir -p $CONFIG_DIR #llserver ecui sudo DOCKER_BUILDKIT=0 docker build \ -t llserver_ecui -f Dockerfile . From 099eb807c99d3e5bfa03f76535169a8a36718b32 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Mon, 23 Jan 2023 20:37:25 +0100 Subject: [PATCH 06/10] update dockerfile to install python as well --- CMakeLists.txt | 4 ++-- Dockerfile | 3 ++- src/drivers/PythonController.cpp | 10 ++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 32ce4839..1b4e22f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ if(NO_PYTHON) add_compile_definitions(NO_PYTHON) list(REMOVE_ITEM sources ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/PythonController.cpp) else() - include_directories(/usr/include/python3.8) + include_directories(/usr/include/python3.10) endif() add_executable(${PROJECT_NAME} ${sources}) @@ -43,7 +43,7 @@ if(LINUX) target_link_libraries(${PROJECT_NAME} PRIVATE -lcanlib) endif() if(NOT NO_PYTHON) - target_link_libraries(${PROJECT_NAME} PRIVATE -lpython3.8) + target_link_libraries(${PROJECT_NAME} PRIVATE -lpython3.10) endif() else() target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) diff --git a/Dockerfile b/Dockerfile index 2af58395..f268bc89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get install -y build-essential RUN apt-get install -y linux-headers-`uname -r` RUN apt-get install -y cmake make RUN apt-get install -y wget +RUN apt-get install -y python3.10-dev RUN wget --content-disposition "https://www.kvaser.com/downloads-kvaser/?utm_source=software&utm_ean=7330130980754&utm_status=latest" RUN tar xvzf linuxcan.tar.gz @@ -33,7 +34,7 @@ RUN git submodule update RUN mkdir -p build WORKDIR /home/llserver_ecui_houbolt/build -RUN cmake -D NO_PYTHON=true -S ../ -B ./ +RUN cmake -D NO_PYTHON=false -D NO_CANLIB=false -S ../ -B ./ RUN make -j ENV ECUI_CONFIG_PATH=/home/config_ecui diff --git a/src/drivers/PythonController.cpp b/src/drivers/PythonController.cpp index 011e0123..a2b6954a 100644 --- a/src/drivers/PythonController.cpp +++ b/src/drivers/PythonController.cpp @@ -8,9 +8,9 @@ #include #include -static std::string PythonController::pyEnv = ""; +std::string PythonController::pyEnv = ""; -static void PythonController::SetPythonEnvironment(std::string pyEnv) { +void PythonController::SetPythonEnvironment(std::string pyEnv) { PythonController::pyEnv = pyEnv; } @@ -230,7 +230,8 @@ void PythonController::RunPyScript(std::string scriptPath) { PythonController::SetupImports(); - FILE *fp = _Py_fopen(scriptPath.c_str(), "r"); + std::wstring path = std::wstring(scriptPath.begin(), scriptPath.end()); + FILE *fp = _Py_wfopen(path.c_str(), L"r"); StateController::Instance() -> SetState((std::string) "python_running", 1, utils::getCurrentTimestamp()); @@ -270,7 +271,8 @@ void PythonController::RunPyScriptWithArgvWChar(std::string scriptPath, int pyAr PySys_SetArgv(pyArgc, pyArgv); - FILE *fp = _Py_fopen(scriptPath.c_str(), "r"); + std::wstring path = std::wstring(scriptPath.begin(), scriptPath.end()); + FILE *fp = _Py_wfopen(path.c_str(), L"r"); StateController::Instance() -> SetState((std::string) "python_running", 1, utils::getCurrentTimestamp()); From ba2c306592756f29afb3045b2c5b8e9fd7c60fce Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 19 Mar 2023 17:47:45 +0100 Subject: [PATCH 07/10] change scaling for servos from 0 to 100 --- .vscode/launch.json | 4 ++-- src/can/Servo.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 41f8d155..1e900225 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,13 +3,13 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", - "configurations": [ + "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/llserver_ecui_houbolt", - "args": [], + "args": ["../config_ecui/"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/src/can/Servo.cpp b/src/can/Servo.cpp index c9d500fa..97b0e962 100644 --- a/src/can/Servo.cpp +++ b/src/can/Servo.cpp @@ -27,8 +27,8 @@ const std::vector Servo::states = const std::map> Servo::scalingMap = { - {"Position", {1.0, 0.0}}, - {"TargetPosition", {1.0, 0.0}}, + {"Position", {0.00152590219, 0.0}}, + {"TargetPosition", {0.00152590219, 0.0}}, {"TargetPressure", {1.0, 0.0}}, {"MaxSpeed", {1.0, 0.0}}, {"MaxAccel", {1.0, 0.0}}, From 85e4e6032d179deb24de22b94a1d356db7680007 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 30 Apr 2023 18:48:03 +0200 Subject: [PATCH 08/10] uBetter kvaser bus params output --- .vscode/launch.json | 8 ++++---- README.md | 10 +++++----- src/can/CANDriverKvaser.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1e900225..afa298fa 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,11 +8,11 @@ "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/llserver_ecui_houbolt", - "args": ["../config_ecui/"], + "program": "${workspaceFolder}/build/llserver_ecui_houbolt", + "args": [], "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], + "cwd": "${workspaceFolder}/build", + "environment": [{"name": "ECUI_CONFIG_PATH", "value": "../../config_ecui"}], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ diff --git a/README.md b/README.md index 82f6c5e8..ebfb0635 100644 --- a/README.md +++ b/README.md @@ -475,7 +475,7 @@ Example: [ { "timestamp": 0, - "igniter:SetState": [65000] + "igniter:SetState": [100] } ] }, @@ -506,8 +506,8 @@ Example: }, { "timestamp": 1.7, - "fuel_main_valve:SetTargetPosition": [65000], - "ox_main_valve:SetTargetPosition": [65000] + "fuel_main_valve:SetTargetPosition": [100], + "ox_main_valve:SetTargetPosition": [100] } ] }, @@ -519,8 +519,8 @@ Example: [ { "timestamp": 0, - "fuel_main_valve:SetTargetPosition": [65000], - "ox_main_valve:SetTargetPosition": [65000] + "fuel_main_valve:SetTargetPosition": [100], + "ox_main_valve:SetTargetPosition": [100] }, { "timestamp": 0.7, diff --git a/src/can/CANDriverKvaser.cpp b/src/can/CANDriverKvaser.cpp index 9a415be7..cb32feb7 100644 --- a/src/can/CANDriverKvaser.cpp +++ b/src/can/CANDriverKvaser.cpp @@ -274,7 +274,7 @@ canStatus CANDriverKvaser::InitializeCANChannel(uint32_t canBusChannelID) { return stat; } - Debug::print("can channel bus %d: arb-bitrate %d", canBusChannelID, arbitrationParamsMap[canBusChannelID].bitrate); + Debug::print("can channel bus %d: bitrate %d, arb-bitrate %d", canBusChannelID, dataParamsMap[canBusChannelID].bitrate, arbitrationParamsMap[canBusChannelID].bitrate); stat = canSetBusParams(canHandlesMap[canBusChannelID], arbitrationParamsMap[canBusChannelID].bitrate, arbitrationParamsMap[canBusChannelID].timeSegment1, From 4f91d96d18c6977f5545ad3bf8a76dd33c54973f Mon Sep 17 00:00:00 2001 From: markuspinter Date: Thu, 8 Aug 2024 18:46:17 +0200 Subject: [PATCH 09/10] feat(PI-control): Add PI pressure control channel --- .vscode/settings.json | 3 +- CMakeLists.txt | 2 +- include/can/Channel.h | 2 +- include/can/PIControl.h | 71 +++++++ include/can_houbolt | 2 +- src/SequenceManager.cpp | 3 +- src/can/Control.cpp | 119 ++++++----- src/can/Node.cpp | 4 + src/can/PIControl.cpp | 453 ++++++++++++++++++++++++++++++++++++++++ src/can/Servo.cpp | 192 +++++++++-------- 10 files changed, 689 insertions(+), 162 deletions(-) create mode 100644 include/can/PIControl.h create mode 100644 src/can/PIControl.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index f95ef421..1ba192f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -88,5 +88,6 @@ "numbers": "cpp", "semaphore": "cpp", "stop_token": "cpp" - } + }, + "C_Cpp.errorSquiggles": "disabled" } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b4e22f7..3eb78b41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,4 +47,4 @@ if(LINUX) endif() else() target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) -endif() \ No newline at end of file +endif() diff --git a/include/can/Channel.h b/include/can/Channel.h index f193c63b..98727748 100644 --- a/include/can/Channel.h +++ b/include/can/Channel.h @@ -56,7 +56,7 @@ class Channel double result = value; if (a != 1 || b != 0) { - result = value / a + b; + result = (value - b) / a; } return result; }; diff --git a/include/can/PIControl.h b/include/can/PIControl.h new file mode 100644 index 00000000..9a11f68e --- /dev/null +++ b/include/can/PIControl.h @@ -0,0 +1,71 @@ +// +// Created by Markus on 03.09.21. +// + +#ifndef LLSERVER_ECUI_HOUBOLT_PI_CONTROL_H +#define LLSERVER_ECUI_HOUBOLT_PI_CONTROL_H + +#include "can/Channel.h" +#include "can/Node.h" +#include "can_houbolt/channels/pi_control_channel_def.h" + +class PIControl : public Channel, public NonNodeChannel +{ +public: + //TODO: MP check if this is the only and correct way to implement static const with inheritation + static const std::vector states; + static const std::map> scalingMap; + static const std::map variableMap; + + //-------------------------------RECEIVE Functions-------------------------------// + +public: + PIControl(uint8_t channelID, std::string channelName, std::vector sensorScaling, Node *parent); + + std::vector GetStates() override; + + //-------------------------------RECEIVE Functions-------------------------------// + + void ProcessCANCommand(Can_MessageData_t *canMsg, uint32_t &canMsgLength, uint64_t ×tamp) override; + + //-------------------------------SEND Functions-------------------------------// + + void SetEnabled(std::vector ¶ms, bool testOnly); + void GetEnabled(std::vector ¶ms, bool testOnly); + + void SetTarget(std::vector ¶ms, bool testOnly); + void GetTarget(std::vector ¶ms, bool testOnly); + + void SetP(std::vector ¶ms, bool testOnly); + void GetP(std::vector ¶ms, bool testOnly); + + void SetI(std::vector ¶ms, bool testOnly); + void GetI(std::vector ¶ms, bool testOnly); + + void SetSensorSlope(std::vector ¶ms, bool testOnly); + void GetSensorSlope(std::vector ¶ms, bool testOnly); + + void SetSensorOffset(std::vector ¶ms, bool testOnly); + void GetSensorOffset(std::vector ¶ms, bool testOnly); + + void SetOperatingPoint(std::vector ¶ms, bool testOnly); + void GetOperatingPoint(std::vector ¶ms, bool testOnly); + + void SetActuatorChannelID(std::vector ¶ms, bool testOnly); + void GetActuatorChannelID(std::vector ¶ms, bool testOnly); + + void SetSensorChannelID(std::vector ¶ms, bool testOnly); + void GetSensorChannelID(std::vector ¶ms, bool testOnly); + + void SetRefreshDivider(std::vector ¶ms, bool testOnly); + void GetRefreshDivider(std::vector ¶ms, bool testOnly); + + void RequestStatus(std::vector ¶ms, bool testOnly) override; + void RequestResetSettings(std::vector ¶ms, bool testOnly) override; + + //-------------------------------Utility Functions-------------------------------// + + void RequestCurrentState() override; +}; + +#endif //LLSERVER_ECUI_HOUBOLT_PI_CONTROL_H diff --git a/include/can_houbolt b/include/can_houbolt index cac3fb2f..ef63dc05 160000 --- a/include/can_houbolt +++ b/include/can_houbolt @@ -1 +1 @@ -Subproject commit cac3fb2f79a7c608d7d25834baaa7e29e79256a8 +Subproject commit ef63dc052591c2f54347955e9b21596fe64f6cfe diff --git a/src/SequenceManager.cpp b/src/SequenceManager.cpp index 3acefc01..59ccbcf6 100644 --- a/src/SequenceManager.cpp +++ b/src/SequenceManager.cpp @@ -281,8 +281,9 @@ void SequenceManager::StartSequence(nlohmann::json jsonSeq, nlohmann::json jsonA sequenceRunning = true; sequenceThread = std::thread(&SequenceManager::sequenceLoop, this, interval_us); //sequenceThread.detach(); + std::string sequence_name = jsonSeq["data"][0]["desc"]; - Debug::print("Sequence Started"); + Debug::print("Sequence Started " + sequence_name); } } diff --git a/src/can/Control.cpp b/src/can/Control.cpp index 0f07d880..ef626bf3 100644 --- a/src/can/Control.cpp +++ b/src/can/Control.cpp @@ -5,43 +5,42 @@ #include "can/Control.h" const std::vector Control::states = - { - "Enabled", - "Target", - "Threshold", - "Hysteresis", - "ActuatorChannelID", - "SensorChannelID", - "RefreshDivider", - "RequestStatus", - "ResetAllSettings" - }; + { + "Enabled", + "Target", + "Threshold", + "Hysteresis", + "ActuatorChannelID", + "SensorChannelID", + "RefreshDivider", + "RequestStatus", + "ResetAllSettings" + }; const std::map> Control::scalingMap = - { - {"Enabled", {1.0, 0.0}}, - {"Position", {1.0, 0.0}}, - {"Target", {0.003735, 0.0}}, - {"Threshold", {1.0, 0.0}}, - {"Hysteresis", {0.003735, 0.0}}, - {"ActuatorChannelID", {1.0, 0.0}}, - {"SensorChannelID", {1.0, 0.0}}, - {"RefreshDivider", {1.0, 0.0}}, - }; - -const std::map Control::variableMap = - { - {CONTROL_ENABLED, "Enabled"}, - {CONTROL_TARGET, "Target"}, - {CONTROL_THRESHOLD, "Threshold"}, - {CONTROL_HYSTERESIS, "Hysteresis"}, - {CONTROL_ACTUATOR_CHANNEL_ID, "ActuatorChannelID"}, - {CONTROL_SENSOR_CHANNEL_ID, "SensorChannelID"}, - {CONTROL_REFRESH_DIVIDER, "RefreshDivider"}, - }; + { + {"Enabled", {1.0, 0.0}}, + {"Target", {0.001189720812, -15.19667413}}, + {"Threshold", {1.0, 0.0}}, + {"Hysteresis", {0.001189720812, -15.19667413}}, + {"ActuatorChannelID", {1.0, 0.0}}, + {"SensorChannelID", {1.0, 0.0}}, + {"RefreshDivider", {1.0, 0.0}}, + }; + +const std::map Control::variableMap = + { + {CONTROL_ENABLED, "Enabled"}, + {CONTROL_TARGET, "Target"}, + {CONTROL_THRESHOLD, "Threshold"}, + {CONTROL_HYSTERESIS, "Hysteresis"}, + {CONTROL_ACTUATOR_CHANNEL_ID, "ActuatorChannelID"}, + {CONTROL_SENSOR_CHANNEL_ID, "SensorChannelID"}, + {CONTROL_REFRESH_DIVIDER, "RefreshDivider"}, + }; Control::Control(uint8_t channelID, std::string channelName, std::vector sensorScaling, Node *parent) - : Channel("Control", channelID, std::move(channelName), sensorScaling, parent, CONTROL_DATA_N_BYTES), NonNodeChannel(parent) + : Channel("Control", channelID, std::move(channelName), sensorScaling, parent, CONTROL_DATA_N_BYTES), NonNodeChannel(parent) { commandMap = { {"SetEnabled", {std::bind(&Control::SetEnabled, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, @@ -87,25 +86,25 @@ void Control::ProcessCANCommand(Can_MessageData_t *canMsg, uint32_t &canMsgLengt { switch (canMsg->bit.cmd_id) { - case CONTROL_RES_GET_VARIABLE: - case CONTROL_RES_SET_VARIABLE: - GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); - break; - case CONTROL_RES_STATUS: - StatusResponse(canMsg, canMsgLength, timestamp); - break; - case CONTROL_RES_RESET_SETTINGS: - ResetSettingsResponse(canMsg, canMsgLength, timestamp); - break; - case CONTROL_REQ_RESET_SETTINGS: - case CONTROL_REQ_STATUS: - case CONTROL_REQ_SET_VARIABLE: - case CONTROL_REQ_GET_VARIABLE: - //TODO: comment out after testing - //throw std::runtime_error("request message type has been received, major fault in protocol"); - break; - default: - throw std::runtime_error("Control specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); + case CONTROL_RES_GET_VARIABLE: + case CONTROL_RES_SET_VARIABLE: + GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); + break; + case CONTROL_RES_STATUS: + StatusResponse(canMsg, canMsgLength, timestamp); + break; + case CONTROL_RES_RESET_SETTINGS: + ResetSettingsResponse(canMsg, canMsgLength, timestamp); + break; + case CONTROL_REQ_RESET_SETTINGS: + case CONTROL_REQ_STATUS: + case CONTROL_REQ_SET_VARIABLE: + case CONTROL_REQ_GET_VARIABLE: + // TODO: comment out after testing + // throw std::runtime_error("request message type has been received, major fault in protocol"); + break; + default: + throw std::runtime_error("Control specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); } } catch (std::exception &e) @@ -341,12 +340,12 @@ void Control::RequestResetSettings(std::vector ¶ms, bool testOnly) void Control::RequestCurrentState() { std::vector params; - - GetEnabled(params, false); - GetTarget(params, false); - GetThreshold(params, false); - GetHysteresis(params, false); - GetActuatorChannelID(params, false); - GetSensorChannelID(params, false); - GetRefreshDivider(params, false); + + GetEnabled(params, false); + GetTarget(params, false); + GetThreshold(params, false); + GetHysteresis(params, false); + GetActuatorChannelID(params, false); + GetSensorChannelID(params, false); + GetRefreshDivider(params, false); } \ No newline at end of file diff --git a/src/can/Node.cpp b/src/can/Node.cpp index 15bf9cc2..88990cce 100644 --- a/src/can/Node.cpp +++ b/src/can/Node.cpp @@ -14,6 +14,7 @@ #include "can/Servo.h" #include "can/PneumaticValve.h" #include "can/Control.h" +#include "can/PIControl.h" #include "can/IMU.h" #include "can/Rocket.h" #include "StateController.h" @@ -188,6 +189,9 @@ void Node::InitChannels(NodeInfoMsg_t &nodeInfo, std::map(channelInfo[channelID]), std::get<1>(channelInfo[channelID]), this); break; + case CHANNEL_TYPE_PI_CONTROL: + ch = new PIControl(channelID, std::get<0>(channelInfo[channelID]), std::get<1>(channelInfo[channelID]), this); + break; case CHANNEL_TYPE_IMU: ch = new IMU(channelID, std::get<0>(channelInfo[channelID]), std::get<1>(channelInfo[channelID]), this); break; diff --git a/src/can/PIControl.cpp b/src/can/PIControl.cpp new file mode 100644 index 00000000..ed8de803 --- /dev/null +++ b/src/can/PIControl.cpp @@ -0,0 +1,453 @@ +// +// Created by Markus on 03.09.21. +// + +#include "can/PIControl.h" + +const std::vector PIControl::states = + { + "Enabled", + "Target", + "P", + "I", + "SensorSlope", + "SensorOffset", + "OperatingPoint", + "ActuatorChannelID", + "SensorChannelID", + "RefreshDivider", + "RequestStatus", + "ResetAllSettings" + }; + +const std::map> PIControl::scalingMap = + { + {"Enabled", {1.0, 0.0}}, + {"Target", {0.001, 0.0}}, + {"P", {0.001, 0.0}}, + {"I", {0.001, 0.0}}, + {"SensorSlope", {0.001, 0.0}}, + {"SensorOffset", {0.001, 0.0}}, + {"OperatingPoint", {0.001, 0.0}}, + {"ActuatorChannelID", {1.0, 0.0}}, + {"SensorChannelID", {1.0, 0.0}}, + {"RefreshDivider", {1.0, 0.0}}, + }; + +const std::map PIControl::variableMap = + { + {PI_CONTROL_ENABLED, "Enabled"}, + {PI_CONTROL_TARGET, "Target"}, + {PI_CONTROL_P, "P"}, + {PI_CONTROL_I, "I"}, + {PI_CONTROL_SENSOR_SLOPE, "SensorSlope"}, + {PI_CONTROL_SENSOR_OFFSET, "SensorOffset"}, + {PI_CONTROL_OPERATING_POINT, "OperatingPoint"}, + {PI_CONTROL_ACTUATOR_CHANNEL_ID, "ActuatorChannelID"}, + {PI_CONTROL_SENSOR_CHANNEL_ID, "SensorChannelID"}, + {PI_CONTROL_REFRESH_DIVIDER, "RefreshDivider"}, + }; + +PIControl::PIControl(uint8_t channelID, std::string channelName, std::vector sensorScaling, Node *parent) + : Channel("PIControl", channelID, std::move(channelName), sensorScaling, parent, PI_CONTROL_DATA_N_BYTES), NonNodeChannel(parent) +{ + commandMap = { + {"SetEnabled", {std::bind(&PIControl::SetEnabled, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetEnabled", {std::bind(&PIControl::GetEnabled, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetTarget", {std::bind(&PIControl::SetTarget, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetTarget", {std::bind(&PIControl::GetTarget, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetP", {std::bind(&PIControl::SetP, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetP", {std::bind(&PIControl::GetP, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetI", {std::bind(&PIControl::SetI, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetI", {std::bind(&PIControl::GetI, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetSensorSlope", {std::bind(&PIControl::SetSensorSlope, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetSensorSlope", {std::bind(&PIControl::GetSensorSlope, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetSensorOffset", {std::bind(&PIControl::SetSensorOffset, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetSensorOffset", {std::bind(&PIControl::GetSensorOffset, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetOperatingPoint", {std::bind(&PIControl::SetOperatingPoint, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetOperatingPoint", {std::bind(&PIControl::GetOperatingPoint, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetActuatorChannelID", {std::bind(&PIControl::SetActuatorChannelID, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetActuatorChannelID", {std::bind(&PIControl::GetActuatorChannelID, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetSensorChannelID", {std::bind(&PIControl::SetSensorChannelID, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetSensorChannelID", {std::bind(&PIControl::GetSensorChannelID, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetRefreshDivider", {std::bind(&PIControl::SetRefreshDivider, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetRefreshDivider", {std::bind(&PIControl::GetRefreshDivider, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"RequestStatus", {std::bind(&PIControl::RequestStatus, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"RequestResetSettings", {std::bind(&PIControl::RequestResetSettings, this, std::placeholders::_1, std::placeholders::_2), {}}}, + }; +} + +//---------------------------------------------------------------------------------------// +//-------------------------------GETTER & SETTER Functions-------------------------------// +//---------------------------------------------------------------------------------------// + +std::vector PIControl::GetStates() +{ + std::vector states = PIControl::states; + for (auto &state : states) + { + state = GetStatePrefix() + state; + } + return states; +} + +//-------------------------------------------------------------------------------// +//-------------------------------RECEIVE Functions-------------------------------// +//-------------------------------------------------------------------------------// + +void PIControl::ProcessCANCommand(Can_MessageData_t *canMsg, uint32_t &canMsgLength, uint64_t ×tamp) +{ + try + { + switch (canMsg->bit.cmd_id) + { + case PI_CONTROL_RES_GET_VARIABLE: + case PI_CONTROL_RES_SET_VARIABLE: + GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); + break; + case PI_CONTROL_RES_STATUS: + StatusResponse(canMsg, canMsgLength, timestamp); + break; + case PI_CONTROL_RES_RESET_SETTINGS: + ResetSettingsResponse(canMsg, canMsgLength, timestamp); + break; + case PI_CONTROL_REQ_RESET_SETTINGS: + case PI_CONTROL_REQ_STATUS: + case PI_CONTROL_REQ_SET_VARIABLE: + case PI_CONTROL_REQ_GET_VARIABLE: + // TODO: comment out after testing + // throw std::runtime_error("request message type has been received, major fault in protocol"); + break; + default: + throw std::runtime_error("PIControl specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); + } + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl '" + this->channelName + "' - ProcessCANCommand: " + std::string(e.what())); + } +} + +//----------------------------------------------------------------------------// +//-------------------------------SEND Functions-------------------------------// +//----------------------------------------------------------------------------// + +void PIControl::SetEnabled(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("Enabled"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_ENABLED, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetEnabled: " + std::string(e.what())); + } +} + +void PIControl::GetEnabled(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_ENABLED, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetEnabled: " + std::string(e.what())); + } +} + +void PIControl::SetTarget(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("Target"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_TARGET, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetTarget: " + std::string(e.what())); + } +} + +void PIControl::GetTarget(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_TARGET, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetTarget: " + std::string(e.what())); + } +} + +void PIControl::SetP(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("P"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetP: " + std::string(e.what())); + } +} + +void PIControl::GetP(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetP: " + std::string(e.what())); + } +} + +void PIControl::SetI(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("I"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetI: " + std::string(e.what())); + } +} + +void PIControl::GetI(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetI: " + std::string(e.what())); + } +} + +void PIControl::SetSensorSlope(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("SensorSlope"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_SLOPE, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetSensorSlope: " + std::string(e.what())); + } +} + +void PIControl::GetSensorSlope(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_SLOPE, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetSensorSlope: " + std::string(e.what())); + } +} + +void PIControl::SetSensorOffset(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("SensorOffset"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_OFFSET, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetSensorOffset: " + std::string(e.what())); + } +} + +void PIControl::GetSensorOffset(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_OFFSET, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetSensorOffset: " + std::string(e.what())); + } +} + +void PIControl::SetOperatingPoint(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("OperatingPoint"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_OPERATING_POINT, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetOperatingPoint: " + std::string(e.what())); + } +} + +void PIControl::GetOperatingPoint(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_OPERATING_POINT, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetOperatingPoint: " + std::string(e.what())); + } +} + +void PIControl::SetActuatorChannelID(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("ActuatorChannelID"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_ACTUATOR_CHANNEL_ID, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetActuatorChannelID: " + std::string(e.what())); + } +} + +void PIControl::GetActuatorChannelID(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_ACTUATOR_CHANNEL_ID, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetActuatorChannelID: " + std::string(e.what())); + } +} + +void PIControl::SetSensorChannelID(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("SensorChannelID"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_CHANNEL_ID, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetSensorChannelID: " + std::string(e.what())); + } +} + +void PIControl::GetSensorChannelID(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_CHANNEL_ID, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetSensorChannelID: " + std::string(e.what())); + } +} + +void PIControl::SetRefreshDivider(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("RefreshDivider"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_REFRESH_DIVIDER, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetRefreshDivider: " + std::string(e.what())); + } +} + +void PIControl::GetRefreshDivider(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_REFRESH_DIVIDER, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetRefreshDivider: " + std::string(e.what())); + } +} + +void PIControl::RequestStatus(std::vector ¶ms, bool testOnly) +{ + try + { + SendNoPayloadCommand(params, parent->GetNodeID(), PI_CONTROL_REQ_STATUS, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - RequestStatus: " + std::string(e.what())); + } +} + +void PIControl::RequestResetSettings(std::vector ¶ms, bool testOnly) +{ + try + { + SendNoPayloadCommand(params, parent->GetNodeID(), PI_CONTROL_REQ_RESET_SETTINGS, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - RequestResetSettings: " + std::string(e.what())); + } +} + +void PIControl::RequestCurrentState() +{ + std::vector params; + + GetEnabled(params, false); + GetTarget(params, false); + GetP(params, false); + GetI(params, false); + GetSensorSlope(params, false); + GetSensorOffset(params, false); + GetOperatingPoint(params, false); + GetActuatorChannelID(params, false); + GetSensorChannelID(params, false); + GetRefreshDivider(params, false); +} \ No newline at end of file diff --git a/src/can/Servo.cpp b/src/can/Servo.cpp index 97b0e962..c78173ca 100644 --- a/src/can/Servo.cpp +++ b/src/can/Servo.cpp @@ -5,68 +5,67 @@ #include "can/Servo.h" const std::vector Servo::states = - { - "Position", - "TargetPosition", - "TargetPressure", - "MaxSpeed", - "MaxAccel", - "MaxTorque", - "P", - "I", - "D", - "SensorChannelID", - "Startpoint", - "Endpoint", - "PWMEnabled", - "PositionRaw", - "RefreshDivider", - "RequestStatus", - "ResetAllSettings" - }; + { + "Position", + "TargetPosition", + "TargetPressure", + "MaxSpeed", + "MaxAccel", + "MaxTorque", + "P", + "I", + "D", + "SensorChannelID", + "Startpoint", + "Endpoint", + "PWMEnabled", + "PositionRaw", + "RefreshDivider", + "RequestStatus", + "ResetAllSettings"}; const std::map> Servo::scalingMap = - { - {"Position", {0.00152590219, 0.0}}, - {"TargetPosition", {0.00152590219, 0.0}}, - {"TargetPressure", {1.0, 0.0}}, - {"MaxSpeed", {1.0, 0.0}}, - {"MaxAccel", {1.0, 0.0}}, - {"MaxTorque", {1.0, 0.0}}, - {"P", {1.0, 0.0}}, - {"I", {1.0, 0.0}}, - {"D", {1.0, 0.0}}, - {"SensorChannelID", {1.0, 0.0}}, - {"Startpoint", {1.0, 0.0}}, - {"Endpoint", {1.0, 0.0}}, - {"PWMEnabled", {1.0, 0.0}}, - {"PositionRaw", {1.0, 0.0}}, - {"MovePosition", {1.0, 0.0}}, - {"MoveInterval", {1.0, 0.0}}, - {"RefreshDivider", {1.0, 0.0}}, - }; - -const std::map Servo::variableMap = - { - {SERVO_POSITION, "Position"}, - {SERVO_TARGET_POSITION, "TargetPosition"}, - {SERVO_TARGET_PRESSURE, "TargetPressure"}, - {SERVO_MAX_SPEED, "MaxSpeed"}, - {SERVO_MAX_ACCEL, "MaxAccel"}, - {SERVO_MAX_TORQUE, "MaxTorque"}, - {SERVO_P_PARAM, "P"}, - {SERVO_I_PARAM, "I"}, - {SERVO_D_PARAM, "D"}, - {SERVO_SENSOR_CHANNEL_ID, "SensorChannelID"}, - {SERVO_POSITION_STARTPOINT, "Startpoint"}, - {SERVO_POSITION_ENDPOINT, "Endpoint"}, - {SERVO_PWM_ENABLED, "PWMEnabled"}, - {SERVO_POSITION_RAW, "PositionRaw"}, - {SERVO_SENSOR_REFRESH_DIVIDER, "RefreshDivider"}, - }; + { + {"Position", {0.00152590219, 0.0}}, + {"TargetPosition", {0.00152590219, 0.0}}, + {"TargetPressure", {1.0, 0.0}}, + {"MaxSpeed", {1.0, 0.0}}, + {"MaxAccel", {1.0, 0.0}}, + {"MaxTorque", {1.0, 0.0}}, + {"P", {1.0, 0.0}}, + {"I", {1.0, 0.0}}, + {"D", {1.0, 0.0}}, + {"SensorChannelID", {1.0, 0.0}}, + {"Startpoint", {1.0, 0.0}}, + {"Endpoint", {1.0, 0.0}}, + {"PWMEnabled", {1.0, 0.0}}, + {"PositionRaw", {1.0, 0.0}}, + {"MovePosition", {1.0, 0.0}}, + {"MoveInterval", {1.0, 0.0}}, + {"RefreshDivider", {1.0, 0.0}}, +}; + +const std::map Servo::variableMap = + { + {SERVO_POSITION, "Position"}, + {SERVO_TARGET_POSITION, "TargetPosition"}, + {SERVO_TARGET_PRESSURE, "TargetPressure"}, + {SERVO_MAX_SPEED, "MaxSpeed"}, + {SERVO_MAX_ACCEL, "MaxAccel"}, + {SERVO_MAX_TORQUE, "MaxTorque"}, + {SERVO_P_PARAM, "P"}, + {SERVO_I_PARAM, "I"}, + {SERVO_D_PARAM, "D"}, + {SERVO_SENSOR_CHANNEL_ID, "SensorChannelID"}, + {SERVO_POSITION_STARTPOINT, "Startpoint"}, + {SERVO_POSITION_ENDPOINT, "Endpoint"}, + {SERVO_PWM_ENABLED, "PWMEnabled"}, + {SERVO_POSITION_RAW, "PositionRaw"}, + {SERVO_SENSOR_REFRESH_DIVIDER, "RefreshDivider"}, +}; Servo::Servo(uint8_t channelID, std::string channelName, std::vector sensorScaling, Node *parent) - : Channel("Servo", channelID, std::move(channelName), sensorScaling, parent, SERVO_DATA_N_BYTES), NonNodeChannel(parent) + : Channel("Servo", channelID, std::move(channelName), sensorScaling, parent, SERVO_DATA_N_BYTES), NonNodeChannel(parent) { commandMap = { {"SetPosition", {std::bind(&Servo::SetPosition, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, @@ -101,7 +100,7 @@ Servo::Servo(uint8_t channelID, std::string channelName, std::vector sen {"GetRefreshDivider", {std::bind(&Servo::GetRefreshDivider, this, std::placeholders::_1, std::placeholders::_2), {}}}, {"RequestStatus", {std::bind(&Servo::RequestStatus, this, std::placeholders::_1, std::placeholders::_2), {}}}, {"RequestResetSettings", {std::bind(&Servo::RequestResetSettings, this, std::placeholders::_1, std::placeholders::_2), {}}}, - {"RequestMove", {std::bind(&Servo::RequestMove, this, std::placeholders::_1, std::placeholders::_2),{"Position","TimeInterval"}}}, + {"RequestMove", {std::bind(&Servo::RequestMove, this, std::placeholders::_1, std::placeholders::_2), {"Position", "TimeInterval"}}}, }; } @@ -129,25 +128,25 @@ void Servo::ProcessCANCommand(Can_MessageData_t *canMsg, uint32_t &canMsgLength, { switch (canMsg->bit.cmd_id) { - case SERVO_RES_GET_VARIABLE: - case SERVO_RES_SET_VARIABLE: - GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); - break; - case SERVO_RES_STATUS: - StatusResponse(canMsg, canMsgLength, timestamp); - break; - case SERVO_RES_RESET_SETTINGS: - ResetSettingsResponse(canMsg, canMsgLength, timestamp); - break; - case SERVO_REQ_RESET_SETTINGS: - case SERVO_REQ_STATUS: - case SERVO_REQ_SET_VARIABLE: - case SERVO_REQ_GET_VARIABLE: - //TODO: comment out after testing - //throw std::runtime_error("request message type has been received, major fault in protocol"); - break; - default: - throw std::runtime_error("Servo specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); + case SERVO_RES_GET_VARIABLE: + case SERVO_RES_SET_VARIABLE: + GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); + break; + case SERVO_RES_STATUS: + StatusResponse(canMsg, canMsgLength, timestamp); + break; + case SERVO_RES_RESET_SETTINGS: + ResetSettingsResponse(canMsg, canMsgLength, timestamp); + break; + case SERVO_REQ_RESET_SETTINGS: + case SERVO_REQ_STATUS: + case SERVO_REQ_SET_VARIABLE: + case SERVO_REQ_GET_VARIABLE: + // TODO: comment out after testing + // throw std::runtime_error("request message type has been received, major fault in protocol"); + break; + default: + throw std::runtime_error("Servo specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); } } catch (std::exception &e) @@ -272,7 +271,6 @@ void Servo::GetTargetPressure(std::vector ¶ms, bool testOnly) } } - void Servo::SetMaxSpeed(std::vector ¶ms, bool testOnly) { std::vector scalingParams = scalingMap.at("MaxSpeed"); @@ -585,7 +583,7 @@ void Servo::RequestMove(std::vector ¶ms, bool testOnly) { try { - if (params.size() != 2) //number of required parameters + if (params.size() != 2) // number of required parameters { throw std::runtime_error("2 parameters expected, but " + std::to_string(params.size()) + " were provided"); } @@ -593,10 +591,10 @@ void Servo::RequestMove(std::vector ¶ms, bool testOnly) std::vector scalingInterval = scalingMap.at("MoveInterval"); ServoMoveMsg_t moveMsg = {0}; - moveMsg.position = Channel::ScaleAndConvertInt32(params[0],scalingPosition[0],scalingPosition[1]); - moveMsg.interval = Channel::ScaleAndConvertInt32(params[1],scalingInterval[0],scalingInterval[1]); + moveMsg.position = Channel::ScaleAndConvertInt32(params[0], scalingPosition[0], scalingPosition[1]); + moveMsg.interval = Channel::ScaleAndConvertInt32(params[1], scalingInterval[0], scalingInterval[1]); - SendStandardCommand(parent->GetNodeID(), SERVO_REQ_MOVE, (uint8_t *) &moveMsg, sizeof(moveMsg), parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + SendStandardCommand(parent->GetNodeID(), SERVO_REQ_MOVE, (uint8_t *)&moveMsg, sizeof(moveMsg), parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { @@ -632,19 +630,19 @@ void Servo::RequestCurrentState() { std::vector params; - GetPosition(params, false); + GetPosition(params, false); GetPositionRaw(params, false); - GetTargetPosition(params, false); - GetTargetPressure(params, false); - GetMaxSpeed(params, false); - GetMaxAccel(params, false); - GetMaxTorque(params, false); - GetP(params, false); - GetI(params, false); - GetD(params, false); - GetSensorChannelID(params, false); - GetStartpoint(params, false); - GetEndpoint(params, false); - GetPWMEnabled(params, false); - GetRefreshDivider(params, false); + GetTargetPosition(params, false); + GetTargetPressure(params, false); + GetMaxSpeed(params, false); + GetMaxAccel(params, false); + GetMaxTorque(params, false); + GetP(params, false); + GetI(params, false); + GetD(params, false); + GetSensorChannelID(params, false); + GetStartpoint(params, false); + GetEndpoint(params, false); + GetPWMEnabled(params, false); + GetRefreshDivider(params, false); } \ No newline at end of file From 575e69342cd48f861e06e9f1b5bc75de0512b0e7 Mon Sep 17 00:00:00 2001 From: spaceteamofficial Date: Sat, 4 Jan 2025 14:58:05 +0100 Subject: [PATCH 10/10] - updated PI Controller to allow asymetric responses - Added debug output for every can message - Removed the RequestCurrentState in the CANManager init Raffael Rott (changes by other people) --- include/can/PIControl.h | 14 +++-- include/can_houbolt | 2 +- src/can/CANManager.cpp | 14 ++++- src/can/PIControl.cpp | 120 +++++++++++++++++++++++++++++++--------- 4 files changed, 117 insertions(+), 33 deletions(-) diff --git a/include/can/PIControl.h b/include/can/PIControl.h index 9a11f68e..b1e250a0 100644 --- a/include/can/PIControl.h +++ b/include/can/PIControl.h @@ -36,11 +36,17 @@ class PIControl : public Channel, public NonNodeChannel void SetTarget(std::vector ¶ms, bool testOnly); void GetTarget(std::vector ¶ms, bool testOnly); - void SetP(std::vector ¶ms, bool testOnly); - void GetP(std::vector ¶ms, bool testOnly); + void SetP_POS(std::vector ¶ms, bool testOnly); + void GetP_POS(std::vector ¶ms, bool testOnly); - void SetI(std::vector ¶ms, bool testOnly); - void GetI(std::vector ¶ms, bool testOnly); + void SetI_POS(std::vector ¶ms, bool testOnly); + void GetI_POS(std::vector ¶ms, bool testOnly); + + void SetP_NEG(std::vector ¶ms, bool testOnly); + void GetP_NEG(std::vector ¶ms, bool testOnly); + + void SetI_NEG(std::vector ¶ms, bool testOnly); + void GetI_NEG(std::vector ¶ms, bool testOnly); void SetSensorSlope(std::vector ¶ms, bool testOnly); void GetSensorSlope(std::vector ¶ms, bool testOnly); diff --git a/include/can_houbolt b/include/can_houbolt index ef63dc05..1e5a2ef2 160000 --- a/include/can_houbolt +++ b/include/can_houbolt @@ -1 +1 @@ -Subproject commit ef63dc052591c2f54347955e9b21596fe64f6cfe +Subproject commit 1e5a2ef28e63694ed6ec2c6c5626a4745c90b262 diff --git a/src/can/CANManager.cpp b/src/can/CANManager.cpp index c3314659..02bad3f6 100644 --- a/src/can/CANManager.cpp +++ b/src/can/CANManager.cpp @@ -195,7 +195,7 @@ CANResult CANManager::Init(Config &config) initialized = true; Debug::print("Request current state and config from nodes...\n"); - RequestCurrentState(); + //RequestCurrentState(); } catch (std::exception& e) @@ -387,7 +387,17 @@ void CANManager::OnCANRecv(uint8_t canBusChannelID, uint32_t canID, uint8_t *pay { if (canIDStruct->info.direction == 0) { - Debug::print("Direction bit master to node from node %d on bus %d, delegating msg...", nodeID, canBusChannelID); + Debug::print("Direction bit master to node %d on bus %d, delegating msg...", nodeID, canBusChannelID); + std::string msg; + msg += "\nNode ID: " + std::to_string(nodeID) + "\n"; + msg += "Channel ID: " + std::to_string(canMsg->bit.info.channel_id) + "\n"; + msg += "CMD ID: " + std::to_string(canMsg->bit.cmd_id) + "\n"; + for (int i = 0; i < payloadLength; i++) + { + msg += std::to_string(canMsg->bit.data.uint8[i]) + " "; + } + msg += "\n"; + Debug::print(msg); //TODO: DIRTY HOTFIX, remove it std::vector channels = {0,1,2,3}; channels.erase(channels.begin()+canBusChannelID); diff --git a/src/can/PIControl.cpp b/src/can/PIControl.cpp index ed8de803..760eca70 100644 --- a/src/can/PIControl.cpp +++ b/src/can/PIControl.cpp @@ -8,8 +8,10 @@ const std::vector PIControl::states = { "Enabled", "Target", - "P", - "I", + "P_POS", + "I_POS", + "P_NEG", + "I_NEG", "SensorSlope", "SensorOffset", "OperatingPoint", @@ -24,8 +26,10 @@ const std::map> PIControl::scalingMap = { {"Enabled", {1.0, 0.0}}, {"Target", {0.001, 0.0}}, - {"P", {0.001, 0.0}}, - {"I", {0.001, 0.0}}, + {"P_POS", {0.001, 0.0}}, + {"I_POS", {0.001, 0.0}}, + {"P_NEG", {0.001, 0.0}}, + {"I_NEG", {0.001, 0.0}}, {"SensorSlope", {0.001, 0.0}}, {"SensorOffset", {0.001, 0.0}}, {"OperatingPoint", {0.001, 0.0}}, @@ -38,8 +42,10 @@ const std::map PIControl::variableMap = { {PI_CONTROL_ENABLED, "Enabled"}, {PI_CONTROL_TARGET, "Target"}, - {PI_CONTROL_P, "P"}, - {PI_CONTROL_I, "I"}, + {PI_CONTROL_P_POS, "P_POS"}, + {PI_CONTROL_I_POS, "I_POS"}, + {PI_CONTROL_P_NEG, "P_NEG"}, + {PI_CONTROL_I_NEG, "I_NEG"}, {PI_CONTROL_SENSOR_SLOPE, "SensorSlope"}, {PI_CONTROL_SENSOR_OFFSET, "SensorOffset"}, {PI_CONTROL_OPERATING_POINT, "OperatingPoint"}, @@ -56,10 +62,14 @@ PIControl::PIControl(uint8_t channelID, std::string channelName, std::vector ¶ms, bool testOnly) } } -void PIControl::SetP(std::vector ¶ms, bool testOnly) +void PIControl::SetP_POS(std::vector ¶ms, bool testOnly) { - std::vector scalingParams = scalingMap.at("P"); + std::vector scalingParams = scalingMap.at("P_POS"); try { - SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P, + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P_POS, scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { - throw std::runtime_error("PIControl - SetP: " + std::string(e.what())); + throw std::runtime_error("PIControl - SetP_POS: " + std::string(e.what())); } } -void PIControl::GetP(std::vector ¶ms, bool testOnly) +void PIControl::GetP_POS(std::vector ¶ms, bool testOnly) { try { - GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P, + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P_POS, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { - throw std::runtime_error("PIControl - GetP: " + std::string(e.what())); + throw std::runtime_error("PIControl - GetP_POS: " + std::string(e.what())); } } -void PIControl::SetI(std::vector ¶ms, bool testOnly) +void PIControl::SetI_POS(std::vector ¶ms, bool testOnly) { - std::vector scalingParams = scalingMap.at("I"); + std::vector scalingParams = scalingMap.at("I_POS"); try { - SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I, + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I_POS, scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { - throw std::runtime_error("PIControl - SetI: " + std::string(e.what())); + throw std::runtime_error("PIControl - SetI_POS: " + std::string(e.what())); } } -void PIControl::GetI(std::vector ¶ms, bool testOnly) +void PIControl::GetI_POS(std::vector ¶ms, bool testOnly) { try { - GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I, + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I_POS, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { - throw std::runtime_error("PIControl - GetI: " + std::string(e.what())); + throw std::runtime_error("PIControl - GetI_POS: " + std::string(e.what())); + } +} + +void PIControl::SetP_NEG(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("P_NEG"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P_NEG, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetP_NEG: " + std::string(e.what())); + } +} + +void PIControl::GetP_NEG(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P_NEG, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetP_NEG: " + std::string(e.what())); + } +} + +void PIControl::SetI_NEG(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("I_NEG"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I_NEG, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetI_NEG: " + std::string(e.what())); + } +} + +void PIControl::GetI_NEG(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I_NEG, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetI_NEG: " + std::string(e.what())); } } @@ -442,8 +508,10 @@ void PIControl::RequestCurrentState() GetEnabled(params, false); GetTarget(params, false); - GetP(params, false); - GetI(params, false); + GetP_POS(params, false); + GetI_POS(params, false); + GetP_NEG(params, false); + GetI_NEG(params, false); GetSensorSlope(params, false); GetSensorOffset(params, false); GetOperatingPoint(params, false);