From d0d58edec1d72af17ae5b11a871c050942fb56c7 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 13 Jan 2024 18:48:49 -0500 Subject: [PATCH 001/126] Update to 2024 dependencies --- .gitignore | 16 + .vscode/settings.json | 6 +- .wpilib/wpilib_preferences.json | 4 +- README.md | 26 -- TestMode1.PNG | Bin 88763 -> 0 bytes TestMode2.PNG | Bin 87198 -> 0 bytes WPILib-License.md | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 35 +- gradlew.bat | 1 + settings.gradle | 5 +- shuffleboard.json | 36 -- src/main/cpp/commands/AutonomousCommands.cpp | 4 +- src/main/cpp/infrastructure/SparkMax.cpp | 76 ++-- src/main/cpp/infrastructure/SwerveModule.cpp | 4 +- src/main/cpp/subsystems/DriveSubsystem.cpp | 2 +- src/main/cpp/subsystems/FeederSubsystem.cpp | 8 +- src/main/cpp/subsystems/ShooterSubsystem.cpp | 4 +- src/main/include/Constants.h | 2 +- .../include/commands/AutonomousCommands.h | 4 +- src/main/include/commands/TestModeCommands.h | 22 +- src/main/include/infrastructure/SparkMax.h | 10 +- src/test/cpp/main.cpp | 3 +- vendordeps/NavX.json | 15 +- vendordeps/Phoenix.json | 423 ------------------ vendordeps/REVLib.json | 13 +- vendordeps/WPILibNewCommands.json | 49 +- 29 files changed, 160 insertions(+), 616 deletions(-) delete mode 100644 README.md delete mode 100644 TestMode1.PNG delete mode 100644 TestMode2.PNG mode change 100644 => 100755 gradlew delete mode 100644 shuffleboard.json delete mode 100644 vendordeps/Phoenix.json diff --git a/.gitignore b/.gitignore index 9535c83..5528d4f 100644 --- a/.gitignore +++ b/.gitignore @@ -158,5 +158,21 @@ gradle-app.setting .settings/ bin/ +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Fleet +.fleet + # Simulation GUI and other tools window save file *-window.json + +# Simulation data log directory +logs/ + +# Folder that has CTRE Phoenix Sim device config storage +ctre_sim/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 64b7017..5e795ff 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,9 +14,5 @@ "**/.factorypath": true, "**/*~": true }, - "C_Cpp.default.configurationProvider": "vscode-wpilib", - "files.associations": { - "*.inc": "cpp" - }, - "wpilib.skipTests": true + "C_Cpp.default.configurationProvider": "vscode-wpilib" } diff --git a/.wpilib/wpilib_preferences.json b/.wpilib/wpilib_preferences.json index 227c830..719fe35 100644 --- a/.wpilib/wpilib_preferences.json +++ b/.wpilib/wpilib_preferences.json @@ -1,6 +1,6 @@ { "enableCppIntellisense": true, "currentLanguage": "cpp", - "projectYear": "2023", - "teamNumber": 7443 + "projectYear": "2024", + "teamNumber": 2559 } \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 57230d1..0000000 --- a/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Swerve (for Scrappy the Robot) -C++ code for swerve drive: 2x SPARK MAX/NEO plus SRX MAG ENCODER per swerve module (i.e SDS Swerve Module); with Test Mode code. - -See [SwerveSensorInterfaceBoard](https://github.com/Jagwires7443/SwerveSensorInterfaceBoard) for details on electrical connections. - -Here are some of the features implemented in this code: - -* Handling PWM input from SRX MAG ENCODER to derive absolute position; -* Handling SPARK MAX motor controllers; -* Profiled PID control of turning position; -* Using turning position to override commanded drive, when modules are not facing in commanded direction; -* Distance and velocity Profiled PID control of drive motors; -* Test Mode includes interactive adjustment of PID settings; -* Error handling for motor controllers, up to being able to test code on a roboRIO with no motor controllers; -* Test Mode uses Shuffleboard to create a tab for each swerve module, and a tab for the overall swerve drive; -* Step-by-step bring up procedure for swerve modules and drive system is documented in block comments (in [SwerveModule.h](https://github.com/Jagwires7443/Swerve/blob/master/src/main/include/subsystems/SwerveModule.h)); -* Provides several Test Mode routines to automatically have robot drive various fixed test patterns; -* Logic to manage and save configuration of motor controller settings; -* Primitives useful for autonomous driving; -* Integrated with WPILib. - -Please see (and/or use) [shuffleboard.json](https://github.com/Jagwires7443/Swerve/blob/master/shuffleboard.json) for suggested Shuffleboard settings. - -![alt text](https://github.com/Jagwires7443/Swerve/blob/master/TestMode1.PNG?raw=true) - -![alt text](https://github.com/Jagwires7443/Swerve/blob/master/TestMode2.PNG?raw=true) diff --git a/TestMode1.PNG b/TestMode1.PNG deleted file mode 100644 index f94dc2bdad4a3e1e3ef8ae3b585a06e51763d648..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88763 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%0Dr_ zpMf-b^D7u$>H{CM8cy1I81vHo&VtMOLU{Nu<`4csJnsw5#{ZvAb^I6Qng6OGl)rmj zAg2r_q6+@#dJ3jOeyMp4kih%PC87YkE%*CB#|D}%C_t;hJ}Q$k0p?O2fupmFSiXms ziX5M5REL*_d^?TodM%h;Dj0%yP5e+JKo9{e;IGP|DgGVpeWNAR&Lm`6P~(q*Y9`GS z!+637(EUmzI%Z+P47y+(6U?`iE>jG|kE0$8It+ODMjD8B2 zt(b-KJB&w9)|LiIeEO%G3bJQaE1vCK8lb9M=nn?8Ioh0f9kuLoSJaf|jv; zbfA;NekylU#Jyq7q{V;SK$ys~_8EzGLx2T&0Tx6sE!-_` zv2q#NQa$Y7IZ5P??%54)+rrfg%ZsiSSlZ13ndPZ8gU$2`0KOZCJ*?v zBN=m=8$(?t9p)+31YUF`)Y~=it^=FbcTna2ujkTFrHpL5X?F722^Et6jn~RMtd(y% zmmEAFAFmewI=B1RU55_I92#i%Jh^`BR`Cbp?s`wI{_;!2$-w-iQwI*d+V=$3dGufU zYvgn*CU;L*rIu^VO+Jdrs1F7^;zqTl6Wnf<&v(BDRoZvpv~t{L1(>Xob(0Hsz_dz= z!II@Uw80W`-b52QR^0=xjZ3wNYj%hc27h%WMYuPMe0T5}r|Hf}F!fA0)P`3eDVDRa zI4^7eJzw|#w$|5vA?7KE-v5^0zd<@6qhD}ZSrnnlY)Zv73j}$rE}^jss&2-xMKlgv zJn8qjK|&<({1pGJLlX4qjDG5m8NbZ$yA`^Mv^9jH;!v`5G-Z%UQv@q6CF{P;G#IO^ zk$G&BY<$DgG+|K8Gvy1v#VYw2&OvP!|ILlH$osz(KeW9A>bkF&Z(SNE{4>(i;buSd zREYb>pVM7MX0$W|6!8O_6T5&i{K6>XX&q~XMg7Mi(hb`lERV2g@)`gGK_xYmZ_mJ7 z7UtbJv>4l2-28VQ&HuN}`*`2Ls9N)vpH~A6SMIp=H~9uYYZCaNrEm$%8_m=FUojE? z=lcNYUSAzpz1)^ik?FDMjEhxE1X7P7#9{ry zFRlkQ=f7_3Umq|F+*tZgD#}KJW@wRh)=#@39gDK#e;%U;bL14xN%d+Ruk&Yi9-Yc)Cy^m&g8P>xNwEz{irSg+f!Ej<9fNFU&~-xW)F z=<6*)TmLrJ&1D#nWAE9^4oJn<*&=jQx6)yIQcWGCYfs5og9f$F?Wfg#Nkdfq<1OPp zivdU%t0;OtN*|G5sA2bq*j%*b{4M8%G4=7bD_^L%OH7ZF-b;Y07GP zQx8NOk47e%N-|}cNe1TxT*{u^QuhjBJ$j|$*9aGg4L(K%Yi0d>WH;XOYWJ3=ETt?6Kd(JTOADw){*?=PZ&Xot3EO)|2}uZ>0x&C zJ%!eE#Ii)w>)$X~Zsls<+{>;aB1j&~l>CxdytI*{)sL5NJpmdqm-aWI`8Jrzys~|3 zmF^=hVZx9X>gMg&W&k1RRM0AUglR zAk|Y|uu+{fUSaI%6U$1sFXv`^^6djlCG6L_zJU)o=D|{;hUa5bogZEeQcApFLrcssJme$0K7TFs!YdC;@$XmyyK}zf;|5#^60?Q11Ot}CaYAvzg+_G z{S5&7JJQem@B<4{YY02Q`i2^QnMh`e)cSGcsLNkh6Vpc0!>X4Z<;n5SD&dlJUH7hP zfuFk`kDD=ijE1lROaa{zR>#gU<|uqHhi8qNkD9>akV$N{Cg0RqoK^cy-zJeMzX_j8 zT45nlPiCx$Gw%R9ux+$NAHvC#(dXwVmaoW%;>^)@PqfY={%-kGJgB988fJM%O>AFY zwwN*^t?~lBms+^y^?+KUcapN$y*1VMHnpnf8I@|JLXGPx$htaPAEilEAom$s%k39l z$$nW|Yc6FxGoPcxjaxp9rrIlt{TL#lR#Oq8P~i_FVMrb?NZRILC1uQ;NkaUe!p_?m zyQl&64QKidxef8U$)XyW8tQ+O zYSX7GJE)l1B0QFdIG^WP2(bv}Njt#cCL4t9>Ay+s=~Kbq9`6 zjnzk-l65Oz-Vab*r?hd;nj0H0-PYS{V)n~k`*U3lr7Ksj-XpOFEmUE4d_wf+#&Unw zupbkZl^4*BB2atqQ?t5bRavJQ85R|%QPkDQz_rq^+d(}iy+RyX9ZBYN-a{AxA z$$xmQf8w~w#gf5rT*6%o2&m_>VKDAzbc_) znH%gpno{)Ez2vgfOSuh)gxL10c4L2v?RmQ)E6>I)r~@MKM3w$vQ_Mb}|1wixEWw{J zgYPYmOMUiD%w}No!5@1`aLM<7zU0B*TP}FCMC~rbQ@rh8f0xz<{PxR{-&Vp97n_i$??==P|97eX zMbXiJm-^opi2iq}|F@R}e(*Oc zqO5TK5p~PLqce~4|L~ULu@(!@k$HpY{=yy0Y`_ctI!rw+r&#N=9|j@+f?qwe#}W0# zQUces@V5D3b;EUUK5xq#;GNT`)Y&r?Wx@2j0sj0N{uqfC3f39^y`^5{mtI0e$iBWs z!DEiVXQYnKzc-$%7zv%ULS6}cJCB{f&9g?0c@RFyhrd$C&6ZR#={46aI)#Tbt-=F} zp+Z9EK=SvGw;4NkyuuWm4r$L5^CUi-&n-=Vl3Gky!LVo~t9Uq0LisfQ_=UNQMlQr7 zGh>YnlI-(O_P+PJG*97AVuLpY+ji6q^T#WASj`W? zlVnO$K}pde@Ka$m-EDf7ogyR6h%(9Ft>u})6;7RLEqWGZWsSV;{8z@iy>L&#d#n z(;2PZ`5RvLWOatZD-Mv;y_$yoXYs29U;Jmw-F3oUK7Pbs=UYr?JnF64(Zd7XV?S%Z zrK(w&pPcS=`l)m12oP17#$Ujvjvz(TR(J-l{{Ptf4!9=Fb#F&2=hT7LRwxM6V{5Aw z5hZLATbz}lGDM~}4j?9oVFp6%X$7l<)H*;`3Md;QN{EakR%_BK5Fn69LWqhILJSeJ zAR)>3hT-VE_k8z$-?`_W{(fy8Mkefa^m_jr59E@gYSU(EX+vj{8i z##x;pmLSis*{DmXzJ?Sukwl$R)p%%0?ydg8fi+y~uVO0a%tFnzsIn}PGhKe<*qbuM zQ?}8ZSq-gtPskFsN@ZDvWO#3L+H*&!_b*}N{UEM_Zm-(tYE50`ZQ<#jQGO=ktjf8c zV_k-&HGDISY>yK5#m=7P9PpcaclKY}$?sl(v^>3fpcKvziXUL(zK&=MKqg-AQj;E~ zmt_qEE2MiMD4l7v(H#BW2H%4(DtcqR>BaBm*__BCAd~KIa9Y%}S>fokUGSsVdrnq- zMzmI4Ib3EKKA{RBNo^{)PQKV?2r@~NR7{`ueOq}}A^ojQ3xfsO;Qu#4bjJD6@9a&! z$9s#Dz0TB`e5uN!>Jfd>rirB3+k3R}d^Nw-LBgn{&;A6(a2?3)`=Ce2ti;yd#{1jc zW|5HK;p&kZUKHS5lqI-N&zDl;vq{oMEziw8t(kX(|4c#iw5ZZIROK3}Wiy$D_kwJS zhhY&t-hNJj0cS?Emi5TenjOhjT&8+p3u}N`J}-vr6d50iRHMg2$VQn#S@`Qq0rKVk zoL_%4_~WZ@{%80Zd>|-qpcbdb?xDSIF^@6UfWMrze?ID|<*$Y3KPcn&FU9wO_Q#A7 z(Auq^DsOjdb`nn97G%>D7jpQ2hg*K0@7cGi^{ZH;SM%a4ZYaTr!nFeV)}QX#;jrqi zoe_NMD8q>*gQ@%E_Bg&3d)K4oj~WhGXR?h7%_up+KSzJ4iK(s}9UZQsD@U-9PZh}P z3H*!8fA42e)x!jFQ_yLlNP^C6F#hr##ArDeLGbxN4ddh&z&ky*`pfqI8;Q{7#sK)HT(`wG;`!5^HB zVMQ4p5-10%0Q|!lT;>!wp0dQIn{T`X$1kQXL18_koBAFxDxcXUM}WV78!TZtv&6Lb zck~C~S7HscCx+2Aos^%u7blNDArW!>r+6n?a$1D<+4FR1R@BF_i(>ifr{%>~!)Rrc zweibOK$_}sEtMV};ok6oaGcIkv~V%D*$ekku|6o~F7gNag&#fyd-~PGtg@)OA-kXc z_OZN~HGfA5`a=SBw8`r@R?r2hH0oK?Q}w3z-lW&z$9dPbTcnT#@<30;H+T zWvhMqiTDn8FK#)A4#I?JhB?~OR-^#(1J+*^nhhKzu@d4FKItx6fq7toUL;`tk2xR^23GaD+KG!%-y`Ul%@NoMn?gf37Rl8soU5~E-e=&X zi*#P?w(>(U`N&3tqVn#v0D4DPqdC^kl&dCzBSQWEZPrHT~C6 z``>{N#kuP{wk1TQDlXjLUQp6vot>2t-}#5MX58H4k8qCRo)2H$oE1#>Q^c7zcGXa< zI4Q{Nk13%Izrv!5G8Z4eG{|#M=jiy8(OdX`3ZtDui$uHqBY3{jI5C^lacC#7zzAIj?z)yj)Z2YgQlFhwGSC@R^C_JCgI%e8$^4DSEMLm47wpq$ zzZtGf_`dG+TP5&Q^tZmht78lcfeV14s7P~udf`W<+2h{}%lVRjVw#duf^U;AB0w5x zDF@d*Ep-MNi}5}DFBxmrthEQX`N)P8T&M^3b_zgIr9A}DLdz@ExaC#^+|B5H+)=__ zt3+M1?A&wU5!L^AYeBy2v~&d7r#lw*G9c%^-i?M24cHh`Thebub-v)hdQ_qeO4B~@ zBnos-m=T1wE>GQX??C)=D?1NURH5|Xo|?7(hZ{sfo_wZZ4>ev-mF04}%*L@!U8R|L-7pq*HWvvu^D zTu)oN2H!*O`eQDB2p{_SYR%nvC^vo|YkX=k&3IlZL7;s$GM~`$c3v6!Ja-^9GcNVd zbjl(tPp}0mip+}bke||e?g&fie?RTQz388`BmF(-Htt! zBpI|U%7!fE#(^^MWK}O{f=UUnp&`EAPuO^SjxmTZ= zWJ@Xu=)bRD>c1|-nUhKz<&+L9_hfe1x6!c!vTKYdU-7eH{S&k*%PLb;_JBl!t%Van zXqyZ!rW9HGIe)wN!hqb$vd{jQG%~V^s&eD+HCER(Ro}Cy+Sy#aq{#t@Erz+PH&Pzw zgIUhm`KK#Lq7%Z8z~hBOEqHv-J(i7R-d0q{7n22i+Juvm)qoXS2q=G zpsQ2p2GmvsZ_mgo?64n_N48KmVt8j`*|GL>A4c~W ziShWWmg6HccFq;E!w=q>_S#-XU~Xv$!LOM$(LdW?9_#|scTGevww|0cdWRGq`LrS& zqT^+j|4dOa`e6o9j1)|qG-JgWju?6khtzz!P6qpS-~0k}L5hIW=)F+t=Pa^y`a5Lg z6U!=Qm|;YFITeJBY#Z0lEjv9exo$ZH&Pe8IgDP5V|2XEuQY^wA+1~~k?_P^uc?|EL z7kyO5!Y);mS_MmZtnvI|#xNcWCwp{Qn{l`Y#3$c#-tyAq4_Z0s0W2RtF7+NQB6G6B z-1Q`*(gGuO^6y#fhbFh3$~}a!)o*JcE{oHs|#L) zKmsFxUD`|$rlqLCRK}L?}Ah=n6g^TBR2O-7w z;*c6Av7=*P2*O>2j;!`s+gN_IvkM6R(=%EL5mil7@nnS&=UXP8a}({T3bMd5CRIM6 zN3%R^g5z7wzp(o3OnhVtp#R|<`^SS7WbeCVNT6R0(;=`7cOJVCl>(2)i&8-f z=9~U4Z<<+M2O4VOw1zrliw_9|et$88;hZbfzoUvH4IJg>^rcGb& z-bu1VAgWifSKU|0LcKZnAN#c-y%DI*)_$Mhd$d3rw7$OYNZ{wRivPA*sEDM{!6XS5 z{jXi9+m7pH_LYeX>J}ZSJhDJM=a_gfgNDBhzpKdGQ93Vu;|9zMUYe!1@Uxwl;LDHq zU5VIvyeAeIao{6iPu{Vp%JoCuuC3WC4_oLVjG^pp9o=0i5Yjv@f0}x3FxrERF6;;@ z2s*yIxA;{O4(4^APgN6x72R??Iu|?%h6#>W3ZzKWjh-zJzH2iLdxx^{ zC)J!Lvf?Bz< zH@Rsmwe8F>KYnTsyzwKj}e#9J$GCI%PX^1%!2%eS8$JKS(Q2f&jS?DUb6qSry7=Uclv6;HsFJt6<%N(WL|W2gCa*!!|@cv!O0L! z@ZefO&>_K1aKbseCT+P9E40yM=mK%MwXHAmh|S}`S0oJ=-+4h~>`9`KU3gP0Y|PDcTp2&18r=zTBDjpKh6L{Wv#+1@NzGPjN;C9n8+1#W&)=3NwzhC{ zmI%4d(Rl?PYNel=N>c0VFLr8?NHNqpuGRc)&Wa!-4b-_Jw>B_2lzlx(3SELWpp9EK zlsw_x>zUh_k?V$fg4nUfqUu)DY_|k(0T+$ROwe33i2;Iz$6LMPcOpOv^@Q<#fLH_E zWh)4J7xI3*S5zJWxv@)o=o|Vuf2MM@R#RnyX|KF0)Ab(8K71US0IZHpRa^ZvEq{AY_v@d(p!g zZ}dEKmxg+p|4sbdk|3w)NJI)VbVh~Ny$lMhq1WZ6F=u5wYw^{d@LZq1{x1AuUAPB? zMAzd5pq=(q;6H{op3!WF6@OxXWbc!ROHk~l+4&nL zEo>(hzDKnwg^Ul-eFr9#a1tk#lN_t5K(4MbouzsISs$83Zx&A6s4zy=Os*m9udSCX zZvhcCA+|uM;Uqpvc6k>=RQ#|`nul(MUxlue--P3u!p#1X(T=OJ3de1YT#JD=)v{6t z<@U`E#(>mx9OjFI{c~@b-zw*CNLTFJ#@!yBHZ0GqHI2iKG(D(nAytH9m6Y}vN!f_q z=st02ozbI?EEps1UeLNqqub z?QlcpiZIWLaj{m@SH#*TZQukXhaK@Ikq2w(J_~^~w@>G6<34^#*9#-E~jypWH{TxR_A)EO``3ohKmb zZI&U~CM#{Ni%T1FceQKcCHZ0+VL(2JzRVoU!FXv?)99)Fe5}-Ij3)}%T8|B;BbuJt zJBKmb=XM5lJ)kp9Tq9(jW1nwLHnhl>wEp;7Scl6@rso?m_(>Z3Y zh*;_cJ2!^j)$G0KXNhLNCJ4)ET1af147JolgRigC61Vv6eG(%#gf@9ks;#39b(kTi zKyUi}GJYumA-?@(7RgJyJlDO+jwc(;gt|vQA5uB+trNTJ=3kFKj{T;ckPB<7Dg8D) z&oUQdi@`S4a4%zNBx3mb)OLk46$Q!JtlpySd)5u-!9l{Iq^)rsb)Jkps>DouSyMyz z=s!gGLF}4feTs3Nk)%E2GvGO}E%m^$-uzvR#;R)=s8p`IXw9|WR?4g`{;d<14IOC? zBktI<^uf0+sxsC_je9rBZOfzs6Kocyz5?32{XK4B!uD{4Kv6dwA6ame^6+q!F?QDL z4=4AeebP#Q#y_fg0<(IW)^}dDn(y`z+~eR>*Yj*Q6}dEM zALQfs5N#^-LezEMh^aRlU3%xoHoquBZ#0XmFqdMj?zL4V5E0RqduwAy>}dpc+*9|( zgd<@Kl9nU#ld>W+Lm?lXUm_T}1oKOyCGg|%yC*TBAZXb6Q;W&hz$>R5STK%OE3SV7 zO}Wgn$#_hLVGBD|3H;SxOnR$Lp$(;@uxymmBx3`u?qhO2`KngmcQbJ%2fUbfo+bNf z9DOX=&TXW8a_`7DDB4oxFgZE)ndCXmkq=G4_{0gB->Pz=y{cSlnYlI_J9f*53b99?JgcEB`_u5Rl}x%FZY z))S=LKW5poS*tW&pZPufbm|4N?{OTWC_k?lZ-ngP^N~09P3w-r(fw*&6h|1@P1!;9 z2HTGMTm4c*t_L}$TJy;{$3wNjv)CZ{2V1eogOX9lWN- z^EqXl@WWJGA*?SYr#32>1h}1&Hys~3;(i}XZ>tm+MNDLSe!sOfL@%5wne@oU|p{0h&ME$MXXQI2DrMi1-kTgX|{#CORVui zTqNBK286ZM$0j92FX}>0$~F+4eh zN{`D_(fW77e3U5>A%?gEc~gh&2zr*papoqeq4mE*r4@x=KoVo_670iH z+*l#er5#NPfyE0O;nRSvFYsFjJ>Cal{U%%Zc4EB3Wh-&7;?ieCLVN`ANPR?v*R$+* z7X6IKgEEIUQc>FL;9v7eU1;tpDN#zU8TL8E2k0FQtSkJ@Msw%xVT-E!@E0-jSK~#L zs0HFPCeh2vUA{N$lh~a)HR5Y9899_ZN_?BG7aI;j4^2_H4Y)wZNqO=Q#^PVmcIvD~ zV|oXKVg3qYsxx4WXTP6Oc*3C7@vI{W^Cgm-|Mauj^y4X@&?^Df-*FI3&d;DN zXQJZ2lkIvi_wEcf`xhak7dfWsoZPe7hbA$uf9}VOowzntbMcae$am}&SM0Ed)I0>; zQZ0C+r#QEZ_EvzbG5ofKgj+chQUxYR4URO53JKcU+j%ki1AWj+O*}GIFpIPO6&Gn{ z>3g{rRhq>*5Jz(Ve)u++lNa`F;kC2LbcJ~rJOU#|h$u=nr4Gf+T0s?7%zm!o$H>Cd z#wDglW9$P_*&lL?DE*p|HH_Pb=p|73C6 zo|(~cD6=(%YO>RDJ@4?Pb&AS$s;kv-=C|uU>v4{xi1~-3&)4(cX55utd+d5S_KJO6 z*uq;b*}AMfo=14=*;q*8WkK4d57SJ2MBhc{9bU6mSy*-U%3%yE`J5_V_)brM?U$7m zVfXxr;gS_?O@-X+Ki+UTKb)JTypox@qUT9Wchk?6$ki$3h3fsouEM4Jqm1T6yevl| z#1hW~scEqfWKm}FfhlF|pa8U|jf(7R^A7gQN^Mh8=awq^zLJ=b$mZvI_}5MAcdfJX zOnq2|)iZMK-k(@F2g#;zc<`}DpQ*hGY&qHH6kpp^b7tLWgJL2CvO$K`v!Mlv!~xZ3 zSWOO4YBqEtZ(SOTXWyFz>1zSkzcSEsBN|PNno;K_FSk18tQCEUh1hXgM z&;_{(t6@=;gaa9 zyA+AkNh-;1YLgH=ieD41H@C0-!%mti?ZSsb^r&PpJrRS)cwpL~%(Om5Jgdeso65qZ zri7DylL;RY2gLW+_jEXN<01%Ztc_4cQE8pCYrff_#vF8Y#NvV}*0MTGZ}N?&?h?IX z{NnMa=6jHAm-|iN_v9pE`^ga&)ug>aYv)#LW0G%SWZtH2@OZy0W6tpFnVex+XHXBA zVLLZTJKKLBF0$JsQlK_TLvk}V3pggnuqV2aQG^Q9Vf}t_b%S^`8+$e1`g)s>nyfC< z4i*a4l*0%6B_27SyAIwcgGu^r)8?epVea5at<6^5QXf}R`Q!}DqM^B z7M9@=o8dRXoHEAeJKrPh6cpAX``2JYToX)-B#H>v|S zvq&fj`kVGBt+gx6BOUx+pwLCjBpwLvUp;(S@x7vQjv{{O29NVR$Or!Ec>O9LGHDx+ zFV%_NpFtsu?c7XV^?k|9Y~W4U?mGB(WdrF-d`~imrgEpK%VeK6%f21JxE+f&I>5vI zlh_xieza0@?CGrqFQ7*kA-aKp8gYgTHi*-(`be3L1vZ3_#c5`@2+E{Qg6A2#k9e7_ z)m-f#Y$3IUtG>iF`BJbgaeX!KT~fOyN|V-yIhXI_6vC;dVtXl;YD%kVv03R_x^7ck zr)a*j>~@>Qd!EH=wv3ebPLxN-*t47)N2?=`mRCHi6AgUKZda;Y>Zxrz^S0ESj^94m z&lQj5W-UacKIfMaF7@wir-X3&>CY=kNftRcIS$zUencCnQP=ol1i_ztisU! zZyzF-1hctC0~+k5!x+wH*gC4UOfFUFgmmb6x9A34gx+57F}g??5cjjGZ9T2m3GMJ2hNrd<)f&&CrL?{(-(n-6S)@{?RcxMf@jLETi$9B$xl#QVwQ&WFNoy!Wo;+g2)xH=+)A2s|&pABl^pAK2w$ zKpovEre(=QNO2>-t~PdPAg}PYLB5?CCvPWhiyzV}TQS&Cjkho0XV;AW*OL zqp3t!qEsT+elL8NS1bPzgG?h_i1pj4OS?EQvw?uMMoi0G|PLsVtLXg znfeZoTdA}D1d~tXYZoPZJiV^u!j-^}$cO{+1iBfyCRJ(}}ky{uWimrhS_I)mW*?V{yihsyg0? zR&%q)bU?GX0;#Dmt!*FnY=-W_5DQ7zmhsWbAD9Yg#K$)ropzk>L3#x8%gp9}SC|A{ z88mBFFmcD`PyOiv`iCp!;UbciY3cko2{r{0)V-($_3)j1WueCWuBPEQhA>em!Kwxn z=zFqW9x#E}DZKD+PjeRF|4J7XN{;sib>?&7_1?^GBnifDBoU#jB)8fsf-;_V2%V%B zCHYJ*w?*;G@ped}9(AdosL$G9<;z$4#kEH1qzOF|5wRA_7Cl467x2oY;0>B&qC{j< z7*gTkL=B`a>#4^ zQdJIU9H(Tj5zCiz7MK;bFEXmg>cE_|SxgK-tPJ}DGc&0%CoBVmZK5BQ|Mi?o@PTe5 z5E&)xYETt^gi0#IQcgnW2jb&@nWXrI4-)@5e7lhLobp}wrY{PDjmx3Rlh@XM8Qs{P z3sz!vQY@91R)bMFQcA`r>wke9{Dn7F?q}htSy`utHwTySV1f9&>v!U}!(+S`LVAuD z$Pb*6jpwTfMZ#4*q%*%j7S4Dv7i}Sdv97-j(3=JV{+GH&cL7lJMbY4PyH$_{vE~52 zIPKBBgbhww{lAN&d_8-Sx@q53b=phl;IF*A=^TUuD24~1p zjPC}DHU0#v`Ql(NL4d!L-(eL>$$by=nOjWiotBY1U5W<3)%RhZPv+$)!+C|C`Qmk z?aoomTawG_owB@Xq+0hO_)wasvQuD2AKNl(t^=WOzqkAaNUibcfqAbXSEm8KuYzMq zCC4ljdhs-RHNMw;&)n^8->a#zhxb38bIiG|1dG1FSW|nf7i{ts`g#Se3I;Hi4M5)U zIn&P+r`!X`5H$QJVIUPPIx9%&N834`p0(UZzFi&#q8>9z-PS&^+FySY5cx~&E5Kgz za3RV%c}%bFK7k6GS}xL169BN>w_aW6^{WH8!*j%m-dEv)#-*GBZ3I*`)l{0M*d{cy z+%juXS@Pv^2Q_#03$qA4f8*5;e+>+ZMlArfKvt2}#kcy*qb;jo&sWOu_`l7{V7nLB zo&KuTw)N)4KSXDO9i_pQ03W}9ww5tWl_$qFp1YU26S^na=x^kLA}4i91y4U$m;$@4uQK57*Fx$ z#rW@pK}gej=1FCI&a0n!CDew7pJEJ4H^JY1*N6%Y*uT#qF!pNW$?-M6mnAmI|2ur} z2Y^DNrvNCwtUqZWuUUqb5#Wq)5TcT`Ur1=qDq^OrGp2@Flqljg-o+MxWD0j~w|EhG z&$^`EbY>DY`rBW=A$quc$>q}TEViN+G_H6fKNoys{ph6)rRf!eKh@V&N*^^U?tVfa z9f9gtZy^y>V{icEu_(-ab6=!r?B&*x01@%pdx9FowPzS>7CBPZM{HgAH%L-y#KHhb z3J-a6waa1a9R0T2hwuSXgR$bG-ydmN6Ioo1v%4D`6i9AUbH?KujKiZE56T%?=e(->WD8L{;ssq<`*7FcbLTuB10qeA>*Q&$;=_MB8tmVkc41FRqCNkc>P> z{0Tqf-q9fE-(xOy@&aGmwnukiR&;)}k2p9|o5Vx_~;@;Ps2fJ2&+owZC}8U*2)C&KV`q1eyVY2O^BQ1!CcEEZAEzE+$7=iS zy>B-%-IG`b>WFui7+*GUN=FkQ^KD`uPh-jaHufvf5Bo8&kgb;v&8qp*Q zKe_@cpulnO4do?i4j0IpN7<;kK0n*bZw}JxDhDHX8swk$IrKQ~D#Nu10pC(qlaQ{5 zo3WFpHg;K%Y}_E5eE`%W6u~2D`<#S-p;lkIku}if6v&Ox+B5k8HC%2)iN71P3?H!k zDCz($(_%+-?XYEBv}VYruTv+siJ0ee2YlfkIOGxli^;dhx5~Exp}j+0hI?%27!XdP zEkNgnI~ow0oqWL|b63IPM~9efTH`a9aM)2g0RP$mq$d%&{iq#05v-)(B|B8{dpSqblhri+2Q>=XH5Eufq)tC(J*+S zfa@qDDP_m+gU3f@Lq5v}ZG3vBrP@k-kENDb#hWXQJ%D|E6|kr2x*c$_Lru8_+}4)nR{Ap-tOyud(NEYL{u)>GZjb3Z?D070R^9`#Lku4ViJ{i|Qt`Dj)(i zq~y};QnXS^qO3*UKGNo7IHm?Ui4O;lY3mjtO@Apge!VNJjp^Yj(nMC?N;~?5Qn1qw zi&Qf#h3<3%!0|BMQBKp~)yet;&1HlB4t>lFzfKXOCxS6lI2qW~OE& z(NLD-!dSr!(~v*dYn2UPO_PNXk^zHWCDij}D6LLt(8-a}*BW})=B>+? zNyi(<8k}@hZ={Wy;bPU&7@Ss{B9p}s!97+;mV2iknwk^$!`HWy4*1BHUVV4k z#(B}jg?Lq8a#5sHc)oqi2y5N<)n*>@etIE*Rv#?IGIJc?6Xl+bey8bx%je9_QJ!{Z zDni*Yux>nfF!TugLOeWg=F&h#HEhhp@E|oW_ev9qF+|jhiG?wK=m7j~o!ZXugzhdR z5jDPTY}|8S{mdF@C`R^C^kFQ7F~P2?ru|^_oSVp9L2He;A8%{l|A^tL){baI0f@#3Myw;O(00r zgu>nM0A>g7uC@->Y8e{R_9em7q;uR!Ic_dkO58jifEe_N&x*jN=hk#Sb3rfgZX32qpC{> ztl2>Y`m@?_M09l)?V`(0r@)i(b8Dv9bBwMcJkZKKdz^O03-NwBd)$#yg)Pw9v-PX0 z(uOUoP}Q2CZwx!NR~P&i=MLp08-QIE+pbn3i&P<-0f6l`zahXdI<^C~;y9}eCyL&{%j_QIGwpi%+y`%zbpvqlCd8y+ z4b_v7)eX3o2(@nS>>=46rPJ;$6oGpHyoaybrc*i_0%ifc@d+gy%QukuGaBY~`2uzc z(=k6&;ENi1d(ja9)S<=5d=c+ag`A5c1D^uXldMz5x?4q`jT;vAk;;4ro(IV&7fvcm58>yL2>~(cw0R#C^cJ^2IF;XbZr8 z70~AA6ioFMMh2Yg0owAyi3Ul7Olw0q9?m>!Ss%1$O5@IJn8~5Mf}jP6=_{61#q%y_ ztwzf0kXteIwWEpO4c~zF2jG;O2#o`Qw2PN3>)yIQSw@GQhybe7$&DK3y+^kjXDC}$ zZpiAcmpS3&PC}21xg?F`%~UVQ!FZu?SHQ*|t-^LMY|%ZS5dQB}$t%^UMpk*D++V-? z>EgxzZha^D!15)`;j-Debveg!8c)7=|LvLT;;4n(;97`>)Hlem`^;}WZp{`?B#Kdv8A&jtR^3X z(16!u?I~8TL49Pjc0-&%W%Ip1up`R(4(Ude`fpXJwAbFHKffFE)Cd!R0$FX;|MRuJ z@BX>X0PX?+UW+EH=2unZ5??P*!TE>tyLVxgJwjJ{4QS%QXTu9DUfSkUR{yVM9Vndc zM!x46eYz5$XrsSvvgZvxt(-9GS9HH!3{@#GX(;&He{dOd5J;U6^Djq#J)Bn|b#(cF z#E>ZJG*xpId*{{jeU|*kjtEe%hB{qTiNAM;0u&lP=fRNG8j@)wM0#-igj$i0wow$pxJBb@oqO06b_+q6K&0+j-=m+b%g7icx8VZ?9G1eC>o-aHe=2jDyy4q%{t<1vA)A!j zRLX67sxOeknTBgEwjg{z9wzZx!4{i9mOECHEW}GxayK$ClWPhYS(mDzn6<*-=Tki0S9sNOQ z7C4lb87x6FK|0cxd{AAe?sGFV6y$os6rQka+Uqz~1T1FR?3}XX;_>3-lJOD{WxJf* z!+*`=eaY&RUhM7H+7GO=^wh@GKrwn-H^@l^-VJg}k+@~fjoiUAgZM#~o?kgmn!Wf1 zOko{(%$gZc5s0x>o4(c_q3P&?@sO(MdE&f;=mlcgslH~D2Ya-Q#+_Y{sT_={Iz3rO zV=N@f27XuIbRZ8y06HP#H8m0jNmsQ6I0dP_17~@3dkLKUMY)xe*j6>Fylz;l9wlUl zW>f)_ZT^fms>3Bay^QHjlNYEk)y@0l1RPw-WCiexXz&og104Q!_;K1n{ACf%e;~{7 z1n5lhmPf{j`*h#Ni+35(m2Wz~@!A#rcXp8YAA!oeV2P+ctZwh~K$rCv1k1ITZIOq|Vba@C&9(Ml^Cga1D{2x+R8@1V zjgQ;|CvSsg@>7hRV-i^&>eRl(yeR0}ahC^wDVCSZ#ad~`H8FJm4p1?pDDty0 z=Oc4X_3JTw=2c_x@DNfMbV!$x*{UtHkNe4acR2HN?ci8q4C3*ly@YrHn|tEu!37nK z-wWSMlhKqlvcAaMUv9nMRvw(OCaSu%1`;eWS^{Z_Ypb>%A~iE0ZYmFMvX{tLq9!yj>?+i&E)J=@ z^|S{YxsxXny9_ca8FjTLaUlbm(&*6Za3Q*YD;9Y{evHfFBmH*bmLYL_mwzt9hG-S) zY{riYa+}NGICm2%R`X^i9A9m@QbaSO?>)w!D)87051@Ztx-@KlrolH?^$qT?WCJB& zKEZ5d%u?L;4rxMNGzjRGBQa0+Ec|K7&ULM9q*S83^HqZ+b=W<+IZb2uP8Ep;jN4sUdq2g~^@vrHD|s^fKmA03J$rg<)G2 zNz&w_5&XFPaeIugzO8Wyzz|)cwTl~xDPd?LT*N3C;VW9^P)-`<{RMA_z%BDh%k&9f zI@oqgzYyrVIx^m{wdgN9c{yxt>6r{!ihYkfd3LV;vm1W}PZxensi_a6@VOFnxm;vO zsX_9}_?XO?Gg$y(M7x(&gB}JOR{PM`Vwb=F-X)Vqrf=uYm73fY-zc!WjmcC;^`*2% z(?=*~bZPKqP@<96z`_b9L)XA%e@g;8c(7lcS(K1J)Bs*>wxPY-yfdL@rNt562p@V8 zg~&tsGDCJUmiJs#AsYYqBH}ClKqK)>GoFb!Rq<8g%QQoD8PKw=3q?c=)y_9Wb-nCU8^f$^`VLT zf}=9Y5OS&H5_eLiuVd*2YEhYLJcL)8FQbSKqWUBa z8^lS)qR+Y2Q9hCU`UH42;W6bu;tmw*i|q@tRm<0i zHcx;nJCyCxELRLu(9DJ*m69E5dU^8!&{T$jk7O9THcFMf)TOxMdi)$e} za1`1erb1KPl$wH}TLHx3`@FK?xX?q`hxLbmBq`X5zf|vADnq6iu7SE%tsgI~H$^ru z=2+|7`I!YT0E>nz>UstFJ)H#9#!0tikyVlXtQ&1LdBVF^Lyj8@gc_#pu}XdD9A_-chT0)8r>mN(Q@}#|)ih2)fi9#jLOZJCIzgP65^Vbj!9LNj-|UBy7248Nn-!|G7<@fQ^dr zl{Nds#6)=BgEWwj_5ductSxTsP(y=QrJy2Z5e+P6Bmt34RTneICokT?>LXrl{FY1I z-SneRy$;|aI6sphd zT$WQ$`1N6TZxPW8!PRzJhma|BApx=>y_7`ph(VJOcZqO2%fv?mb0`;P6pr%DXY|c2 zV+i|>IV+k-5e+oyo1SN`p%6==%)SxN0rhNVON-enE zB&((*r;gK-!}bJ{hZC#0W>#$G*@TD?kpdE#FQI`xr;_9rTon*r#d7Ij-StsEF10}xGimP;t1GJd=4NHb@MW?d4hbwfue2!0<8*bwTYOn!*LrWQdtlsJq7yO(@vPCTXQMjQV=JTM zkSJ{=UQFZ8LV^Oy8=aD!13qz~HuGoXTXEf*+{eLxad^08{@-!$MG}|2YgZWj&V^1# z+sYV z?|uvuI3+$6+g%l7-*-u{jyxKb*@lc^O5+wF+B(LR!2?(xr@cy*#uM=YRFo}>M)Ufu z;j4{k1Y-{x(9-Dm)0XT)`|kEeRV-VG1fb%d;_9|v03(z#+9BGz*jwekMyK1vQ`)0)kLMw2oYOOx*)bw+_CFEQ40{Kg1PV6EDZS6W=Qp61Yur8;eo!+}-3&aL!Q0=-A2r}tJn;X$ z`cTwi>oEfJ6pEptfq5^-5B&lO_p+zQbX!+r;_PKa;?Hq; zGliiCzPczFJc}>jb-arfBql&cxVk z6@aXaeR{hr0TgG}enBN|s$cuf+-WC1_Q1kfl-V^7oN(@t7e*|Z!xpaVU3bb>cPrD+*i`^fI1i2)@Q^X#U7)bIFHCFxyAAQ5Ue z++L88Aj6XMbMC8&5C3L4i-Rzj`bM>JcvQok=ZYmDNscove=`>!VF=y_SB?liYCoMa zR=qvU-(Ws#zp@`qJB*NWEYgm`FDOCNanPq21?SQmBH_xS3>A*q?`i6R4(vYHtfm_} zWro-ebz!paO6vQwZnoVgwQ1<_TG+hNp>Z(hB$Zwop-XJNs-J6Ig%dNOp^(R5;ben} zS7EQzP)KF6+>c+x3X{!(w#dYKZGtS>x6~}Bf#u>LJ9p$C9lGV>fWuj($<*8@YMdea z?n)6tt(Rpd4+`Q15~#%R8W8yt)mJ=x~Pc2cVNjY{E^$j+EpC(#IDOo(G zjERK@_~$h4iin&$i&GRin#`)+b|<(^GdZ^>C=Ya+#~RlB^Nxx82MO>`rrsH6NNxJ( zpD6T?a>i@qZyZZ>;(x$=z5kNAgInRsS^ryzIg>Fw`+vYZznSq^|5wBeh62w0rx{)O z-y@S=j79{ri%f?8TW4?nHy_{+O@r4ZGu$E2wB!HN1k(T4x4?+3w*LN~e{$#xI{Z&O z)-a&zo;Vqw>|Z*sfhn=PyK6py8pJP{xv<_ z|5XY$!GCH}HVD!+JT>XsIvK75h9szToc?9UH&ZPgz;ENG=cR&Q78s|OoyI=s^E4GK zfZ=M9KH3|Sn{tw5+!zpi5N#adce3w6YC@u{f1^;X7#R@+V-~@Ph1#^QAU%<0me*>V z12MJrHJ`uyk2cg{pYhWQ;|Ro$uf=3mG>%_lO>~c(iI{kV??5;I;3{GKd=8dZdmMb#Pyj1GMY4!3K+u-xBMu9M+5IJ%S0 zMc;SY>o?F!hNko%^gAc>w9(urk3TTMO=zJO>b-o8Tqd=wwwUX%*SM4Ar;T}sSWr-G zSq$dakW++O>oaZblFJtUXAEPBl>zv@klwz>+M6b|_qHXlpxU#7jV$*>eG6CHcTFl1 z!bAijU9CSXkIMwB&Du0MDbX2HI)Us_w}{T(f7;?x`S1}Xu#8;!*9k@PS4iP8?N8)n zRj(+4N_Uxotf?dnAPnsh% zSLnw_?wH}89QDS=sw;eTt-UR}AA4Jy&j+kcqfiVSLD-e9NlD z5%)T6olVNnms>^gehVv~{J@CT?p@}t)Wz&9WrQq*yt~EwPFbPmX0w`9Rd+)viV;(} z<~LwlBWu6NgR1ISBeaPp5%)nh+b&}G2u^iUfM#KzPx#Ia9T8u5Q9ez+t{P>+F#Y?n zirp5~fH1(YDDcgFw0+}>k@f;+B1}`F{^GjawPGGIZm*j@kv44T=>)9{Br6^a{%>yI+{AS)EZZUfEH0Q0ltD>;|QXZfu~?n*nRq{(&Mt+*f;W_ zSU+-EMRas+8lcOTj!o3lg@hPDgeUkXCP@fYO_O1nPJuAPI7?QTGSYY*?h)tne_A{D zsI;!^k2{k!?dq?!{-$Z0j5hL{l}XarhhthmQEMkL9+64BrjC(-yu|=Zg6(t#HYJorL-npN2_b^Ho?QE& zR6~F+RkNPsFt9psbo5C=WYIp4*XIUZe8LcV9TT{2hza}{lYfz>KOfJy04OzCBxreQ zBe!jnW96g-PHGtb_Dt_%rgm<6^)lt~<`DwzvStrgOlc#V516xigJ0U}&;|P_!ucz* zQpaH<=J@?t?$uj@g=S$u9Cb7}mKfCA!5aJPjI_{SCpd7UU!_X-oQ-*%l-Tah<(Lz~ z7oUszWnc}98D1dx2?bLgQ!~f)qvYY2_e2W6PRS~O%jG3n4^Zwd%o9|++HM|LT#=57 z96XIw^S&Omphtaw4PcEG=bD-X9ng?5zq_PM@Eh;Xn@NN4+vV#j%Fyx1Ys3kUJ7zZ* zsj9Y`Okbobc}Rjq#JuOU=KH61sx%rJVP<#b$SgWzKO4hw{{r5?kxx^lOKjP3LbCU# zCq?h;=bgN;?C~c4p$B;pq1uaEr(t?H%)|s2ND8^_euS(nN&Kisq+hIN#|mS1dV<>d zh{1c*e*t9YhXibP-ktAMn$8G~rcHRkQrLD?#y1t}+s+)@M-l082fNLA&A`OaY|7fu zq|w=`}>2u+RH(of}?cX8RF(o$&Q-Y~XIlTUGj z_4Wfg%?s`|qnwbLa|HQ{TkAJKU~>xiaJH;>K>dNrC*z^RPja1#D?`ga_1-Kj=JLY6 z-t_y)L!VC=!E&$adLrsXLdn;i7B4Ss4o_A}K^HJKzk)0y036nc{VthpF{myym9lEl zjkNHLSy9d=1D=wNkgV(mlskRzyua$spnD>0ZIO`TgnIjYp4=ExbDZ@2WOm+3t_>>N z3a*3SA%nB9?|66_6KcOU1l0<5b%?#1jZ$}t=fU# zllaCdsnrLo_C zZLh6O-Ebp=qpO|*CS4yhG-b#Qj<+j+KrP!setS`yH%^|I>Z8v;ug4!s>^a6T#R^N# zgvLpbFCb|WD3_n)_)0_UrNIXR*k9M)?*Lui^=5UpVd7Z%Ad}Z592n}4Y{kzsUrCG1 z-qkk&uki3$9_w>u=DMg)hGzamjpGd1I$Fzw7dl{$!x2Qy(GrhH(S54*2XOrodod(c z1|K&|byulUVR2_6zs&0ECy8I=gJR@WRsr7M1+leBGpXaw0VamryI)m|_}nP7S?gT% z=`Mnj;hLH=u8qOc?b?aUOEZk4^0;fPrzfuj2OOs5nRLRT8bvFWYcuD}szj@zL+#+xzbF2Hm`%@Wcl*rYK(%(fAv^1! zabE@KkabrXCRg-JhA|mP|}e3&F-&&fc8pH#Y4e zrr-|{xuXne!|we=_N!^>*j2rmq^D$ZdjT6+2=A02oljV{US2q+H|;DZyIJ8D3sq;G zq*UaoD*Lirfu4-sPN>@!lmYcN+KoKeHN(n05@4S$Sw=gd+4Rb%l)c)d`xB#UK5phO z6!$lwgcI^gOki`E0c(@a6u&I-3Zwo6&W2~(&U}@MF719SoN$7(Ikf+Hi)0JhM-+fefECS~=nJWkm1AZV%>S7xOzx%0%k#o`S=aTZ0%5V-gR=ALBN!kt zV)j^a2hw!$_+rHU!VSO@074OKJ6l4yO7~F%Q8tzPE2j5dIOfW&#Uya4P6Lp%%zYo- zJuuJcq<2YBJ#DY|x8bViqCVWE-+DpOdew9_juVo6j5eqy`X89+HEa|$X6s0FZIGe_ zB;Sru>}Q^1J0N!2JH#A(DooLAGD?R??#~$vmtIeoWD?23g1QmLF#aIl4CFEnokx%d=D4&0xZX4;X{kdt*X%lK?7?mhD(-w`JSOLF`=x40oy+1z;O zU!G$J^ipM64R`l!_$z}76Y`ym6Y{Da4jb0TI8}|u$R%BOem060IScO^%eny~fHSu( z^j7J&9`BTjz53}b%aQ(gLdd&&WTMynF*vo(zJR+OP-g_kA?yo=H?tYU=v_tUs7kSg zkZIp~^@oy;{v@DOOIBy#ojoPBlISca+v&7ckN@|$2w=5qz5uEPF|)KNt5-wh?NyD7 z%E_t;8evvF>IqyR3*&oHo0ZDjl|n;A9gf{KoHDQ*3Em;lYf1^dtL#Ct`H1Si)l+pp zRJ`dG5Hp_`ZonPPFutq9_$H0Agdt8qnG=B@tpsPSBbi@38&6fGDm6#+1ZnUSOj_JJ(o$RX!fSG1nGMWCSYUeHA&;+Pg;$kZ16cK$&$e0;E3y|Lsv;kqTtJ z^hEsg(okT7xItGma_iJRY<96=j+wPSJA6^ML9zUk{qGxGh%$7Jk*M&#R772@my@8y zqDX#lx=&Uk=C^&#<>9k^bpjCA({@ERjb!Y_@tcE;qO^t_-CGp*<;X}uU^{&5t=A$b z?;oE91yeuHjSr3+b7a6W|4wL1Sl^2Thv@?%|0Tm&68mED%cj7XWNlII-d&XLM4bxi+MU)x)a?x$1pbxgS>-< zJUojToVYbN!BbvA3Mi4N7)0W`)^-v*yKnV2P;7JlY~Rg(sIdbH7K|bIOHAm#c#N?q z%egU3-<(N>JSeB)`g%DKh!Yt=Cbl5nPXqN?VmSWZ!70D99_XqfvFl==)Fwr8zo;i3 zy=CK=F{_jF`@_VSAWC+$26Ut%OYDWZhzoyz&&Vb7+5=9O>?Z>GC1p|0AFxxd$;TEj z^0E1nG`1%ng;|+SMg8=DrG`T(x@@;Jrx-Z8sHo(p!0q4sw4PJh*_7*l{&G``AI)@g z_vi*HL-{~f1^Jaz*+H#fxGhe?Gd-$&9$ynzgPMoz=5JpiDBJRk*^sIBsfUk-8WV9< zFCJfMzF6|H559=fw@fUrxbs;WPxC*V(S4xLne0#$mY$9kyq>d`Ap1TAxdIcp_Zmi@ z<{p2XIbh={g23jf;px>7lUHUf9V)+U(1`E@fw?Zp zc4PbvadI!D2;N*jlKQ3A#^m{uY>W>iWJewSjd|FH+fUS*seLw^M=aKxCrO(~(pEag zQOj!K6nK*9PNo4_sWnC(#>kXH#tR#`pyZndGsWpXzjH%W6L8CO-C}A5BMin`)d58bemzP#pidbE|yC}rDGm@w&X zWqL|I9o=}mRT+jMTC~=Tq|>NIJVBxMT`N{{BmgwQUBT&Q3oDH7ewoEOL1JjoK%-0h zP0lOq23z{5#DkTj1YKTt$WxI5Ce*S14?4sp9Q1Gt6(bC?TQIQcUt@ivmCLRL=$+tT z2O8fui&ro5tuGazZ)_Jyg2iGM=&%! zJ3%7cg0pLybCh3+gMkUY)gG$RbL>UpCl>85#1*fXjrLrwNACiwbKoLp#~cGqoVTBnyAL@UXtx091 PpV*U;`F77+AAa^v12>C0 diff --git a/TestMode2.PNG b/TestMode2.PNG deleted file mode 100644 index 861603614e2233d90fdf5495713d7f7ea8eedc5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87198 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 literal 60756 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 /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index f127cfd..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/settings.gradle b/settings.gradle index 48c039e..d94f73c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ pluginManagement { repositories { mavenLocal() gradlePluginPortal() - String frcYear = '2023' + String frcYear = '2024' File frcHome if (OperatingSystem.current().isWindows()) { String publicFolder = System.getenv('PUBLIC') @@ -25,3 +25,6 @@ pluginManagement { } } } + +Properties props = System.getProperties(); +props.setProperty("org.gradle.internal.native.headers.unresolved.dependencies.ignore", "true"); diff --git a/shuffleboard.json b/shuffleboard.json deleted file mode 100644 index f1a7b61..0000000 --- a/shuffleboard.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "tabPane": [ - { - "title": "SmartDashboard", - "autoPopulate": true, - "autoPopulatePrefix": "SmartDashboard/", - "widgetPane": { - "gridSize": 48.0, - "showGrid": true, - "hgap": 0.0, - "vgap": 0.0, - "titleType": 1, - "tiles": {} - } - }, - { - "title": "LiveWindow", - "autoPopulate": true, - "autoPopulatePrefix": "LiveWindow/", - "widgetPane": { - "gridSize": 48.0, - "showGrid": true, - "hgap": 0.0, - "vgap": 0.0, - "titleType": 1, - "tiles": {} - } - } - ], - "windowGeometry": { - "x": -8.0, - "y": -8.0, - "width": 1382.0, - "height": 784.0 - } -} diff --git a/src/main/cpp/commands/AutonomousCommands.cpp b/src/main/cpp/commands/AutonomousCommands.cpp index 7a197eb..8602b06 100644 --- a/src/main/cpp/commands/AutonomousCommands.cpp +++ b/src/main/cpp/commands/AutonomousCommands.cpp @@ -293,8 +293,8 @@ frc2::CommandPtr TrajectoryAuto::TrajectoryAutoCommandFactory(DriveSubsystem *co trajectory, [drive]() -> frc::Pose2d { return drive->GetPose(); }, drive->kDriveKinematics, - frc2::PIDController{0.6, 0, 0}, - frc2::PIDController{0.6, 0, 0}, + frc::PIDController{0.6, 0, 0}, + frc::PIDController{0.6, 0, 0}, frc::ProfiledPIDController{pidf::kDriveThetaP, pidf::kDriveThetaI, pidf::kDriveThetaD, frc::TrapezoidProfile::Constraints{pidf::kDriveThetaMaxVelocity, pidf::kDriveThetaMaxAcceleration}}, // [drive]() -> frc::Rotation2d "desiredRotation" [drive](std::array states) -> void diff --git a/src/main/cpp/infrastructure/SparkMax.cpp b/src/main/cpp/infrastructure/SparkMax.cpp index e7ad4b1..223d687 100644 --- a/src/main/cpp/infrastructure/SparkMax.cpp +++ b/src/main/cpp/infrastructure/SparkMax.cpp @@ -9,8 +9,8 @@ #include #include #include -#include -#include +#include +#include #include #include @@ -132,12 +132,12 @@ namespace // Underlying REV object holders. std::unique_ptr motor_; std::unique_ptr encoder_; - std::unique_ptr controller_; + std::unique_ptr controller_; // REV object holders, only used when not using an external // ("alternate") encoder. - std::unique_ptr forward_; - std::unique_ptr reverse_; + std::unique_ptr forward_; + std::unique_ptr reverse_; // Shuffleboard UI elements, used by ShuffleboardCreate() and // ShuffleboardPeriodic() only. @@ -167,22 +167,22 @@ namespace std::string updateConfigReporting_; // Config parameters which are not individually settable. - double outputRangeMin0_ = std::get(SparkMaxFactory::configDefaults.at("kOutputMin_0")); - double outputRangeMax0_ = std::get(SparkMaxFactory::configDefaults.at("kOutputMax_0")); - double outputRangeMin1_ = std::get(SparkMaxFactory::configDefaults.at("kOutputMin_1")); - double outputRangeMax1_ = std::get(SparkMaxFactory::configDefaults.at("kOutputMax_1")); - uint followerID_ = std::get(SparkMaxFactory::configDefaults.at("kFollowerID")); - uint followerConfig_ = std::get(SparkMaxFactory::configDefaults.at("kFollowerConfig")); - double currentChop_ = std::get(SparkMaxFactory::configDefaults.at("kCurrentChop")); - uint currentChopCycles_ = std::get(SparkMaxFactory::configDefaults.at("kCurrentChopCycles")); - uint smartCurrentStallLimit_ = std::get(SparkMaxFactory::configDefaults.at("kSmartCurrentStallLimit")); - uint smartCurrentFreeLimit_ = std::get(SparkMaxFactory::configDefaults.at("kSmartCurrentFreeLimit")); - uint smartCurrentConfig_ = std::get(SparkMaxFactory::configDefaults.at("kSmartCurrentConfig")); + double outputRangeMin0_ = std::get(SparkFactory::configDefaults.at("kOutputMin_0")); + double outputRangeMax0_ = std::get(SparkFactory::configDefaults.at("kOutputMax_0")); + double outputRangeMin1_ = std::get(SparkFactory::configDefaults.at("kOutputMin_1")); + double outputRangeMax1_ = std::get(SparkFactory::configDefaults.at("kOutputMax_1")); + uint followerID_ = std::get(SparkFactory::configDefaults.at("kFollowerID")); + uint followerConfig_ = std::get(SparkFactory::configDefaults.at("kFollowerConfig")); + double currentChop_ = std::get(SparkFactory::configDefaults.at("kCurrentChop")); + uint currentChopCycles_ = std::get(SparkFactory::configDefaults.at("kCurrentChopCycles")); + uint smartCurrentStallLimit_ = std::get(SparkFactory::configDefaults.at("kSmartCurrentStallLimit")); + uint smartCurrentFreeLimit_ = std::get(SparkFactory::configDefaults.at("kSmartCurrentFreeLimit")); + uint smartCurrentConfig_ = std::get(SparkFactory::configDefaults.at("kSmartCurrentConfig")); // Config parameters which are needed before SetConfig()/AddConfig(). - uint status0_ = std::get(SparkMaxFactory::configDefaults.at("kStatus0")); - uint status1_ = std::get(SparkMaxFactory::configDefaults.at("kStatus1")); - uint status2_ = std::get(SparkMaxFactory::configDefaults.at("kStatus2")); + uint status0_ = std::get(SparkFactory::configDefaults.at("kStatus0")); + uint status1_ = std::get(SparkFactory::configDefaults.at("kStatus1")); + uint status2_ = std::get(SparkFactory::configDefaults.at("kStatus2")); // The next two data members are used to implement pacing of the config // state machines. @@ -305,7 +305,7 @@ namespace } } -void SparkMaxFactory::ConfigIndex() noexcept +void SparkFactory::ConfigIndex() noexcept { for (const auto &elem : configDefaults) { @@ -317,7 +317,7 @@ void SparkMaxFactory::ConfigIndex() noexcept } } -std::unique_ptr SparkMaxFactory::CreateSparkMax(const std::string_view name, const int canId, const bool inverted, const int encoderCounts) noexcept +std::unique_ptr SparkFactory::CreateSparkMax(const std::string_view name, const int canId, const bool inverted, const int encoderCounts) noexcept { return std::make_unique(name, canId, inverted, encoderCounts); } @@ -341,8 +341,8 @@ void SparkMax::SetConfig(const ConfigMap config) noexcept // Ensure at least these two config parameters are managed. config_.clear(); - config_["Firmware Version"] = std::get(SparkMaxFactory::configDefaults.at("Firmware Version")); - config_["kIdleMode"] = std::get(SparkMaxFactory::configDefaults.at("kIdleMode")); + config_["Firmware Version"] = std::get(SparkFactory::configDefaults.at("Firmware Version")); + config_["kIdleMode"] = std::get(SparkFactory::configDefaults.at("kIdleMode")); // The order matters here, hence the somewhat convoluted logic. tmp.merge(config_); @@ -691,7 +691,7 @@ void SparkMax::ConfigPeriodic() noexcept { DoSafely("motor_", [&]() -> void { - motor_ = std::make_unique(canId_, rev::CANSparkMaxLowLevel::MotorType::kBrushless); + motor_ = std::make_unique(canId_, rev::CANSparkLowLevel::MotorType::kBrushless); if (!motor_) { ++throws_; @@ -736,7 +736,7 @@ void SparkMax::ConfigPeriodic() noexcept { if (encoderCounts_ == 0) { - encoder_ = std::make_unique(motor_->GetEncoder()); + encoder_ = std::make_unique(motor_->GetEncoder(rev::SparkRelativeEncoder::Type::kHallSensor, 42)); } else { @@ -756,7 +756,7 @@ void SparkMax::ConfigPeriodic() noexcept { DoSafely("controller_", [&]() -> void { - controller_ = std::make_unique(motor_->GetPIDController()); + controller_ = std::make_unique(motor_->GetPIDController()); if (!controller_) { ++throws_; @@ -838,17 +838,17 @@ void SparkMax::ConfigPeriodic() noexcept } else { - uint tmp = std::get(SparkMaxFactory::configDefaults.at("kLimitSwitchFwdPolarity")); + uint tmp = std::get(SparkFactory::configDefaults.at("kLimitSwitchFwdPolarity")); if (config_.count("kLimitSwitchFwdPolarity") != 0) { tmp = std::get(config_.at("kLimitSwitchFwdPolarity")); } - const rev::SparkMaxLimitSwitch::Type polarity = tmp == 0 ? rev::SparkMaxLimitSwitch::Type::kNormallyOpen : rev::SparkMaxLimitSwitch::Type::kNormallyClosed; + const rev::SparkLimitSwitch::Type polarity = tmp == 0 ? rev::SparkLimitSwitch::Type::kNormallyOpen : rev::SparkLimitSwitch::Type::kNormallyClosed; DoSafely("forward_", [&]() -> void { - forward_ = std::make_unique(motor_->GetForwardLimitSwitch(polarity)); + forward_ = std::make_unique(motor_->GetForwardLimitSwitch(polarity)); if (!forward_) { ++throws_; @@ -873,17 +873,17 @@ void SparkMax::ConfigPeriodic() noexcept } else { - uint tmp = std::get(SparkMaxFactory::configDefaults.at("kLimitSwitchRevPolarity")); + uint tmp = std::get(SparkFactory::configDefaults.at("kLimitSwitchRevPolarity")); if (config_.count("kLimitSwitchRevPolarity") != 0) { tmp = std::get(config_.at("kLimitSwitchRevPolarity")); } - const rev::SparkMaxLimitSwitch::Type polarity = tmp == 0 ? rev::SparkMaxLimitSwitch::Type::kNormallyOpen : rev::SparkMaxLimitSwitch::Type::kNormallyClosed; + const rev::SparkLimitSwitch::Type polarity = tmp == 0 ? rev::SparkLimitSwitch::Type::kNormallyOpen : rev::SparkLimitSwitch::Type::kNormallyClosed; DoSafely("reverse_", [&]() -> void { - reverse_ = std::make_unique(motor_->GetReverseLimitSwitch(polarity)); + reverse_ = std::make_unique(motor_->GetReverseLimitSwitch(polarity)); if (!reverse_) { ++throws_; @@ -1378,9 +1378,9 @@ void SparkMax::DoSafely(const char *const what, std::function work) noex // latter function. std::tuple SparkMax::VerifyConfig(const std::string_view key, const ConfigValue &value) noexcept { - const auto kv = SparkMaxFactory::configDefaults.find(std::string(key)); + const auto kv = SparkFactory::configDefaults.find(std::string(key)); - if (kv == SparkMaxFactory::configDefaults.end()) + if (kv == SparkFactory::configDefaults.end()) { return std::make_tuple(false, false, "Invalid"); } @@ -1393,7 +1393,7 @@ std::tuple SparkMax::VerifyConfig(const std::string_vie // In order to avoid switching on a string, obtain the index into defaults. // The complication here is that maps are sorted, so the indices are not in // an easy-to-maintain order. - const auto ndx = std::distance(kv, SparkMaxFactory::configDefaults.end()); + const auto ndx = std::distance(kv, SparkFactory::configDefaults.end()); // Some settings only apply to one encoder type or the other. const bool altMode = (encoderCounts_ != 0); @@ -1909,9 +1909,9 @@ std::tuple SparkMax::VerifyConfig(const std::string_vie // if there was some problem (or empty string, if not). std::string SparkMax::ApplyConfig(const std::string_view key, const ConfigValue &value) noexcept { - const auto kv = SparkMaxFactory::configDefaults.find(std::string(key)); + const auto kv = SparkFactory::configDefaults.find(std::string(key)); - if (kv == SparkMaxFactory::configDefaults.end()) + if (kv == SparkFactory::configDefaults.end()) { return std::string("Invalid"); } @@ -1919,7 +1919,7 @@ std::string SparkMax::ApplyConfig(const std::string_view key, const ConfigValue // In order to avoid switching on a string, obtain the index into defaults. // The complication here is that maps are sorted, so the indices are not in // an easy-to-maintain order. - const auto ndx = std::distance(kv, SparkMaxFactory::configDefaults.end()); + const auto ndx = std::distance(kv, SparkFactory::configDefaults.end()); // Some settings only apply to one encoder type or the other. const bool altMode = (encoderCounts_ != 0); diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index a50f283..e2e7ca2 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -67,10 +67,10 @@ SwerveModule::SwerveModule( // Motor controller configurations are only checked (or saved) in test mode // but a minimal amount is set up in these methods. - m_turningMotorBase = SparkMaxFactory::CreateSparkMax(m_name + std::string(" Turning"), turningMotorCanID, m_turningMotorInverted); + m_turningMotorBase = SparkFactory::CreateSparkMax(m_name + std::string(" Turning"), turningMotorCanID, m_turningMotorInverted); m_turningMotor = std::make_unique>(*m_turningMotorBase); - m_driveMotorBase = SparkMaxFactory::CreateSparkMax(m_name + std::string(" Drive"), driveMotorCanID, m_driveMotorInverted); + m_driveMotorBase = SparkFactory::CreateSparkMax(m_name + std::string(" Drive"), driveMotorCanID, m_driveMotorInverted); m_driveMotor = std::make_unique>(*m_driveMotorBase); // kStatus1 includes velocity; kStatus2 includes position -- these are made diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index f2c393b..7e2081b 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/main/cpp/subsystems/FeederSubsystem.cpp b/src/main/cpp/subsystems/FeederSubsystem.cpp index 442c278..e1412a0 100644 --- a/src/main/cpp/subsystems/FeederSubsystem.cpp +++ b/src/main/cpp/subsystems/FeederSubsystem.cpp @@ -29,10 +29,10 @@ FeederSubsystem::FeederSubsystem() noexcept {"kSmartCurrentStallLimit", uint{30}}, // Amps }; - m_intakeMotorBase = SparkMaxFactory::CreateSparkMax("Intake", 9, false); - m_elevatorMotorBase = SparkMaxFactory::CreateSparkMax("Elevator", 10, true); - m_feederMotorBase = SparkMaxFactory::CreateSparkMax("Feeder", 11, true); - m_climberMotorBase = SparkMaxFactory::CreateSparkMax("Climber", 12, false); + m_intakeMotorBase = SparkFactory::CreateSparkMax("Intake", 9, false); + m_elevatorMotorBase = SparkFactory::CreateSparkMax("Elevator", 10, true); + m_feederMotorBase = SparkFactory::CreateSparkMax("Feeder", 11, true); + m_climberMotorBase = SparkFactory::CreateSparkMax("Climber", 12, false); m_intakeMotor = std::make_unique>(*m_intakeMotorBase); m_elevatorMotor = std::make_unique>(*m_elevatorMotorBase); m_feederMotor = std::make_unique>(*m_feederMotorBase); diff --git a/src/main/cpp/subsystems/ShooterSubsystem.cpp b/src/main/cpp/subsystems/ShooterSubsystem.cpp index 9dbc58b..265f615 100644 --- a/src/main/cpp/subsystems/ShooterSubsystem.cpp +++ b/src/main/cpp/subsystems/ShooterSubsystem.cpp @@ -14,8 +14,8 @@ ShooterSubsystem::ShooterSubsystem() noexcept {"kIdleMode", uint{0}}, }; - m_shooterMotorBase = SparkMaxFactory::CreateSparkMax("Shooter", 13, false); - m_backspinMotorBase = SparkMaxFactory::CreateSparkMax("Backspin", 14, true); + m_shooterMotorBase = SparkFactory::CreateSparkMax("Shooter", 13, false); + m_backspinMotorBase = SparkFactory::CreateSparkMax("Backspin", 14, true); m_shooterMotor = std::make_unique>(*m_shooterMotorBase); m_backspinMotor = std::make_unique>(*m_backspinMotorBase); diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 551fa80..11ef623 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -83,7 +83,7 @@ namespace physical namespace firmware { - constexpr int kSparkMaxFirmwareVersion = 0x01060003; // Version 1.6.3. + constexpr int kSparkFirmwareVersion = 0x01060003; // Version 1.6.3. } namespace pidf diff --git a/src/main/include/commands/AutonomousCommands.h b/src/main/include/commands/AutonomousCommands.h index 35c25e8..2d0d8e4 100644 --- a/src/main/include/commands/AutonomousCommands.h +++ b/src/main/include/commands/AutonomousCommands.h @@ -4,7 +4,7 @@ #pragma once -#include +#include #include #include #include @@ -18,7 +18,7 @@ #include #include -class TimedAutoBase : public frc2::CommandHelper +class TimedAutoBase : public frc2::CommandHelper { protected: TimedAutoBase(DriveSubsystem *const drive, FeederSubsystem *const feeder, InfrastructureSubsystem *const infrastructure, ShooterSubsystem *const shooter, std::string_view name) noexcept diff --git a/src/main/include/commands/TestModeCommands.h b/src/main/include/commands/TestModeCommands.h index 6440d4d..4029f7c 100644 --- a/src/main/include/commands/TestModeCommands.h +++ b/src/main/include/commands/TestModeCommands.h @@ -4,7 +4,7 @@ #pragma once -#include +#include #include #include @@ -14,7 +14,7 @@ // Home all swerve modules to zero heading. class ZeroCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit ZeroCommand(DriveSubsystem *subsystem) noexcept @@ -37,7 +37,7 @@ class ZeroCommand // Expose turning maximum velocity and acceleration. class MaxVAndATurningCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit MaxVAndATurningCommand(DriveSubsystem *subsystem) noexcept @@ -62,7 +62,7 @@ class MaxVAndATurningCommand // Expose drive maximum velocity and acceleration. class MaxVAndADriveCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit MaxVAndADriveCommand(DriveSubsystem *subsystem) noexcept @@ -87,7 +87,7 @@ class MaxVAndADriveCommand // Alternately command swerve modules to form X and O patterns. class XsAndOsCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit XsAndOsCommand(DriveSubsystem *subsystem) noexcept @@ -112,7 +112,7 @@ class XsAndOsCommand // Command swerve modules to slowly rotate together (spin). class RotateModulesCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit RotateModulesCommand(DriveSubsystem *subsystem) noexcept @@ -137,7 +137,7 @@ class RotateModulesCommand // Drive in a square. class SquareCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit SquareCommand(DriveSubsystem *subsystem) noexcept @@ -162,7 +162,7 @@ class SquareCommand // Drive in a spirograph pattern. class SpirographCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit SpirographCommand(DriveSubsystem *subsystem) noexcept @@ -187,7 +187,7 @@ class SpirographCommand // Drive in an orbit. class OrbitCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit OrbitCommand(DriveSubsystem *subsystem) noexcept @@ -210,7 +210,7 @@ class OrbitCommand // Drive in a fancy pirouette. class PirouetteCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit PirouetteCommand(DriveSubsystem *subsystem) noexcept @@ -233,7 +233,7 @@ class PirouetteCommand // Excersize theta (spin) controller. class SpinCommand - : public frc2::CommandHelper + : public frc2::CommandHelper { public: explicit SpinCommand(DriveSubsystem *subsystem) noexcept diff --git a/src/main/include/infrastructure/SparkMax.h b/src/main/include/infrastructure/SparkMax.h index f2593a2..ca52a57 100644 --- a/src/main/include/infrastructure/SparkMax.h +++ b/src/main/include/infrastructure/SparkMax.h @@ -4,7 +4,7 @@ #include "infrastructure/SmartMotor.h" -namespace SparkMaxFactory +namespace SparkFactory { // Print out string to index mapping (for updating switch/cases). void ConfigIndex() noexcept; @@ -109,8 +109,8 @@ namespace SparkMaxFactory // kDataPortConfig; 0 = Default (limit switches are enabled), 1 = Alternate // Encoder Mode (limit switches are disabled / alternate encoder is enabled) // This configuration parameter also has no explicit set and no get at all. -// By constructing a SparkMax, either a rev::SparkMaxRelativeEncoder or a -// rev::SparkMaxAlternateEncoder is constructed, setting this as a side +// By constructing a SparkMax, either a rev::SparkRelativeEncoder or a +// rev::SparkAlternateEncoder is constructed, setting this as a side // effect. This is otherwise the same as the two prior parameters. This // is the fourth of the four parameters to be manually set and saved, and the // fifth of the six parameters that persist. @@ -205,7 +205,7 @@ namespace SparkMaxFactory // Limit switches (hard limits) only work when not in Alternate Encoder Mode. // For polarity, there are similar issues with get (none) and set (side // effect of calling GetForwardLimitSwitch() or GetReverseLimitSwitch()) as -// with analog feedback. Enable get and set are via SparkMaxLimitSwitch. By +// with analog feedback. Enable get and set are via SparkLimitSwitch. By // definition, there isn't much code to handle a hard limit switch, leaving // this mainly to configuration. It may be useful to be able to report the // status, and to enable/disable the limits. Changing the polarity via @@ -378,7 +378,7 @@ namespace SparkMaxFactory // Also note that SmartMotionAccelStrategy does not appear to be implemented; // if it is set to kSCurve, this succeeds -- but it reads back as kTrapezoidal. -namespace SparkMaxFactory +namespace SparkFactory { // Version 1.5.2; all configuration parameters are current at this release. // **** DO NOT ALTER THIS LIST WITHOUT UNDERSTANDING THE IMPLICATIONS! **** diff --git a/src/test/cpp/main.cpp b/src/test/cpp/main.cpp index 3640cef..b8b23d2 100644 --- a/src/test/cpp/main.cpp +++ b/src/test/cpp/main.cpp @@ -2,8 +2,7 @@ #include "gtest/gtest.h" -int main(int argc, char **argv) -{ +int main(int argc, char** argv) { HAL_Initialize(500, 0); ::testing::InitGoogleTest(&argc, argv); int ret = RUN_ALL_TESTS(); diff --git a/vendordeps/NavX.json b/vendordeps/NavX.json index 29ec93a..e978a5f 100644 --- a/vendordeps/NavX.json +++ b/vendordeps/NavX.json @@ -1,17 +1,18 @@ { "fileName": "NavX.json", - "name": "KauaiLabs_navX_FRC", - "version": "2023.0.3", + "name": "NavX", + "version": "2024.1.0", "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", + "frcYear": "2024", "mavenUrls": [ - "https://dev.studica.com/maven/release/2023/" + "https://dev.studica.com/maven/release/2024/" ], - "jsonUrl": "https://dev.studica.com/releases/2023/NavX.json", + "jsonUrl": "https://dev.studica.com/releases/2024/NavX.json", "javaDependencies": [ { "groupId": "com.kauailabs.navx.frc", "artifactId": "navx-frc-java", - "version": "2023.0.3" + "version": "2024.1.0" } ], "jniDependencies": [], @@ -19,7 +20,7 @@ { "groupId": "com.kauailabs.navx.frc", "artifactId": "navx-frc-cpp", - "version": "2023.0.3", + "version": "2024.1.0", "headerClassifier": "headers", "sourcesClassifier": "sources", "sharedLibrary": false, @@ -30,7 +31,7 @@ "linuxraspbian", "linuxarm32", "linuxarm64", - "linux86-64", + "linuxx86-64", "osxuniversal", "windowsx86-64" ] diff --git a/vendordeps/Phoenix.json b/vendordeps/Phoenix.json deleted file mode 100644 index 282d536..0000000 --- a/vendordeps/Phoenix.json +++ /dev/null @@ -1,423 +0,0 @@ -{ - "fileName": "Phoenix.json", - "name": "CTRE-Phoenix (v5)", - "version": "5.30.4+23.0.12", - "frcYear": 2023, - "uuid": "ab676553-b602-441f-a38d-f1296eff6537", - "mavenUrls": [ - "https://maven.ctr-electronics.com/release/" - ], - "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2023-latest.json", - "javaDependencies": [ - { - "groupId": "com.ctre.phoenix", - "artifactId": "api-java", - "version": "5.30.4" - }, - { - "groupId": "com.ctre.phoenix", - "artifactId": "wpiapi-java", - "version": "5.30.4" - } - ], - "jniDependencies": [ - { - "groupId": "com.ctre.phoenix", - "artifactId": "cci", - "version": "5.30.4", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenix.sim", - "artifactId": "cci-sim", - "version": "5.30.4", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro", - "artifactId": "tools", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "tools-sim", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simTalonSRX", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simTalonFX", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simVictorSPX", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simPigeonIMU", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simCANCoder", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simProTalonFX", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simProCANcoder", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simProPigeon2", - "version": "23.0.12", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - } - ], - "cppDependencies": [ - { - "groupId": "com.ctre.phoenix", - "artifactId": "wpiapi-cpp", - "version": "5.30.4", - "libName": "CTRE_Phoenix_WPI", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenix", - "artifactId": "api-cpp", - "version": "5.30.4", - "libName": "CTRE_Phoenix", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenix", - "artifactId": "cci", - "version": "5.30.4", - "libName": "CTRE_PhoenixCCI", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenixpro", - "artifactId": "tools", - "version": "23.0.12", - "libName": "CTRE_PhoenixTools", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenix.sim", - "artifactId": "wpiapi-cpp-sim", - "version": "5.30.4", - "libName": "CTRE_Phoenix_WPISim", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix.sim", - "artifactId": "api-cpp-sim", - "version": "5.30.4", - "libName": "CTRE_PhoenixSim", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix.sim", - "artifactId": "cci-sim", - "version": "5.30.4", - "libName": "CTRE_PhoenixCCISim", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "tools-sim", - "version": "23.0.12", - "libName": "CTRE_PhoenixTools_Sim", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simTalonSRX", - "version": "23.0.12", - "libName": "CTRE_SimTalonSRX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simTalonFX", - "version": "23.0.12", - "libName": "CTRE_SimTalonFX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simVictorSPX", - "version": "23.0.12", - "libName": "CTRE_SimVictorSPX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simPigeonIMU", - "version": "23.0.12", - "libName": "CTRE_SimPigeonIMU", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simCANCoder", - "version": "23.0.12", - "libName": "CTRE_SimCANCoder", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simProTalonFX", - "version": "23.0.12", - "libName": "CTRE_SimProTalonFX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simProCANcoder", - "version": "23.0.12", - "libName": "CTRE_SimProCANcoder", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenixpro.sim", - "artifactId": "simProPigeon2", - "version": "23.0.12", - "libName": "CTRE_SimProPigeon2", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - } - ] -} \ No newline at end of file diff --git a/vendordeps/REVLib.json b/vendordeps/REVLib.json index f2d0b7d..0f3520e 100644 --- a/vendordeps/REVLib.json +++ b/vendordeps/REVLib.json @@ -1,24 +1,25 @@ { "fileName": "REVLib.json", "name": "REVLib", - "version": "2023.1.3", + "version": "2024.2.0", + "frcYear": "2024", "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", "mavenUrls": [ "https://maven.revrobotics.com/" ], - "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2023.json", + "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2024.json", "javaDependencies": [ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-java", - "version": "2023.1.3" + "version": "2024.2.0" } ], "jniDependencies": [ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2023.1.3", + "version": "2024.2.0", "skipInvalidPlatforms": true, "isJar": false, "validPlatforms": [ @@ -36,7 +37,7 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-cpp", - "version": "2023.1.3", + "version": "2024.2.0", "libName": "REVLib", "headerClassifier": "headers", "sharedLibrary": false, @@ -54,7 +55,7 @@ { "groupId": "com.revrobotics.frc", "artifactId": "REVLib-driver", - "version": "2023.1.3", + "version": "2024.2.0", "libName": "REVLibDriver", "headerClassifier": "headers", "sharedLibrary": false, diff --git a/vendordeps/WPILibNewCommands.json b/vendordeps/WPILibNewCommands.json index 65dcc03..67bf389 100644 --- a/vendordeps/WPILibNewCommands.json +++ b/vendordeps/WPILibNewCommands.json @@ -3,35 +3,36 @@ "name": "WPILib-New-Commands", "version": "1.0.0", "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266", + "frcYear": "2024", "mavenUrls": [], "jsonUrl": "", "javaDependencies": [ - { - "groupId": "edu.wpi.first.wpilibNewCommands", - "artifactId": "wpilibNewCommands-java", - "version": "wpilib" - } + { + "groupId": "edu.wpi.first.wpilibNewCommands", + "artifactId": "wpilibNewCommands-java", + "version": "wpilib" + } ], "jniDependencies": [], "cppDependencies": [ - { - "groupId": "edu.wpi.first.wpilibNewCommands", - "artifactId": "wpilibNewCommands-cpp", - "version": "wpilib", - "libName": "wpilibNewCommands", - "headerClassifier": "headers", - "sourcesClassifier": "sources", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "linuxathena", - "linuxarm32", - "linuxarm64", - "windowsx86-64", - "windowsx86", - "linuxx86-64", - "osxuniversal" - ] - } + { + "groupId": "edu.wpi.first.wpilibNewCommands", + "artifactId": "wpilibNewCommands-cpp", + "version": "wpilib", + "libName": "wpilibNewCommands", + "headerClassifier": "headers", + "sourcesClassifier": "sources", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "linuxathena", + "linuxarm32", + "linuxarm64", + "windowsx86-64", + "windowsx86", + "linuxx86-64", + "osxuniversal" + ] + } ] } From f458d7db76240761ae9f520bfd9438170b74e70a Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 13 Jan 2024 19:44:56 -0500 Subject: [PATCH 002/126] Remove extra parameter from Rev GetEncoder --- src/main/cpp/infrastructure/SparkMax.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/cpp/infrastructure/SparkMax.cpp b/src/main/cpp/infrastructure/SparkMax.cpp index 223d687..8c3986e 100644 --- a/src/main/cpp/infrastructure/SparkMax.cpp +++ b/src/main/cpp/infrastructure/SparkMax.cpp @@ -736,7 +736,10 @@ void SparkMax::ConfigPeriodic() noexcept { if (encoderCounts_ == 0) { - encoder_ = std::make_unique(motor_->GetEncoder(rev::SparkRelativeEncoder::Type::kHallSensor, 42)); + // FIXME: Once Rev updates the library to not include + // conflicting API definitions, the parameter to Get Encoder + // can be removed. + encoder_ = std::make_unique(motor_->GetEncoder(rev::SparkRelativeEncoder::Type::kHallSensor)); } else { From 60028b2a2a8f2b53ac22b1426648710f7d4e07ab Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 13 Jan 2024 23:47:08 -0500 Subject: [PATCH 003/126] disable autonomous --- src/main/cpp/Robot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cpp/Robot.cpp b/src/main/cpp/Robot.cpp index cbc28f6..e69bd77 100644 --- a/src/main/cpp/Robot.cpp +++ b/src/main/cpp/Robot.cpp @@ -65,7 +65,7 @@ void Robot::AutonomousInit() noexcept if (m_autonomousCommand) { - m_autonomousCommand->Schedule(); + //m_autonomousCommand->Schedule();----------------------------------------------------------UNCOMMENT TO REENABLE AUTONOMOUS } m_container.AutonomousInit(); From 1abde0ef4dc83cd35f8babf55d6e07859ab9480f Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:40:47 -0800 Subject: [PATCH 004/126] added pwm reference files --- .../infrastructure/PWMAngleSensor copy.cpp | 215 ++++++++++++++++++ .../infrastructure/PWMAngleSensor copy.h | 58 +++++ 2 files changed, 273 insertions(+) create mode 100644 src/main/cpp/infrastructure/PWMAngleSensor copy.cpp create mode 100644 src/main/include/infrastructure/PWMAngleSensor copy.h diff --git a/src/main/cpp/infrastructure/PWMAngleSensor copy.cpp b/src/main/cpp/infrastructure/PWMAngleSensor copy.cpp new file mode 100644 index 0000000..db19970 --- /dev/null +++ b/src/main/cpp/infrastructure/PWMAngleSensor copy.cpp @@ -0,0 +1,215 @@ +#include "infrastructure/PWMAngleSensor.h" + +#include "infrastructure/ShuffleboardWidgets.h" + +AngleSensor::AngleSensor(const int channel, const int alignment) noexcept : alignment_{alignment} +{ + digitalInput_ = std::make_unique(channel); + dutyCycle_ = std::make_unique(*digitalInput_); +} + +void AngleSensor::Periodic() noexcept +{ + if (!shuffleboard_) + { + return; + } + + units::angle::degree_t commandedPosition{0.0_deg}; + units::angle::degree_t encoderPosition{0.0_deg}; + + // If possible, obtain commanded and encoder positions. + if (getCommandedAndEncoderPositionsF_) + { + const auto pair = getCommandedAndEncoderPositionsF_(); + + commandedPosition = std::get<0>(pair); + encoderPosition = std::get<1>(pair); + } + + const int frequency = dutyCycle_->GetFrequency(); + const double output = dutyCycle_->GetOutput(); + const auto position = GetAbsolutePosition(frequency, output, false); + const bool status = position.has_value(); + int reportPosition{0}; + + if (status) + { + reportPosition = position.value() + alignment_; + + if (reportPosition > 2047) + { + reportPosition = reportPosition - 4096; + } + if (reportPosition < -2048) + { + reportPosition = reportPosition + 4096; + } + } + + const double heading = static_cast(reportPosition) * 360.0 / 4096.0; + + headingGyro_.Set(heading); + + double commandedError = heading - commandedPosition.to(); + + if (commandedError < -180.0) + { + commandedError += 360.0; + } + else if (commandedError >= 180.0) + { + commandedError -= 360.0; + } + + double encoderError = heading - encoderPosition.to(); + + if (encoderError < -180.0) + { + encoderError += 360.0; + } + else if (encoderError >= 180.0) + { + encoderError -= 360.0; + } + + frequencyUI_->GetEntry()->SetInteger(frequency); + commandDiscrepancyUI_->GetEntry()->SetDouble(commandedError); + statusUI_->GetEntry()->SetBoolean(status); + commandedUI_->GetEntry()->SetDouble(commandedPosition.to()); + positionUI_->GetEntry()->SetInteger(reportPosition); + outputUI_->GetEntry()->SetDouble(output); + encoderDiscrepancyUI_->GetEntry()->SetDouble(encoderError); + alignmentUI_->GetEntry()->SetInteger(alignment_); +} + +void AngleSensor::ShuffleboardCreate(frc::ShuffleboardContainer &container, + std::function()> getCommandedAndEncoderPositionsF) noexcept +{ + frequencyUI_ = &container.Add("Frequency", 0) + .WithPosition(0, 0); + + commandDiscrepancyUI_ = &container.Add("PID Error", 0) + .WithPosition(0, 1); + + statusUI_ = &container.Add("Status", false) + .WithPosition(0, 2) + .WithWidget(frc::BuiltInWidgets::kBooleanBox); + + headingUI_ = &container.Add("Heading", headingGyro_) + .WithPosition(1, 0) + .WithWidget(frc::BuiltInWidgets::kGyro) + .WithProperties(wpi::StringMap{ + std::make_pair("Counter clockwise", nt::Value::MakeBoolean(true))}); + + commandedUI_ = &container.Add("Commanded", 0) + .WithPosition(1, 1); + + positionUI_ = &container.Add("Position", 0) + .WithPosition(1, 2); + + outputUI_ = &container.Add("Output", 0.0) + .WithPosition(2, 0); + + encoderDiscrepancyUI_ = &container.Add("Motor Discrepancy", 0) + .WithPosition(2, 1); + + alignmentUI_ = &container.Add("Alignment", 0) + .WithPosition(2, 2); + + getCommandedAndEncoderPositionsF_ = getCommandedAndEncoderPositionsF; + + shuffleboard_ = true; +} + +std::optional AngleSensor::GetAbsolutePosition() noexcept +{ + const auto position = GetAbsolutePosition(dutyCycle_->GetFrequency(), dutyCycle_->GetOutput(), true); + + if (!position.has_value()) + { + return std::nullopt; + } + + return units::angle::turn_t{static_cast(position.value()) / 4096.0}; +} + +std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept +{ + return GetAbsolutePosition(dutyCycle_->GetFrequency(), dutyCycle_->GetOutput(), false); +} + +// Calulate absolute turning position in the range [-2048, +2048), from the raw +// absolute PWM encoder data. No value is returned in the event the absolute +// position sensor itself is not returning valid data. The alignment offset is +// optionally used to establish the zero position. + +// This is a low-level routine, meant to be used only by the version of +// GetAbsolutePosition() with no arguments (above) or, from test mode. It does +// not use standardized units and leaks knowledge of the sensor output range. +std::optional AngleSensor::GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept +{ + // SRX MAG ENCODER chip seems to be AS5045B; from the datasheet, position + // is given by: + // ((t_on * 4098) / (t_on + t_off)) - 1; + // if this result is 4096, set it to 4095. + // This computation results in a position in the range [0, 4096). + + // REV Through Bore Encoder chip is AEAT-8800-Q24, which has configurable + // PWM parameters. However, it seems to be configured to match AS5045B, so + // it works without any changes. A factory preset zero offset may allow no + // alignment (simply specify an alignment of zero). + + // If the frequency isn't within the range specified in the data sheet, + // return an error. This range is [220, 268], with a nominal value of 244. + // A tolerance of 12 (~5% of nominal) is provided for any measurment error. + bool absoluteSensorGood = frequency >= 208 && frequency <= 280; + + if (!absoluteSensorGood) + { + return std::nullopt; + } + + // GetOutput() is (t_on / (t_on + t_off)); this is all that we have; note + // that this is sampled at a high frequency and the sampling window may not + // be perfectly aligned with the signal, although the frequency should be + // employed to ensure the sampling window is sized to a multiple of the + // period of the signal. So, t_on and t_off have a different scale in this + // context, which is OK. We are using the duty cycle (ratio) only. + + // Multiply by 4098 and subtract 1; should yield 0 - 4094, and also 4096. + // Conditionals cover special case of 4096 and enforce the specified range. + int position = std::lround(output * 4098.0) - 1; + if (position < 0) + { + position = 0; + } + else if (position > 4095) + { + position = 4095; + } + + // Now, shift the range from [0, 4096) to [-2048, +2048). + position -= 2048; + + if (!applyOffset) + { + return position; + } + + // There is a mechanical offset reflecting the alignment of the magnet with + // repect to the axis of rotation of the wheel (and also any variance in + // the alignment of the sensor). Add this in here to reference position to + // the desired zero position. + position += alignment_; + if (position > 2047) + { + position -= 4096; + } + if (position < -2048) + { + position += 4096; + } + + return position; +} diff --git a/src/main/include/infrastructure/PWMAngleSensor copy.h b/src/main/include/infrastructure/PWMAngleSensor copy.h new file mode 100644 index 0000000..ffaf82c --- /dev/null +++ b/src/main/include/infrastructure/PWMAngleSensor copy.h @@ -0,0 +1,58 @@ +#pragma once + +#include "infrastructure/ShuffleboardWidgets.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +class AngleSensor +{ +public: + AngleSensor(const int channel, const int alignment) noexcept; + + AngleSensor(const AngleSensor &) = delete; + AngleSensor &operator=(const AngleSensor &) = delete; + + void Periodic() noexcept; + + void ShuffleboardCreate(frc::ShuffleboardContainer &container, + std::function()> getCommandedAndEncoderPositions = nullptr) noexcept; + + int GetAlignment() noexcept { return alignment_; } + + void SetAlignment(const int alignment) noexcept { alignment_ = alignment; } + + std::optional GetAbsolutePosition() noexcept; + + std::optional GetAbsolutePositionWithoutAlignment() noexcept; + +private: + // Range is [-2048, +2048). + std::optional GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept; + + int alignment_{0}; + + std::unique_ptr digitalInput_; + std::unique_ptr dutyCycle_; + + std::function()> getCommandedAndEncoderPositionsF_{nullptr}; + HeadingGyro headingGyro_; + + bool shuffleboard_{false}; + frc::SimpleWidget *frequencyUI_{nullptr}; + frc::SimpleWidget *commandDiscrepancyUI_{nullptr}; + frc::SimpleWidget *statusUI_{nullptr}; + frc::ComplexWidget *headingUI_{nullptr}; + frc::SimpleWidget *commandedUI_{nullptr}; + frc::SimpleWidget *positionUI_{nullptr}; + frc::SimpleWidget *outputUI_{nullptr}; + frc::SimpleWidget *encoderDiscrepancyUI_{nullptr}; + frc::SimpleWidget *alignmentUI_{nullptr}; +}; From 5d2f3caeb5c9c3066f6cccdcfcb4a38f5b749430 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:47:07 -0800 Subject: [PATCH 005/126] Create Phoenix6.json --- vendordeps/Phoenix6.json | 339 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 vendordeps/Phoenix6.json diff --git a/vendordeps/Phoenix6.json b/vendordeps/Phoenix6.json new file mode 100644 index 0000000..69a4079 --- /dev/null +++ b/vendordeps/Phoenix6.json @@ -0,0 +1,339 @@ +{ + "fileName": "Phoenix6.json", + "name": "CTRE-Phoenix (v6)", + "version": "24.1.0", + "frcYear": 2024, + "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", + "mavenUrls": [ + "https://maven.ctr-electronics.com/release/" + ], + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json", + "conflictsWith": [ + { + "uuid": "3fcf3402-e646-4fa6-971e-18afe8173b1a", + "errorMessage": "The combined Phoenix-6-And-5 vendordep is no longer supported. Please remove the vendordep and instead add both the latest Phoenix 6 vendordep and Phoenix 5 vendordep.", + "offlineFileName": "Phoenix6And5.json" + } + ], + "javaDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "wpiapi-java", + "version": "24.1.0" + } + ], + "jniDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "tools", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "tools-sim", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonSRX", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonFX", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simVictorSPX", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simPigeonIMU", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simCANCoder", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFX", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANcoder", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProPigeon2", + "version": "24.1.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ], + "cppDependencies": [ + { + "groupId": "com.ctre.phoenix6", + "artifactId": "wpiapi-cpp", + "version": "24.1.0", + "libName": "CTRE_Phoenix6_WPI", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6", + "artifactId": "tools", + "version": "24.1.0", + "libName": "CTRE_PhoenixTools", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "wpiapi-cpp-sim", + "version": "24.1.0", + "libName": "CTRE_Phoenix6_WPISim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "tools-sim", + "version": "24.1.0", + "libName": "CTRE_PhoenixTools_Sim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonSRX", + "version": "24.1.0", + "libName": "CTRE_SimTalonSRX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simTalonFX", + "version": "24.1.0", + "libName": "CTRE_SimTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simVictorSPX", + "version": "24.1.0", + "libName": "CTRE_SimVictorSPX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simPigeonIMU", + "version": "24.1.0", + "libName": "CTRE_SimPigeonIMU", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simCANCoder", + "version": "24.1.0", + "libName": "CTRE_SimCANCoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProTalonFX", + "version": "24.1.0", + "libName": "CTRE_SimProTalonFX", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProCANcoder", + "version": "24.1.0", + "libName": "CTRE_SimProCANcoder", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix6.sim", + "artifactId": "simProPigeon2", + "version": "24.1.0", + "libName": "CTRE_SimProPigeon2", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ] +} \ No newline at end of file From 89eb6d6a8e54486d10f549e230e8b196b7015481 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 14 Jan 2024 22:21:42 -0500 Subject: [PATCH 006/126] Revert "added pwm reference files" This reverts commit 1abde0ef4dc83cd35f8babf55d6e07859ab9480f. --- .../infrastructure/PWMAngleSensor copy.cpp | 215 ------------------ .../infrastructure/PWMAngleSensor copy.h | 58 ----- 2 files changed, 273 deletions(-) delete mode 100644 src/main/cpp/infrastructure/PWMAngleSensor copy.cpp delete mode 100644 src/main/include/infrastructure/PWMAngleSensor copy.h diff --git a/src/main/cpp/infrastructure/PWMAngleSensor copy.cpp b/src/main/cpp/infrastructure/PWMAngleSensor copy.cpp deleted file mode 100644 index db19970..0000000 --- a/src/main/cpp/infrastructure/PWMAngleSensor copy.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include "infrastructure/PWMAngleSensor.h" - -#include "infrastructure/ShuffleboardWidgets.h" - -AngleSensor::AngleSensor(const int channel, const int alignment) noexcept : alignment_{alignment} -{ - digitalInput_ = std::make_unique(channel); - dutyCycle_ = std::make_unique(*digitalInput_); -} - -void AngleSensor::Periodic() noexcept -{ - if (!shuffleboard_) - { - return; - } - - units::angle::degree_t commandedPosition{0.0_deg}; - units::angle::degree_t encoderPosition{0.0_deg}; - - // If possible, obtain commanded and encoder positions. - if (getCommandedAndEncoderPositionsF_) - { - const auto pair = getCommandedAndEncoderPositionsF_(); - - commandedPosition = std::get<0>(pair); - encoderPosition = std::get<1>(pair); - } - - const int frequency = dutyCycle_->GetFrequency(); - const double output = dutyCycle_->GetOutput(); - const auto position = GetAbsolutePosition(frequency, output, false); - const bool status = position.has_value(); - int reportPosition{0}; - - if (status) - { - reportPosition = position.value() + alignment_; - - if (reportPosition > 2047) - { - reportPosition = reportPosition - 4096; - } - if (reportPosition < -2048) - { - reportPosition = reportPosition + 4096; - } - } - - const double heading = static_cast(reportPosition) * 360.0 / 4096.0; - - headingGyro_.Set(heading); - - double commandedError = heading - commandedPosition.to(); - - if (commandedError < -180.0) - { - commandedError += 360.0; - } - else if (commandedError >= 180.0) - { - commandedError -= 360.0; - } - - double encoderError = heading - encoderPosition.to(); - - if (encoderError < -180.0) - { - encoderError += 360.0; - } - else if (encoderError >= 180.0) - { - encoderError -= 360.0; - } - - frequencyUI_->GetEntry()->SetInteger(frequency); - commandDiscrepancyUI_->GetEntry()->SetDouble(commandedError); - statusUI_->GetEntry()->SetBoolean(status); - commandedUI_->GetEntry()->SetDouble(commandedPosition.to()); - positionUI_->GetEntry()->SetInteger(reportPosition); - outputUI_->GetEntry()->SetDouble(output); - encoderDiscrepancyUI_->GetEntry()->SetDouble(encoderError); - alignmentUI_->GetEntry()->SetInteger(alignment_); -} - -void AngleSensor::ShuffleboardCreate(frc::ShuffleboardContainer &container, - std::function()> getCommandedAndEncoderPositionsF) noexcept -{ - frequencyUI_ = &container.Add("Frequency", 0) - .WithPosition(0, 0); - - commandDiscrepancyUI_ = &container.Add("PID Error", 0) - .WithPosition(0, 1); - - statusUI_ = &container.Add("Status", false) - .WithPosition(0, 2) - .WithWidget(frc::BuiltInWidgets::kBooleanBox); - - headingUI_ = &container.Add("Heading", headingGyro_) - .WithPosition(1, 0) - .WithWidget(frc::BuiltInWidgets::kGyro) - .WithProperties(wpi::StringMap{ - std::make_pair("Counter clockwise", nt::Value::MakeBoolean(true))}); - - commandedUI_ = &container.Add("Commanded", 0) - .WithPosition(1, 1); - - positionUI_ = &container.Add("Position", 0) - .WithPosition(1, 2); - - outputUI_ = &container.Add("Output", 0.0) - .WithPosition(2, 0); - - encoderDiscrepancyUI_ = &container.Add("Motor Discrepancy", 0) - .WithPosition(2, 1); - - alignmentUI_ = &container.Add("Alignment", 0) - .WithPosition(2, 2); - - getCommandedAndEncoderPositionsF_ = getCommandedAndEncoderPositionsF; - - shuffleboard_ = true; -} - -std::optional AngleSensor::GetAbsolutePosition() noexcept -{ - const auto position = GetAbsolutePosition(dutyCycle_->GetFrequency(), dutyCycle_->GetOutput(), true); - - if (!position.has_value()) - { - return std::nullopt; - } - - return units::angle::turn_t{static_cast(position.value()) / 4096.0}; -} - -std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept -{ - return GetAbsolutePosition(dutyCycle_->GetFrequency(), dutyCycle_->GetOutput(), false); -} - -// Calulate absolute turning position in the range [-2048, +2048), from the raw -// absolute PWM encoder data. No value is returned in the event the absolute -// position sensor itself is not returning valid data. The alignment offset is -// optionally used to establish the zero position. - -// This is a low-level routine, meant to be used only by the version of -// GetAbsolutePosition() with no arguments (above) or, from test mode. It does -// not use standardized units and leaks knowledge of the sensor output range. -std::optional AngleSensor::GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept -{ - // SRX MAG ENCODER chip seems to be AS5045B; from the datasheet, position - // is given by: - // ((t_on * 4098) / (t_on + t_off)) - 1; - // if this result is 4096, set it to 4095. - // This computation results in a position in the range [0, 4096). - - // REV Through Bore Encoder chip is AEAT-8800-Q24, which has configurable - // PWM parameters. However, it seems to be configured to match AS5045B, so - // it works without any changes. A factory preset zero offset may allow no - // alignment (simply specify an alignment of zero). - - // If the frequency isn't within the range specified in the data sheet, - // return an error. This range is [220, 268], with a nominal value of 244. - // A tolerance of 12 (~5% of nominal) is provided for any measurment error. - bool absoluteSensorGood = frequency >= 208 && frequency <= 280; - - if (!absoluteSensorGood) - { - return std::nullopt; - } - - // GetOutput() is (t_on / (t_on + t_off)); this is all that we have; note - // that this is sampled at a high frequency and the sampling window may not - // be perfectly aligned with the signal, although the frequency should be - // employed to ensure the sampling window is sized to a multiple of the - // period of the signal. So, t_on and t_off have a different scale in this - // context, which is OK. We are using the duty cycle (ratio) only. - - // Multiply by 4098 and subtract 1; should yield 0 - 4094, and also 4096. - // Conditionals cover special case of 4096 and enforce the specified range. - int position = std::lround(output * 4098.0) - 1; - if (position < 0) - { - position = 0; - } - else if (position > 4095) - { - position = 4095; - } - - // Now, shift the range from [0, 4096) to [-2048, +2048). - position -= 2048; - - if (!applyOffset) - { - return position; - } - - // There is a mechanical offset reflecting the alignment of the magnet with - // repect to the axis of rotation of the wheel (and also any variance in - // the alignment of the sensor). Add this in here to reference position to - // the desired zero position. - position += alignment_; - if (position > 2047) - { - position -= 4096; - } - if (position < -2048) - { - position += 4096; - } - - return position; -} diff --git a/src/main/include/infrastructure/PWMAngleSensor copy.h b/src/main/include/infrastructure/PWMAngleSensor copy.h deleted file mode 100644 index ffaf82c..0000000 --- a/src/main/include/infrastructure/PWMAngleSensor copy.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "infrastructure/ShuffleboardWidgets.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -class AngleSensor -{ -public: - AngleSensor(const int channel, const int alignment) noexcept; - - AngleSensor(const AngleSensor &) = delete; - AngleSensor &operator=(const AngleSensor &) = delete; - - void Periodic() noexcept; - - void ShuffleboardCreate(frc::ShuffleboardContainer &container, - std::function()> getCommandedAndEncoderPositions = nullptr) noexcept; - - int GetAlignment() noexcept { return alignment_; } - - void SetAlignment(const int alignment) noexcept { alignment_ = alignment; } - - std::optional GetAbsolutePosition() noexcept; - - std::optional GetAbsolutePositionWithoutAlignment() noexcept; - -private: - // Range is [-2048, +2048). - std::optional GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept; - - int alignment_{0}; - - std::unique_ptr digitalInput_; - std::unique_ptr dutyCycle_; - - std::function()> getCommandedAndEncoderPositionsF_{nullptr}; - HeadingGyro headingGyro_; - - bool shuffleboard_{false}; - frc::SimpleWidget *frequencyUI_{nullptr}; - frc::SimpleWidget *commandDiscrepancyUI_{nullptr}; - frc::SimpleWidget *statusUI_{nullptr}; - frc::ComplexWidget *headingUI_{nullptr}; - frc::SimpleWidget *commandedUI_{nullptr}; - frc::SimpleWidget *positionUI_{nullptr}; - frc::SimpleWidget *outputUI_{nullptr}; - frc::SimpleWidget *encoderDiscrepancyUI_{nullptr}; - frc::SimpleWidget *alignmentUI_{nullptr}; -}; From 7e398a02658eed2c51f4daf2adce7203c59724c0 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 14 Jan 2024 22:56:46 -0500 Subject: [PATCH 007/126] Update PWMAngleSensor.h --- src/main/include/infrastructure/PWMAngleSensor.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/include/infrastructure/PWMAngleSensor.h b/src/main/include/infrastructure/PWMAngleSensor.h index ffaf82c..e4bcfdb 100644 --- a/src/main/include/infrastructure/PWMAngleSensor.h +++ b/src/main/include/infrastructure/PWMAngleSensor.h @@ -6,6 +6,7 @@ #include #include #include +//#include "ctre/Phoenix/Sensors/CANCoder.h" #include #include From 7c117ffda5558a51e8dec2c5ba8354dbc0ffc748 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 14 Jan 2024 23:10:19 -0500 Subject: [PATCH 008/126] Update PWMAngleSensor.h --- src/main/include/infrastructure/PWMAngleSensor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/include/infrastructure/PWMAngleSensor.h b/src/main/include/infrastructure/PWMAngleSensor.h index e4bcfdb..e9aa4b1 100644 --- a/src/main/include/infrastructure/PWMAngleSensor.h +++ b/src/main/include/infrastructure/PWMAngleSensor.h @@ -6,7 +6,7 @@ #include #include #include -//#include "ctre/Phoenix/Sensors/CANCoder.h" +//#include #include #include From 5d105494a436f6994d5f6d9416dcad6a19306c45 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 14 Jan 2024 23:39:19 -0500 Subject: [PATCH 009/126] added phoenix5.json --- .../include/infrastructure/PWMAngleSensor.h | 2 +- vendordeps/Phoenix5.json | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 vendordeps/Phoenix5.json diff --git a/src/main/include/infrastructure/PWMAngleSensor.h b/src/main/include/infrastructure/PWMAngleSensor.h index e9aa4b1..d25f4f9 100644 --- a/src/main/include/infrastructure/PWMAngleSensor.h +++ b/src/main/include/infrastructure/PWMAngleSensor.h @@ -6,7 +6,7 @@ #include #include #include -//#include +#include #include #include diff --git a/vendordeps/Phoenix5.json b/vendordeps/Phoenix5.json new file mode 100644 index 0000000..88a68dd --- /dev/null +++ b/vendordeps/Phoenix5.json @@ -0,0 +1,151 @@ +{ + "fileName": "Phoenix5.json", + "name": "CTRE-Phoenix (v5)", + "version": "5.33.0", + "frcYear": 2024, + "uuid": "ab676553-b602-441f-a38d-f1296eff6537", + "mavenUrls": [ + "https://maven.ctr-electronics.com/release/" + ], + "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix/Phoenix5-frc2024-latest.json", + "requires": [ + { + "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", + "errorMessage": "Phoenix 5 requires low-level libraries from Phoenix 6. Please add the Phoenix 6 vendordep before adding Phoenix 5.", + "offlineFileName": "Phoenix6.json", + "onlineUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json" + } + ], + "javaDependencies": [ + { + "groupId": "com.ctre.phoenix", + "artifactId": "api-java", + "version": "5.33.0" + }, + { + "groupId": "com.ctre.phoenix", + "artifactId": "wpiapi-java", + "version": "5.33.0" + } + ], + "jniDependencies": [ + { + "groupId": "com.ctre.phoenix", + "artifactId": "cci", + "version": "5.33.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix.sim", + "artifactId": "cci-sim", + "version": "5.33.0", + "isJar": false, + "skipInvalidPlatforms": true, + "validPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ], + "cppDependencies": [ + { + "groupId": "com.ctre.phoenix", + "artifactId": "wpiapi-cpp", + "version": "5.33.0", + "libName": "CTRE_Phoenix_WPI", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix", + "artifactId": "api-cpp", + "version": "5.33.0", + "libName": "CTRE_Phoenix", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix", + "artifactId": "cci", + "version": "5.33.0", + "libName": "CTRE_PhoenixCCI", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "linuxathena" + ], + "simMode": "hwsim" + }, + { + "groupId": "com.ctre.phoenix.sim", + "artifactId": "wpiapi-cpp-sim", + "version": "5.33.0", + "libName": "CTRE_Phoenix_WPISim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix.sim", + "artifactId": "api-cpp-sim", + "version": "5.33.0", + "libName": "CTRE_PhoenixSim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + }, + { + "groupId": "com.ctre.phoenix.sim", + "artifactId": "cci-sim", + "version": "5.33.0", + "libName": "CTRE_PhoenixCCISim", + "headerClassifier": "headers", + "sharedLibrary": true, + "skipInvalidPlatforms": true, + "binaryPlatforms": [ + "windowsx86-64", + "linuxx86-64", + "osxuniversal" + ], + "simMode": "swsim" + } + ] +} \ No newline at end of file From d676e500fcf2eaf51a496d9c4e7f3f913210e274 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 15 Jan 2024 10:53:42 -0500 Subject: [PATCH 010/126] added range bounding --- .vscode/settings.json | 79 ++++++++++++++++++- .../cpp/infrastructure/PWMAngleSensor.cpp | 28 ++++--- .../include/infrastructure/PWMAngleSensor.h | 5 +- 3 files changed, 96 insertions(+), 16 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5e795ff..92d8221 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,82 @@ "**/.factorypath": true, "**/*~": true }, - "C_Cpp.default.configurationProvider": "vscode-wpilib" + "C_Cpp.default.configurationProvider": "vscode-wpilib", + "files.associations": { + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "cfenv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "semaphore": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" + } } diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index db19970..2d0858b 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -2,10 +2,11 @@ #include "infrastructure/ShuffleboardWidgets.h" -AngleSensor::AngleSensor(const int channel, const int alignment) noexcept : alignment_{alignment} -{ - digitalInput_ = std::make_unique(channel); - dutyCycle_ = std::make_unique(*digitalInput_); +AngleSensor::AngleSensor(int deviceID, int alignment) noexcept + : canCoder_(deviceID), alignment_(alignment) { + // Configure the sensor range and other settings + canCoder_.ConfigAbsoluteSensorRange(ctre::phoenix::sensors::AbsoluteSensorRange::Unsigned_0_to_360); + // Other configuration as needed... } void AngleSensor::Periodic() noexcept @@ -122,16 +123,17 @@ void AngleSensor::ShuffleboardCreate(frc::ShuffleboardContainer &container, shuffleboard_ = true; } -std::optional AngleSensor::GetAbsolutePosition() noexcept -{ - const auto position = GetAbsolutePosition(dutyCycle_->GetFrequency(), dutyCycle_->GetOutput(), true); - - if (!position.has_value()) - { - return std::nullopt; +std::optional AngleSensor::GetAbsolutePosition() noexcept { + double position = canCoder_.GetPosition(); + // Continuously adjust position to fall within [-180 to 180) degree range + while (position > 180.0) { + position -= 360.0; } - - return units::angle::turn_t{static_cast(position.value()) / 4096.0}; + while (position <= -180.0) { + position += 360.0; + } + // Apply alignment offset and return + return units::angle::degree_t{position + alignment_}; } std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept diff --git a/src/main/include/infrastructure/PWMAngleSensor.h b/src/main/include/infrastructure/PWMAngleSensor.h index d25f4f9..8cbf5c6 100644 --- a/src/main/include/infrastructure/PWMAngleSensor.h +++ b/src/main/include/infrastructure/PWMAngleSensor.h @@ -16,7 +16,7 @@ class AngleSensor { public: - AngleSensor(const int channel, const int alignment) noexcept; + AngleSensor(int deviceID, int alignment) noexcept; AngleSensor(const AngleSensor &) = delete; AngleSensor &operator=(const AngleSensor &) = delete; @@ -38,7 +38,8 @@ class AngleSensor // Range is [-2048, +2048). std::optional GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept; - int alignment_{0}; + ctre::phoenix::sensors::CANCoder canCoder_; + int alignment_; std::unique_ptr digitalInput_; std::unique_ptr dutyCycle_; From 6848ed066df91e9f39530a41031eae6b394d1f07 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:08:45 -0500 Subject: [PATCH 011/126] diable wheels attempt 1 --- src/main/cpp/subsystems/DriveSubsystem.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index 7e2081b..a6b964f 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -1083,6 +1083,13 @@ void DriveSubsystem::SetModuleStates(std::array &desi m_frontRightSwerveModule->SetDesiredState(frontRight); m_rearLeftSwerveModule->SetDesiredState(rearLeft); m_rearRightSwerveModule->SetDesiredState(rearRight); + +// -----------------------------------------------------------------------------debugging + m_frontLeftSwerveModule->SetDriveVelocity(0.0_mps); + m_frontRightSwerveModule->SetDriveVelocity(0.0_mps); + m_rearLeftSwerveModule->SetDriveVelocity(0.0_mps); + m_rearRightSwerveModule->SetDriveVelocity(0.0_mps); +// -----------------------------------------------------------------------------debugging } units::degree_t DriveSubsystem::GetHeading() noexcept From ddda57368daf669afd39d953adecfe23ea5f912f Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:13:37 -0500 Subject: [PATCH 012/126] attempt 2 --- src/main/cpp/infrastructure/SwerveModule.cpp | 3 ++- src/main/cpp/subsystems/DriveSubsystem.cpp | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index e2e7ca2..f1d9de9 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -445,7 +445,8 @@ void SwerveModule::SetDesiredState(const frc::SwerveModuleState &referenceState) SetTurningPosition(state.angle.Degrees()); - SetDriveVelocity(state.speed); + //SetDriveVelocity(state.speed); -------------------------------------------------------debugging + SetDriveVelocity(0_mps); } void SwerveModule::ResetEncoders() noexcept diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index a6b964f..9910f9e 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -1084,12 +1084,6 @@ void DriveSubsystem::SetModuleStates(std::array &desi m_rearLeftSwerveModule->SetDesiredState(rearLeft); m_rearRightSwerveModule->SetDesiredState(rearRight); -// -----------------------------------------------------------------------------debugging - m_frontLeftSwerveModule->SetDriveVelocity(0.0_mps); - m_frontRightSwerveModule->SetDriveVelocity(0.0_mps); - m_rearLeftSwerveModule->SetDriveVelocity(0.0_mps); - m_rearRightSwerveModule->SetDriveVelocity(0.0_mps); -// -----------------------------------------------------------------------------debugging } units::degree_t DriveSubsystem::GetHeading() noexcept From 752e36e46e744acb784b119246cf4d4e80e12da8 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:24:19 -0500 Subject: [PATCH 013/126] undo changes --- src/main/cpp/infrastructure/SwerveModule.cpp | 3 +-- src/main/include/infrastructure/PWMAngleSensor.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index f1d9de9..e2e7ca2 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -445,8 +445,7 @@ void SwerveModule::SetDesiredState(const frc::SwerveModuleState &referenceState) SetTurningPosition(state.angle.Degrees()); - //SetDriveVelocity(state.speed); -------------------------------------------------------debugging - SetDriveVelocity(0_mps); + SetDriveVelocity(state.speed); } void SwerveModule::ResetEncoders() noexcept diff --git a/src/main/include/infrastructure/PWMAngleSensor.h b/src/main/include/infrastructure/PWMAngleSensor.h index 8cbf5c6..c34cda3 100644 --- a/src/main/include/infrastructure/PWMAngleSensor.h +++ b/src/main/include/infrastructure/PWMAngleSensor.h @@ -39,7 +39,7 @@ class AngleSensor std::optional GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept; ctre::phoenix::sensors::CANCoder canCoder_; - int alignment_; + int alignment_{0}; std::unique_ptr digitalInput_; std::unique_ptr dutyCycle_; From a9d8b7f80154677867794551470688e8bfd2d70c Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:06:41 -0500 Subject: [PATCH 014/126] Update Constants.h --- src/main/include/Constants.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 11ef623..2547bfb 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -63,18 +63,18 @@ namespace physical constexpr units::meter_t kWheelBase = 22.5_in; // CAN ID and Digital I/O Port assignments. - constexpr int kFrontLeftTurningMotorCanID = 1; - constexpr int kFrontLeftDriveMotorCanID = 2; - constexpr int kFrontRightTurningMotorCanID = 3; - constexpr int kFrontRightDriveMotorCanID = 4; - constexpr int kRearLeftTurningMotorCanID = 5; - constexpr int kRearLeftDriveMotorCanID = 6; - constexpr int kRearRightTurningMotorCanID = 7; - constexpr int kRearRightDriveMotorCanID = 8; - constexpr int kFrontLeftTurningEncoderPort = 0; - constexpr int kFrontRightTurningEncoderPort = 1; - constexpr int kRearLeftTurningEncoderPort = 2; - constexpr int kRearRightTurningEncoderPort = 3; + constexpr int kFrontLeftTurningMotorCanID = 8; + constexpr int kFrontLeftDriveMotorCanID = 7; + constexpr int kFrontRightTurningMotorCanID = 2; + constexpr int kFrontRightDriveMotorCanID = 1; + constexpr int kRearLeftTurningMotorCanID = 6; + constexpr int kRearLeftDriveMotorCanID = 5; + constexpr int kRearRightTurningMotorCanID = 4; + constexpr int kRearRightDriveMotorCanID = 3; + constexpr int kFrontLeftTurningEncoderPort = 13; + constexpr int kFrontRightTurningEncoderPort = 10; + constexpr int kRearLeftTurningEncoderPort = 12; + constexpr int kRearRightTurningEncoderPort = 11; // These can flip because of gearing. constexpr bool kDriveMotorInverted = false; @@ -129,8 +129,8 @@ namespace pidf namespace nonDrive { - constexpr int kFeederOneCanID = 11; - constexpr int kFeederTwoCanID = 12; - constexpr int kShooterOneCanID = 9; - constexpr int kShooterTwoCanID = 10; + constexpr int kFeederOneCanID = 23; + constexpr int kFeederTwoCanID = 22; + constexpr int kShooterOneCanID = 21; + constexpr int kShooterTwoCanID = 20; } From c8dd38244692a5c594225511a4ac2f627cca1f47 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:30:06 -0500 Subject: [PATCH 015/126] Changed alignment values to match old code, and changed sensor range --- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 4 ++-- src/main/cpp/infrastructure/SwerveModule.cpp | 2 +- src/main/include/Constants.h | 8 ++++---- src/main/include/infrastructure/PWMAngleSensor.h | 4 ++-- src/main/include/infrastructure/SwerveModule.h | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 2d0858b..cf97b19 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -2,10 +2,10 @@ #include "infrastructure/ShuffleboardWidgets.h" -AngleSensor::AngleSensor(int deviceID, int alignment) noexcept +AngleSensor::AngleSensor(int deviceID, double alignment) noexcept : canCoder_(deviceID), alignment_(alignment) { // Configure the sensor range and other settings - canCoder_.ConfigAbsoluteSensorRange(ctre::phoenix::sensors::AbsoluteSensorRange::Unsigned_0_to_360); + canCoder_.ConfigAbsoluteSensorRange(ctre::phoenix::sensors::AbsoluteSensorRange::Signed_PlusMinus180); // Other configuration as needed... } diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index e2e7ca2..4d75d46 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -38,7 +38,7 @@ SwerveModule::SwerveModule( const int driveMotorCanID, const int turningMotorCanID, const int turningEncoderPort, - const int alignmentOffset) noexcept : m_name{name} + const double alignmentOffset) noexcept : m_name{name} { // Set up onboard printf-style logging. std::string logName{"/SwerveModule/"}; diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 2547bfb..34bc050 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -10,10 +10,10 @@ namespace physical { // Alignment constants, for each swerve module. Specified on [-2048, 2048) // "count" scale, in (dimensionless) angular units. - constexpr int kFrontLeftAlignmentOffset = +2064; - constexpr int kFrontRightAlignmentOffset = +445; - constexpr int kRearLeftAlignmentOffset = -16; - constexpr int kRearRightAlignmentOffset = -201; + constexpr double kFrontLeftAlignmentOffset = -280_deg; + constexpr double kFrontRightAlignmentOffset = -309_deg; + constexpr double kRearLeftAlignmentOffset = -332_deg; + constexpr double kRearRightAlignmentOffset = -276_deg; // SDS Mk3 Standard (or Fast) Gear Ratio: 8.16:1 (or 6.86:1); // Nominal Wheel Diameter (4"): =0.1016m; diff --git a/src/main/include/infrastructure/PWMAngleSensor.h b/src/main/include/infrastructure/PWMAngleSensor.h index c34cda3..61bf6c8 100644 --- a/src/main/include/infrastructure/PWMAngleSensor.h +++ b/src/main/include/infrastructure/PWMAngleSensor.h @@ -16,7 +16,7 @@ class AngleSensor { public: - AngleSensor(int deviceID, int alignment) noexcept; + AngleSensor(int deviceID, double alignment) noexcept; AngleSensor(const AngleSensor &) = delete; AngleSensor &operator=(const AngleSensor &) = delete; @@ -39,7 +39,7 @@ class AngleSensor std::optional GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept; ctre::phoenix::sensors::CANCoder canCoder_; - int alignment_{0}; + double alignment_{0}; std::unique_ptr digitalInput_; std::unique_ptr dutyCycle_; diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 7e37040..092dfd4 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -140,7 +140,7 @@ class SwerveModule const int driveMotorCanID, const int turningMotorCanID, const int turningEncoderPort, - const int alignmentOffset) noexcept; + const double alignmentOffset) noexcept; // No copy/assign. SwerveModule(const SwerveModule &) = delete; From 782e8a22edc9ab0511c97b4ba33c6a383d32222c Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:43:53 -0500 Subject: [PATCH 016/126] early int conversion bandaid --- .vscode/settings.json | 3 ++- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 2 +- src/main/cpp/infrastructure/SwerveModule.cpp | 2 +- src/main/include/Constants.h | 8 ++++---- src/main/include/infrastructure/PWMAngleSensor.h | 2 +- src/main/include/infrastructure/SwerveModule.h | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 92d8221..ff50b04 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -90,6 +90,7 @@ "typeindex": "cpp", "typeinfo": "cpp", "valarray": "cpp", - "variant": "cpp" + "variant": "cpp", + "*.inc": "cpp" } } diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index cf97b19..2ee17fa 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -2,7 +2,7 @@ #include "infrastructure/ShuffleboardWidgets.h" -AngleSensor::AngleSensor(int deviceID, double alignment) noexcept +AngleSensor::AngleSensor(int deviceID, int alignment) noexcept : canCoder_(deviceID), alignment_(alignment) { // Configure the sensor range and other settings canCoder_.ConfigAbsoluteSensorRange(ctre::phoenix::sensors::AbsoluteSensorRange::Signed_PlusMinus180); diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index 4d75d46..e2e7ca2 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -38,7 +38,7 @@ SwerveModule::SwerveModule( const int driveMotorCanID, const int turningMotorCanID, const int turningEncoderPort, - const double alignmentOffset) noexcept : m_name{name} + const int alignmentOffset) noexcept : m_name{name} { // Set up onboard printf-style logging. std::string logName{"/SwerveModule/"}; diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 34bc050..0de6e0c 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -10,10 +10,10 @@ namespace physical { // Alignment constants, for each swerve module. Specified on [-2048, 2048) // "count" scale, in (dimensionless) angular units. - constexpr double kFrontLeftAlignmentOffset = -280_deg; - constexpr double kFrontRightAlignmentOffset = -309_deg; - constexpr double kRearLeftAlignmentOffset = -332_deg; - constexpr double kRearRightAlignmentOffset = -276_deg; + constexpr int kFrontLeftAlignmentOffset = -280; + constexpr int kFrontRightAlignmentOffset = -309; + constexpr int kRearLeftAlignmentOffset = -332; + constexpr int kRearRightAlignmentOffset = -276; // SDS Mk3 Standard (or Fast) Gear Ratio: 8.16:1 (or 6.86:1); // Nominal Wheel Diameter (4"): =0.1016m; diff --git a/src/main/include/infrastructure/PWMAngleSensor.h b/src/main/include/infrastructure/PWMAngleSensor.h index 61bf6c8..23d8759 100644 --- a/src/main/include/infrastructure/PWMAngleSensor.h +++ b/src/main/include/infrastructure/PWMAngleSensor.h @@ -16,7 +16,7 @@ class AngleSensor { public: - AngleSensor(int deviceID, double alignment) noexcept; + AngleSensor(int deviceID, int alignment) noexcept; AngleSensor(const AngleSensor &) = delete; AngleSensor &operator=(const AngleSensor &) = delete; diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 092dfd4..7e37040 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -140,7 +140,7 @@ class SwerveModule const int driveMotorCanID, const int turningMotorCanID, const int turningEncoderPort, - const double alignmentOffset) noexcept; + const int alignmentOffset) noexcept; // No copy/assign. SwerveModule(const SwerveModule &) = delete; From 3753fadc9a92f90faa2c40cd1d9286bf0dca14fd Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Mon, 15 Jan 2024 15:19:26 -0500 Subject: [PATCH 017/126] Update Phoenix api to V6, and remove unused old encoder code --- .../cpp/infrastructure/PWMAngleSensor.cpp | 153 +++++------------- src/main/cpp/infrastructure/SwerveModule.cpp | 24 +-- src/main/include/Constants.h | 8 +- .../include/infrastructure/PWMAngleSensor.h | 18 +-- .../include/infrastructure/SwerveModule.h | 2 +- 5 files changed, 62 insertions(+), 143 deletions(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 2ee17fa..57a106a 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -2,10 +2,15 @@ #include "infrastructure/ShuffleboardWidgets.h" -AngleSensor::AngleSensor(int deviceID, int alignment) noexcept - : canCoder_(deviceID), alignment_(alignment) { +AngleSensor::AngleSensor(int deviceID, units::turn_t alignment) noexcept + : canCoder_(deviceID) { // Configure the sensor range and other settings - canCoder_.ConfigAbsoluteSensorRange(ctre::phoenix::sensors::AbsoluteSensorRange::Signed_PlusMinus180); + canCoder_.GetConfigurator().Apply( + ctre::phoenix6::configs::MagnetSensorConfigs() + .WithAbsoluteSensorRange(ctre::phoenix6::signals::AbsoluteSensorRangeValue::Signed_PlusMinusHalf) + .WithMagnetOffset(alignment.value()) + // .WithSensorDirection(ctre::phoenix6::signals::SensorDirectionValue::Clockwise_Positive) + ); // Other configuration as needed... } @@ -28,27 +33,17 @@ void AngleSensor::Periodic() noexcept encoderPosition = std::get<1>(pair); } - const int frequency = dutyCycle_->GetFrequency(); - const double output = dutyCycle_->GetOutput(); - const auto position = GetAbsolutePosition(frequency, output, false); + const auto position = GetAbsolutePositionWithoutAlignment(); const bool status = position.has_value(); - int reportPosition{0}; + const units::turn_t alignment = GetAlignment(); + units::degree_t reportPosition{0}; if (status) { - reportPosition = position.value() + alignment_; - - if (reportPosition > 2047) - { - reportPosition = reportPosition - 4096; - } - if (reportPosition < -2048) - { - reportPosition = reportPosition + 4096; - } + reportPosition = position.value() + alignment; } - const double heading = static_cast(reportPosition) * 360.0 / 4096.0; + const double heading = reportPosition.value(); headingGyro_.Set(heading); @@ -74,27 +69,23 @@ void AngleSensor::Periodic() noexcept encoderError -= 360.0; } - frequencyUI_->GetEntry()->SetInteger(frequency); commandDiscrepancyUI_->GetEntry()->SetDouble(commandedError); statusUI_->GetEntry()->SetBoolean(status); commandedUI_->GetEntry()->SetDouble(commandedPosition.to()); - positionUI_->GetEntry()->SetInteger(reportPosition); - outputUI_->GetEntry()->SetDouble(output); + positionUI_->GetEntry()->SetDouble(reportPosition.to()); encoderDiscrepancyUI_->GetEntry()->SetDouble(encoderError); - alignmentUI_->GetEntry()->SetInteger(alignment_); + alignmentUI_->GetEntry()->SetDouble(alignment.to()); } void AngleSensor::ShuffleboardCreate(frc::ShuffleboardContainer &container, std::function()> getCommandedAndEncoderPositionsF) noexcept { - frequencyUI_ = &container.Add("Frequency", 0) - .WithPosition(0, 0); commandDiscrepancyUI_ = &container.Add("PID Error", 0) - .WithPosition(0, 1); + .WithPosition(0, 0); statusUI_ = &container.Add("Status", false) - .WithPosition(0, 2) + .WithPosition(0, 1) .WithWidget(frc::BuiltInWidgets::kBooleanBox); headingUI_ = &container.Add("Heading", headingGyro_) @@ -109,109 +100,41 @@ void AngleSensor::ShuffleboardCreate(frc::ShuffleboardContainer &container, positionUI_ = &container.Add("Position", 0) .WithPosition(1, 2); - outputUI_ = &container.Add("Output", 0.0) - .WithPosition(2, 0); - encoderDiscrepancyUI_ = &container.Add("Motor Discrepancy", 0) - .WithPosition(2, 1); + .WithPosition(2, 0); alignmentUI_ = &container.Add("Alignment", 0) - .WithPosition(2, 2); + .WithPosition(2, 1); getCommandedAndEncoderPositionsF_ = getCommandedAndEncoderPositionsF; shuffleboard_ = true; } -std::optional AngleSensor::GetAbsolutePosition() noexcept { - double position = canCoder_.GetPosition(); - // Continuously adjust position to fall within [-180 to 180) degree range - while (position > 180.0) { - position -= 360.0; - } - while (position <= -180.0) { - position += 360.0; - } - // Apply alignment offset and return - return units::angle::degree_t{position + alignment_}; -} +units::turn_t AngleSensor::GetAlignment() noexcept { + ctre::phoenix6::configs::MagnetSensorConfigs sensorConfig = ctre::phoenix6::configs::MagnetSensorConfigs(); + canCoder_.GetConfigurator().Refresh(sensorConfig); + return (units::turn_t)sensorConfig.MagnetOffset; +}; -std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept -{ - return GetAbsolutePosition(dutyCycle_->GetFrequency(), dutyCycle_->GetOutput(), false); -} +void AngleSensor::SetAlignment(const units::turn_t alignment) noexcept { + canCoder_.GetConfigurator().Apply( + ctre::phoenix6::configs::MagnetSensorConfigs() + .WithMagnetOffset(alignment.value()) + ); +}; -// Calulate absolute turning position in the range [-2048, +2048), from the raw -// absolute PWM encoder data. No value is returned in the event the absolute -// position sensor itself is not returning valid data. The alignment offset is -// optionally used to establish the zero position. +std::optional AngleSensor::GetAbsolutePosition() noexcept { + units::degree_t position = canCoder_.GetPosition().GetValue(); + // Position will already within [-180 to 180) degree range, offset to match magnet position + return position; +} -// This is a low-level routine, meant to be used only by the version of -// GetAbsolutePosition() with no arguments (above) or, from test mode. It does -// not use standardized units and leaks knowledge of the sensor output range. -std::optional AngleSensor::GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept +std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept { - // SRX MAG ENCODER chip seems to be AS5045B; from the datasheet, position - // is given by: - // ((t_on * 4098) / (t_on + t_off)) - 1; - // if this result is 4096, set it to 4095. - // This computation results in a position in the range [0, 4096). - - // REV Through Bore Encoder chip is AEAT-8800-Q24, which has configurable - // PWM parameters. However, it seems to be configured to match AS5045B, so - // it works without any changes. A factory preset zero offset may allow no - // alignment (simply specify an alignment of zero). - - // If the frequency isn't within the range specified in the data sheet, - // return an error. This range is [220, 268], with a nominal value of 244. - // A tolerance of 12 (~5% of nominal) is provided for any measurment error. - bool absoluteSensorGood = frequency >= 208 && frequency <= 280; - - if (!absoluteSensorGood) - { - return std::nullopt; - } + units::degree_t position = canCoder_.GetPosition().GetValue(); - // GetOutput() is (t_on / (t_on + t_off)); this is all that we have; note - // that this is sampled at a high frequency and the sampling window may not - // be perfectly aligned with the signal, although the frequency should be - // employed to ensure the sampling window is sized to a multiple of the - // period of the signal. So, t_on and t_off have a different scale in this - // context, which is OK. We are using the duty cycle (ratio) only. - - // Multiply by 4098 and subtract 1; should yield 0 - 4094, and also 4096. - // Conditionals cover special case of 4096 and enforce the specified range. - int position = std::lround(output * 4098.0) - 1; - if (position < 0) - { - position = 0; - } - else if (position > 4095) - { - position = 4095; - } - - // Now, shift the range from [0, 4096) to [-2048, +2048). - position -= 2048; - - if (!applyOffset) - { - return position; - } - - // There is a mechanical offset reflecting the alignment of the magnet with - // repect to the axis of rotation of the wheel (and also any variance in - // the alignment of the sensor). Add this in here to reference position to - // the desired zero position. - position += alignment_; - if (position > 2047) - { - position -= 4096; - } - if (position < -2048) - { - position += 4096; - } + position -= GetAlignment(); return position; } diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index e2e7ca2..03936b4 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -38,7 +38,7 @@ SwerveModule::SwerveModule( const int driveMotorCanID, const int turningMotorCanID, const int turningEncoderPort, - const int alignmentOffset) noexcept : m_name{name} + const units::turn_t alignmentOffset) noexcept : m_name{name} { // Set up onboard printf-style logging. std::string logName{"/SwerveModule/"}; @@ -570,8 +570,8 @@ void SwerveModule::TestPeriodic() noexcept m_driveMotor->CheckConfig(); } - // [-2048, +2048) - std::optional position = m_turningPositionPWM->GetAbsolutePositionWithoutAlignment(); + // [-180, +180) + std::optional position = m_turningPositionPWM->GetAbsolutePositionWithoutAlignment(); // This provides a rough means of zeroing the turning position. if (position.has_value()) @@ -579,29 +579,29 @@ void SwerveModule::TestPeriodic() noexcept if (zeroTurning) { // Work out new alignment so position becomes zero. - int alignmentOffset = -position.value(); - if (alignmentOffset == +2048) + units::turn_t alignmentOffset = -position.value(); + if (alignmentOffset == 180_deg) { - alignmentOffset = -2048; + alignmentOffset = -180_deg; } m_turningPositionPWM->SetAlignment(alignmentOffset); - position = 0; + position = 0.0_deg; m_turningPosition = 0.0_deg; } else { // Alignment is [-2048, +2048). position = position.value() + m_turningPositionPWM->GetAlignment(); - if (position > 2047) + if (position > 180_deg) { - position = position.value() - 4096; + position = position.value() - 360_deg; } - if (position < -2048) + if (position < -180_deg) { - position = position.value() + 4096; + position = position.value() + 360_deg; } - m_turningPosition = position.value() / 2048.0 * 180.0_deg; + m_turningPosition = position.value(); } } diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 0de6e0c..6d9ad3c 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -10,10 +10,10 @@ namespace physical { // Alignment constants, for each swerve module. Specified on [-2048, 2048) // "count" scale, in (dimensionless) angular units. - constexpr int kFrontLeftAlignmentOffset = -280; - constexpr int kFrontRightAlignmentOffset = -309; - constexpr int kRearLeftAlignmentOffset = -332; - constexpr int kRearRightAlignmentOffset = -276; + constexpr units::degree_t kFrontLeftAlignmentOffset = -280_deg; + constexpr units::degree_t kFrontRightAlignmentOffset = -309_deg; + constexpr units::degree_t kRearLeftAlignmentOffset = -332_deg; + constexpr units::degree_t kRearRightAlignmentOffset = -276_deg; // SDS Mk3 Standard (or Fast) Gear Ratio: 8.16:1 (or 6.86:1); // Nominal Wheel Diameter (4"): =0.1016m; diff --git a/src/main/include/infrastructure/PWMAngleSensor.h b/src/main/include/infrastructure/PWMAngleSensor.h index 23d8759..026fcbd 100644 --- a/src/main/include/infrastructure/PWMAngleSensor.h +++ b/src/main/include/infrastructure/PWMAngleSensor.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -16,7 +16,7 @@ class AngleSensor { public: - AngleSensor(int deviceID, int alignment) noexcept; + AngleSensor(int deviceID, units::turn_t alignment) noexcept; AngleSensor(const AngleSensor &) = delete; AngleSensor &operator=(const AngleSensor &) = delete; @@ -26,23 +26,19 @@ class AngleSensor void ShuffleboardCreate(frc::ShuffleboardContainer &container, std::function()> getCommandedAndEncoderPositions = nullptr) noexcept; - int GetAlignment() noexcept { return alignment_; } + units::turn_t GetAlignment() noexcept; - void SetAlignment(const int alignment) noexcept { alignment_ = alignment; } + void SetAlignment(const units::turn_t alignment) noexcept; std::optional GetAbsolutePosition() noexcept; - std::optional GetAbsolutePositionWithoutAlignment() noexcept; + std::optional GetAbsolutePositionWithoutAlignment() noexcept; private: // Range is [-2048, +2048). - std::optional GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept; + std::optional GetAbsolutePosition(const int frequency, const double output, const bool applyOffset) noexcept; - ctre::phoenix::sensors::CANCoder canCoder_; - double alignment_{0}; - - std::unique_ptr digitalInput_; - std::unique_ptr dutyCycle_; + ctre::phoenix6::hardware::CANcoder canCoder_; std::function()> getCommandedAndEncoderPositionsF_{nullptr}; HeadingGyro headingGyro_; diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 7e37040..15ee388 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -140,7 +140,7 @@ class SwerveModule const int driveMotorCanID, const int turningMotorCanID, const int turningEncoderPort, - const int alignmentOffset) noexcept; + const units::turn_t alignmentOffset) noexcept; // No copy/assign. SwerveModule(const SwerveModule &) = delete; From 2b13af3647d8c9123ef1fa5cbe188c712c3a808e Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:37:04 -0500 Subject: [PATCH 018/126] Update .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5528d4f..9bb96da 100644 --- a/.gitignore +++ b/.gitignore @@ -176,3 +176,7 @@ logs/ # Folder that has CTRE Phoenix Sim device config storage ctre_sim/ + +# VSCode config files +.vscode/settings.json +.vscode/launch.json From 30e3405e256ff8c86f989d7ff74726f2abf363e0 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 18 Jan 2024 19:55:05 -0500 Subject: [PATCH 019/126] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9bb96da..44bdfbb 100644 --- a/.gitignore +++ b/.gitignore @@ -178,5 +178,6 @@ logs/ ctre_sim/ # VSCode config files +.vscode/* .vscode/settings.json .vscode/launch.json From 8e988dd9c2ef1361de063dfebd93f03fa821988a Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Thu, 1 Feb 2024 18:10:44 -0500 Subject: [PATCH 020/126] Update launch.json --- .vscode/launch.json | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c9c9713..ee83cb1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,21 +1,36 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { "type": "wpilib", "name": "WPILib Desktop Debug", "request": "launch", - "desktop": true, + "desktop": true }, { "type": "wpilib", "name": "WPILib roboRIO Debug", "request": "launch", - "desktop": false, + "desktop": false + }, + { + "name": "C/C++ Runner: Debug Session", + "type": "cppdbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "externalConsole": true, + "cwd": ".", + "program": "build/Debug/outDebug", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] } ] -} +} \ No newline at end of file From 2cbc19da75ff6a6381f8553903c21f25c4205fe4 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Thu, 1 Feb 2024 18:10:48 -0500 Subject: [PATCH 021/126] Update settings.json --- .vscode/settings.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ff50b04..13d1e50 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -92,5 +92,6 @@ "valarray": "cpp", "variant": "cpp", "*.inc": "cpp" - } -} + }, + "C_Cpp_Runner.msvcBatchPath": "" +} \ No newline at end of file From ecf7bdd08646aec309f3da9abaa79a5fa3c79cf0 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Sat, 3 Feb 2024 15:25:13 -0500 Subject: [PATCH 022/126] Added Some Print Values for Troubleshooting --- .vscode/settings.json | 152 +++++++----------- src/main/cpp/Robot.cpp | 2 +- src/main/cpp/RobotContainer.cpp | 13 +- .../cpp/infrastructure/PWMAngleSensor.cpp | 4 + src/main/cpp/subsystems/DriveSubsystem.cpp | 17 +- src/main/include/Constants.h | 2 +- src/main/include/RobotContainer.h | 1 + 7 files changed, 90 insertions(+), 101 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 13d1e50..d2e3661 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,97 +1,59 @@ { - "java.configuration.updateBuildConfiguration": "disabled", - "java.import.gradle.enabled": false, - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "bin/": true, - "**/.classpath": true, - "**/.project": true, - "**/.settings": true, - "**/.factorypath": true, - "**/*~": true - }, - "C_Cpp.default.configurationProvider": "vscode-wpilib", - "files.associations": { - "any": "cpp", - "array": "cpp", - "atomic": "cpp", - "bit": "cpp", - "*.tcc": "cpp", - "bitset": "cpp", - "cctype": "cpp", - "cfenv": "cpp", - "chrono": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "codecvt": "cpp", - "compare": "cpp", - "complex": "cpp", - "concepts": "cpp", - "condition_variable": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "forward_list": "cpp", - "list": "cpp", - "map": "cpp", - "set": "cpp", - "string": "cpp", - "unordered_map": "cpp", - "unordered_set": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "functional": "cpp", - "iterator": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "optional": "cpp", - "random": "cpp", - "ratio": "cpp", - "regex": "cpp", - "string_view": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "utility": "cpp", - "fstream": "cpp", - "future": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "mutex": "cpp", - "new": "cpp", - "numbers": "cpp", - "ostream": "cpp", - "ranges": "cpp", - "semaphore": "cpp", - "span": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "stop_token": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "cinttypes": "cpp", - "typeindex": "cpp", - "typeinfo": "cpp", - "valarray": "cpp", - "variant": "cpp", - "*.inc": "cpp" - }, - "C_Cpp_Runner.msvcBatchPath": "" + "C_Cpp_Runner.msvcBatchPath": "", + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false } \ No newline at end of file diff --git a/src/main/cpp/Robot.cpp b/src/main/cpp/Robot.cpp index e69bd77..aa97add 100644 --- a/src/main/cpp/Robot.cpp +++ b/src/main/cpp/Robot.cpp @@ -55,7 +55,7 @@ void Robot::DisabledExit() noexcept /** * This autonomous runs the autonomous command selected by your {@link - * RobotContainer} class. + * RobotContainer} class.n */ void Robot::AutonomousInit() noexcept { diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 92d4617..21028c0 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -1,7 +1,6 @@ // Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - +// the WPILib BSD license file in the root directory of this pr #include "Constants.h" #include "RobotContainer.h" @@ -24,6 +23,8 @@ #include #include #include +#include +#include RobotContainer::RobotContainer() noexcept { @@ -37,6 +38,7 @@ RobotContainer::RobotContainer() noexcept frc2::CommandPtr RobotContainer::DriveCommandFactory(RobotContainer *container) noexcept { + frc::SmartDashboard::PutNumber("DCF", 0); // Set up default drive command; non-owning pointer is passed by value. auto driveRequirements = {dynamic_cast(&container->m_driveSubsystem)}; @@ -102,6 +104,8 @@ void RobotContainer::AutonomousInit() noexcept void RobotContainer::TeleopInit() noexcept { + //std:string + frc::SmartDashboard::PutNumber("TeleopInitTest", 1); m_driveSubsystem.ClearFaults(); m_feederSubsystem.ClearFaults(); m_shooterSubsystem.ClearFaults(); @@ -261,7 +265,7 @@ std::tuple RobotContainer::GetDriveTeleopControls( // port) side of the robot. Poitive rotation is counter-clockwise. On the // other hand, as the controller is held, the Y axis is aligned with forward. // And, specifically, it is the negative Y axis which extends forward. So, - // the robot's X is the controllers inverted Y. On the controller, the X + // thne robot's X is the controllers inverted Y. On the controller, the X // axis lines up with the robot's Y axis. And, the controller's positive X // extends to the right. So, the robot's Y is the controller's inverted X. // Finally, the other controller joystick is used for commanding rotation and @@ -326,6 +330,9 @@ std::tuple RobotContainer::GetDriveTeleopControls( y *= 2.0; z *= 1.6; } + frc::SmartDashboard::PutNumber("X", x); + frc::SmartDashboard::PutNumber("Y", y); + frc::SmartDashboard::PutNumber("Z", z); return std::make_tuple(x, y, z, m_fieldOriented); } diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 57a106a..36aa1a8 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -1,6 +1,9 @@ #include "infrastructure/PWMAngleSensor.h" #include "infrastructure/ShuffleboardWidgets.h" +#include +#include +#include AngleSensor::AngleSensor(int deviceID, units::turn_t alignment) noexcept : canCoder_(deviceID) { @@ -11,6 +14,7 @@ AngleSensor::AngleSensor(int deviceID, units::turn_t alignment) noexcept .WithMagnetOffset(alignment.value()) // .WithSensorDirection(ctre::phoenix6::signals::SensorDirectionValue::Clockwise_Positive) ); + frc::SmartDashboard::PutNumber("Alignment", alignment.value()); // Other configuration as needed... } diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index 9910f9e..dadabb7 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -10,6 +10,7 @@ #include "infrastructure/SwerveModule.h" #include +#include #include #include #include @@ -991,7 +992,9 @@ void DriveSubsystem::Drive(units::meters_per_second_t xSpeed, m_rotation = rot / physical::kMaxTurnRate; m_x = xSpeed / physical::kMaxDriveSpeed; m_y = ySpeed / physical::kMaxDriveSpeed; - + frc::SmartDashboard::PutNumber("DriveSubXSpeed", m_x); + frc::SmartDashboard::PutNumber("DriveSubYSpeed", m_y); + frc::SmartDashboard::PutNumber("rotation", m_rotation); frc::Rotation2d botRot; DoSafeIMU("GetRotation2d()", [&]() -> void @@ -999,6 +1002,7 @@ void DriveSubsystem::Drive(units::meters_per_second_t xSpeed, if (m_ahrs) { botRot = -m_ahrs->GetRotation2d(); + frc::SmartDashboard::PutNumber("BotRot", botRot.Degrees().value()); } }); if (!m_ahrs) @@ -1029,6 +1033,7 @@ void DriveSubsystem::ResetEncoders() noexcept void DriveSubsystem::SetModuleStates(std::array &desiredStates) noexcept { + auto [frontLeft, frontRight, rearLeft, rearRight] = desiredStates; m_commandedStateFrontLeft = frontLeft; @@ -1084,6 +1089,16 @@ void DriveSubsystem::SetModuleStates(std::array &desi m_rearLeftSwerveModule->SetDesiredState(rearLeft); m_rearRightSwerveModule->SetDesiredState(rearRight); + frc::SmartDashboard::PutNumber("LeftFront Desired Speed", frontLeft.speed.value()); + frc::SmartDashboard::PutNumber("RightFront Desired Speed", frontRight.speed.value()); + frc::SmartDashboard::PutNumber("LeftRear Desired Speed", rearLeft.speed.value()); + frc::SmartDashboard::PutNumber("RightRear Desired Speed", rearRight.speed.value()); + + frc::SmartDashboard::PutNumber("LeftFront Desired Angle", frontLeft.angle.Degrees().value()); + frc::SmartDashboard::PutNumber("RightFront Desired Angle", frontRight.angle.Degrees().value()); + frc::SmartDashboard::PutNumber("LeftRear Desired Angle", rearLeft.angle.Degrees().value()); + frc::SmartDashboard::PutNumber("RightRear Desired Angle", rearRight.angle.Degrees().value()); + } units::degree_t DriveSubsystem::GetHeading() noexcept diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 6d9ad3c..90ac611 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -10,7 +10,7 @@ namespace physical { // Alignment constants, for each swerve module. Specified on [-2048, 2048) // "count" scale, in (dimensionless) angular units. - constexpr units::degree_t kFrontLeftAlignmentOffset = -280_deg; + constexpr units::degree_t kFrontLeftAlignmentOffset = -50_deg; constexpr units::degree_t kFrontRightAlignmentOffset = -309_deg; constexpr units::degree_t kRearLeftAlignmentOffset = -332_deg; constexpr units::degree_t kRearRightAlignmentOffset = -276_deg; diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index 929325c..6bb55db 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -14,6 +14,7 @@ #include "subsystems/FeederSubsystem.h" #include "subsystems/Infrastructure.h" #include "subsystems/ShooterSubsystem.h" +#include #include #include From 53d44bcff53d070e066bc4caf523a1b94ecb95ed Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:35:37 -0500 Subject: [PATCH 023/126] angle offset finding printouts --- .vscode/settings.json | 5 ++++- src/main/cpp/infrastructure/SwerveModule.cpp | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d2e3661..e9d8a36 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -55,5 +55,8 @@ "C_Cpp_Runner.useLeakSanitizer": false, "C_Cpp_Runner.showCompilationTime": false, "C_Cpp_Runner.useLinkTimeOptimization": false, - "C_Cpp_Runner.msvcSecureNoWarnings": false + "C_Cpp_Runner.msvcSecureNoWarnings": false, + "files.associations": { + "*.inc": "cpp" + } } \ No newline at end of file diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index 03936b4..43c377b 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,11 @@ SwerveModule::SwerveModule( // and it's incremental encoder in order to make things work out/line up. m_turningPositionPWM = std::make_unique(turningEncoderPort, alignmentOffset); + // test printout for alignment calibration + if (m_turningPositionPWM->GetAbsolutePosition().has_value()) { + frc::SmartDashboard::PutNumber("Initial Alignment " + std::string(name), m_turningPositionPWM->GetAbsolutePosition().value().value()); + } + // Motor controller configurations are only checked (or saved) in test mode // but a minimal amount is set up in these methods. m_turningMotorBase = SparkFactory::CreateSparkMax(m_name + std::string(" Turning"), turningMotorCanID, m_turningMotorInverted); @@ -437,6 +443,11 @@ void SwerveModule::SetDesiredState(const frc::SwerveModuleState &referenceState) const std::optional position = m_turningPositionPWM->GetAbsolutePosition(); + // test printout for alignment calibration + if (position.has_value()) { + frc::SmartDashboard::PutNumber("Wheel Position " + std::string(m_name), position.value().value()); + } + if (position.has_value()) { m_turningPosition = position.value(); From 24f7b4ef968fd921ce48fe2354333c0ef2245ff3 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:23:52 -0500 Subject: [PATCH 024/126] Added printout to GetAbsolutePosition --- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 36aa1a8..421b529 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -131,7 +131,8 @@ void AngleSensor::SetAlignment(const units::turn_t alignment) noexcept { std::optional AngleSensor::GetAbsolutePosition() noexcept { units::degree_t position = canCoder_.GetPosition().GetValue(); // Position will already within [-180 to 180) degree range, offset to match magnet position - return position; + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Value", position.value()); + return (position / 360_deg) * 1_deg; } std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept From 8f5e86c4c2539ab874ca8f40b47e135d6136bf22 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:27:47 -0500 Subject: [PATCH 025/126] zeroed turn PID for testing --- src/main/include/Constants.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 90ac611..d1b63cd 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -90,8 +90,8 @@ namespace pidf { constexpr units::degrees_per_second_t kTurningPositionMaxVelocity = 2750.0_deg_per_s; constexpr units::degrees_per_second_squared_t kTurningPositionMaxAcceleration = 20000.0_deg_per_s_sq; - constexpr double kTurningPositionP = 0.006; - constexpr double kTurningPositionF = 0.003; + constexpr double kTurningPositionP = 0.0; // previous value .006 + constexpr double kTurningPositionF = 0.0; // previous value .003 constexpr double kTurningPositionI = 0.0; constexpr double kTurningPositionIZ = 0.0; constexpr double kTurningPositionIM = 0.0; From 78ff729e0234a3381454400e60a2ed2bcc522c4a Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:41:29 -0500 Subject: [PATCH 026/126] Revert "zeroed turn PID for testing" This reverts commit 8f5e86c4c2539ab874ca8f40b47e135d6136bf22. --- src/main/include/Constants.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index d1b63cd..90ac611 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -90,8 +90,8 @@ namespace pidf { constexpr units::degrees_per_second_t kTurningPositionMaxVelocity = 2750.0_deg_per_s; constexpr units::degrees_per_second_squared_t kTurningPositionMaxAcceleration = 20000.0_deg_per_s_sq; - constexpr double kTurningPositionP = 0.0; // previous value .006 - constexpr double kTurningPositionF = 0.0; // previous value .003 + constexpr double kTurningPositionP = 0.006; + constexpr double kTurningPositionF = 0.003; constexpr double kTurningPositionI = 0.0; constexpr double kTurningPositionIZ = 0.0; constexpr double kTurningPositionIM = 0.0; From 68489b6b31705050956bff75f07aded09498eb3e Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:44:33 -0500 Subject: [PATCH 027/126] increased tolerance for testing --- src/main/include/infrastructure/SwerveModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 15ee388..2d3d2ee 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -177,7 +177,7 @@ class SwerveModule // Determine if commanded turning position has been achieved, to within // specified tolerance. - bool CheckTurningPosition(const units::angle::degree_t tolerance = 2.5_deg) noexcept; + bool CheckTurningPosition(const units::angle::degree_t tolerance = 60_deg) noexcept; // original tolerance was 2.5_deg // Drive is normally oriented around velocity, but distance enables odometry, // simple dead reckoning, etc. Possibly useful for autonomous driving. From b8451396265c9ba257b5aa4b04dbcc5e51d087bb Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:47:39 -0500 Subject: [PATCH 028/126] Update SwerveModule.h --- src/main/include/infrastructure/SwerveModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 2d3d2ee..cd8da6a 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -177,7 +177,7 @@ class SwerveModule // Determine if commanded turning position has been achieved, to within // specified tolerance. - bool CheckTurningPosition(const units::angle::degree_t tolerance = 60_deg) noexcept; // original tolerance was 2.5_deg + bool CheckTurningPosition(const units::angle::degree_t tolerance = 10_deg) noexcept; // original tolerance was 2.5_deg // Drive is normally oriented around velocity, but distance enables odometry, // simple dead reckoning, etc. Possibly useful for autonomous driving. From b82efd47518d54304f2d13b5f509cec03139aa8c Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:51:06 -0500 Subject: [PATCH 029/126] Revert "Update SwerveModule.h" This reverts commit b8451396265c9ba257b5aa4b04dbcc5e51d087bb. --- src/main/include/infrastructure/SwerveModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index cd8da6a..2d3d2ee 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -177,7 +177,7 @@ class SwerveModule // Determine if commanded turning position has been achieved, to within // specified tolerance. - bool CheckTurningPosition(const units::angle::degree_t tolerance = 10_deg) noexcept; // original tolerance was 2.5_deg + bool CheckTurningPosition(const units::angle::degree_t tolerance = 60_deg) noexcept; // original tolerance was 2.5_deg // Drive is normally oriented around velocity, but distance enables odometry, // simple dead reckoning, etc. Possibly useful for autonomous driving. From ddd90fa3ae7ae7ef95633c91bce208ca1e5f210d Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:51:47 -0500 Subject: [PATCH 030/126] Revert "increased tolerance for testing" This reverts commit 68489b6b31705050956bff75f07aded09498eb3e. --- src/main/include/infrastructure/SwerveModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 2d3d2ee..15ee388 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -177,7 +177,7 @@ class SwerveModule // Determine if commanded turning position has been achieved, to within // specified tolerance. - bool CheckTurningPosition(const units::angle::degree_t tolerance = 60_deg) noexcept; // original tolerance was 2.5_deg + bool CheckTurningPosition(const units::angle::degree_t tolerance = 2.5_deg) noexcept; // Drive is normally oriented around velocity, but distance enables odometry, // simple dead reckoning, etc. Possibly useful for autonomous driving. From b1abb4b6a83b4e78046a8ee078862c8ecc2de1f6 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:17:09 -0500 Subject: [PATCH 031/126] Update PWMAngleSensor.cpp --- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 421b529..1b53b3b 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -127,14 +127,28 @@ void AngleSensor::SetAlignment(const units::turn_t alignment) noexcept { .WithMagnetOffset(alignment.value()) ); }; - +/* std::optional AngleSensor::GetAbsolutePosition() noexcept { units::degree_t position = canCoder_.GetPosition().GetValue(); // Position will already within [-180 to 180) degree range, offset to match magnet position frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Value", position.value()); - return (position / 360_deg) * 1_deg; + return position; +} +*/ +std::optional AngleSensor::GetAbsolutePosition() noexcept { + units::degree_t position = canCoder_.GetPosition().GetValue(); // Assuming this returns units::angle::degree_t + double normalizedPosition = std::fmod(position.value() + 360.0, 360.0); // Normalize to [0, 360) range + + // If normalizedPosition is greater than 180, adjust to [-180, 180) range + if (normalizedPosition > 180.0) { + normalizedPosition -= 360.0; + } + + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Value", normalizedPosition); + return units::angle::degree_t(normalizedPosition); // Construct a new degree_t with the normalized value } + std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept { units::degree_t position = canCoder_.GetPosition().GetValue(); From 1bf10ee1c93110b58f749bda1c771d63d2d28cef Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:44:13 -0500 Subject: [PATCH 032/126] Update PWMAngleSensor.cpp --- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 1b53b3b..429dbc0 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -145,7 +145,7 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep } frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Value", normalizedPosition); - return units::angle::degree_t(normalizedPosition); // Construct a new degree_t with the normalized value + return (units::angle::degree_t(normalizedPosition) / 360_deg) * 1_deg; // Construct a new degree_t with the normalized value } From 5f0733ddea946e77e8fd4cae6a4cc1acebb3498b Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:55:36 -0500 Subject: [PATCH 033/126] Update SwerveModule.h --- src/main/include/infrastructure/SwerveModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 15ee388..49085ee 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -177,7 +177,7 @@ class SwerveModule // Determine if commanded turning position has been achieved, to within // specified tolerance. - bool CheckTurningPosition(const units::angle::degree_t tolerance = 2.5_deg) noexcept; + bool CheckTurningPosition(const units::angle::degree_t tolerance = 10_deg) noexcept; // Drive is normally oriented around velocity, but distance enables odometry, // simple dead reckoning, etc. Possibly useful for autonomous driving. From bc8b03547db848437638476d804324a9de98731e Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:57:14 -0500 Subject: [PATCH 034/126] Revert "Update SwerveModule.h" This reverts commit 5f0733ddea946e77e8fd4cae6a4cc1acebb3498b. --- src/main/include/infrastructure/SwerveModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 49085ee..15ee388 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -177,7 +177,7 @@ class SwerveModule // Determine if commanded turning position has been achieved, to within // specified tolerance. - bool CheckTurningPosition(const units::angle::degree_t tolerance = 10_deg) noexcept; + bool CheckTurningPosition(const units::angle::degree_t tolerance = 2.5_deg) noexcept; // Drive is normally oriented around velocity, but distance enables odometry, // simple dead reckoning, etc. Possibly useful for autonomous driving. From d0bff0a00c65b9cc1abdc6c7187b8b301e6d194e Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:08:33 -0500 Subject: [PATCH 035/126] Revert "Update PWMAngleSensor.cpp" This reverts commit 1bf10ee1c93110b58f749bda1c771d63d2d28cef. --- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 429dbc0..1b53b3b 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -145,7 +145,7 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep } frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Value", normalizedPosition); - return (units::angle::degree_t(normalizedPosition) / 360_deg) * 1_deg; // Construct a new degree_t with the normalized value + return units::angle::degree_t(normalizedPosition); // Construct a new degree_t with the normalized value } From 926f7e20fa7dde55b23635a68bbae47e33d25b03 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:13:00 -0500 Subject: [PATCH 036/126] added a divide by 360 --- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 1b53b3b..6ddd76d 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -137,14 +137,15 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep */ std::optional AngleSensor::GetAbsolutePosition() noexcept { units::degree_t position = canCoder_.GetPosition().GetValue(); // Assuming this returns units::angle::degree_t - double normalizedPosition = std::fmod(position.value() + 360.0, 360.0); // Normalize to [0, 360) range + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Value", position.value()); + + double normalizedPosition = std::fmod((position.value() / 360) + 360.0, 360.0); // Normalize to [0, 360) range // If normalizedPosition is greater than 180, adjust to [-180, 180) range if (normalizedPosition > 180.0) { normalizedPosition -= 360.0; } - frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Value", normalizedPosition); return units::angle::degree_t(normalizedPosition); // Construct a new degree_t with the normalized value } From ea7085bc732b85e15a9ce6b40ed9a5b0dc7548cc Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:46:00 -0500 Subject: [PATCH 037/126] improved printouts --- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 6ddd76d..2f306d0 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -137,7 +137,11 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep */ std::optional AngleSensor::GetAbsolutePosition() noexcept { units::degree_t position = canCoder_.GetPosition().GetValue(); // Assuming this returns units::angle::degree_t - frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Value", position.value()); + int deviceId = canCoder_.GetDeviceID(); // Obtain the CANCoder device ID + std::string deviceIdStr = std::to_string(deviceId); // Convert the device ID to string + + // Use the device ID in the SmartDashboard key + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Absolute ID: " + deviceIdStr, position.value()); double normalizedPosition = std::fmod((position.value() / 360) + 360.0, 360.0); // Normalize to [0, 360) range @@ -146,10 +150,11 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep normalizedPosition -= 360.0; } + // Use the device ID in the adjusted position SmartDashboard key as well + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Adjusted ID: " + deviceIdStr, normalizedPosition); return units::angle::degree_t(normalizedPosition); // Construct a new degree_t with the normalized value } - std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept { units::degree_t position = canCoder_.GetPosition().GetValue(); From e062d32e004a09d7d10b44985f9c5fee13dd2dfa Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:59:45 -0500 Subject: [PATCH 038/126] added bounding to GetAbsolutePositionWithoutAlignment --- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 2f306d0..d75ceb3 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -143,7 +143,7 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep // Use the device ID in the SmartDashboard key frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Absolute ID: " + deviceIdStr, position.value()); - double normalizedPosition = std::fmod((position.value() / 360) + 360.0, 360.0); // Normalize to [0, 360) range + double normalizedPosition = std::fmod(position.value() + 360.0, 360.0); // Normalize to [0, 360) range // If normalizedPosition is greater than 180, adjust to [-180, 180) range if (normalizedPosition > 180.0) { @@ -158,8 +158,19 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept { units::degree_t position = canCoder_.GetPosition().GetValue(); + int deviceId = canCoder_.GetDeviceID(); // Obtain the CANCoder device ID + std::string deviceIdStr = std::to_string(deviceId); // Convert the device ID to string + + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePositionWithoutAlignment Absolute ID: " + deviceIdStr, position.value()); position -= GetAlignment(); - return position; + double normalizedPosition = std::fmod(position.value() + 360.0, 360.0); // Normalize to [0, 360) range + + // If normalizedPosition is greater than 180, adjust to [-180, 180) range + if (normalizedPosition > 180.0) { + normalizedPosition -= 360.0; + } + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePositionWithoutAlignment Adjusted ID: " + deviceIdStr, normalizedPosition); + return units::angle::degree_t(normalizedPosition); // Construct a new degree_t with the normalized value } From 476059e2e807907a758d38013b24a0877489ed24 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 12:44:13 -0500 Subject: [PATCH 039/126] added more detailed test printouts --- .../cpp/infrastructure/PWMAngleSensor.cpp | 5 +++-- src/main/cpp/subsystems/DriveSubsystem.cpp | 21 ++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index d75ceb3..40ed066 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -14,8 +14,9 @@ AngleSensor::AngleSensor(int deviceID, units::turn_t alignment) noexcept .WithMagnetOffset(alignment.value()) // .WithSensorDirection(ctre::phoenix6::signals::SensorDirectionValue::Clockwise_Positive) ); - frc::SmartDashboard::PutNumber("Alignment", alignment.value()); - // Other configuration as needed... + int deviceId = canCoder_.GetDeviceID(); // Obtain the CANCoder device ID + std::string deviceIdStr = std::to_string(deviceId); // Convert the device ID to string + frc::SmartDashboard::PutNumber("Alignment Device ID: " + deviceIdStr, alignment.value()); } void AngleSensor::Periodic() noexcept diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index dadabb7..cc1bca2 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -992,9 +992,9 @@ void DriveSubsystem::Drive(units::meters_per_second_t xSpeed, m_rotation = rot / physical::kMaxTurnRate; m_x = xSpeed / physical::kMaxDriveSpeed; m_y = ySpeed / physical::kMaxDriveSpeed; - frc::SmartDashboard::PutNumber("DriveSubXSpeed", m_x); - frc::SmartDashboard::PutNumber("DriveSubYSpeed", m_y); - frc::SmartDashboard::PutNumber("rotation", m_rotation); + frc::SmartDashboard::PutNumber("DriveSubXSpeed Pre ToSwerveModuleStates", m_x); + frc::SmartDashboard::PutNumber("DriveSubYSpeed Pre ToSwerveModuleStates", m_y); + frc::SmartDashboard::PutNumber("rotation Pre ToSwerveModuleStates", m_rotation); frc::Rotation2d botRot; DoSafeIMU("GetRotation2d()", [&]() -> void @@ -1020,6 +1020,21 @@ void DriveSubsystem::Drive(units::meters_per_second_t xSpeed, kDriveKinematics.DesaturateWheelSpeeds(&states, physical::kMaxDriveSpeed); + + // Iterate over the states array and put each module's speed and angle to SmartDashboard + std::array moduleNames = {"FrontLeft", "FrontRight", "RearLeft", "RearRight"}; + for (size_t i = 0; i < states.size(); ++i) { + std::string speedKey = moduleNames[i] + " Speed Post ToSwerveModuleStates"; + std::string angleKey = moduleNames[i] + " Angle Post ToSwerveModuleStates"; + + // Convert speed to meters per second and angle to degrees for display + double speedMps = states[i].speed.to(); + double angleDeg = states[i].angle.Degrees().value(); + + frc::SmartDashboard::PutNumber(speedKey, speedMps); + frc::SmartDashboard::PutNumber(angleKey, angleDeg); + } + SetModuleStates(states); } From d6566d0ab1742239d5ad742c9d001c160b1e5c50 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 13:30:02 -0500 Subject: [PATCH 040/126] set default to field oriented --- src/main/cpp/RobotContainer.cpp | 3 ++- src/main/include/RobotContainer.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 21028c0..9cb4b23 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -111,7 +111,8 @@ void RobotContainer::TeleopInit() noexcept m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); - + m_driveSubsystem.ZeroHeading(); + m_driveSubsystem.SetDefaultCommand(DriveCommandFactory(this)); m_feederSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void { m_feederSubsystem.Default(m_xbox.GetRightTriggerAxis()); }, diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index 6bb55db..842d247 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -53,7 +53,7 @@ class RobotContainer void ConfigureBindings() noexcept; - bool m_fieldOriented{false}; + bool m_fieldOriented{true}; bool m_lock{false}; bool m_slow{false}; double m_shooterVelocity{0.0}; From 096f939ec39e8d21e6905b30b86e6575415cb5f1 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Sat, 10 Feb 2024 15:35:30 -0500 Subject: [PATCH 041/126] Remove unused subsystems Co-authored-by: Alan Everett Co-authored-by: Scott --- build.gradle | 2 +- src/main/cpp/RobotContainer.cpp | 214 +++++++++--------- src/main/cpp/infrastructure/SwerveModule.cpp | 9 +- src/main/cpp/subsystems/DriveSubsystem.cpp | 26 ++- src/main/include/Constants.h | 4 +- src/main/include/RobotContainer.h | 4 +- .../include/infrastructure/SwerveModule.h | 7 +- 7 files changed, 139 insertions(+), 127 deletions(-) diff --git a/build.gradle b/build.gradle index db8855c..749489b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id "cpp" id "google-test-test-suite" - id "edu.wpi.first.GradleRIO" version "2024.1.1" + id "edu.wpi.first.GradleRIO" version "2024.2.1" } // Define my targets (RoboRIO) and artifacts (deployable files) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 9cb4b23..564e8da 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -87,17 +87,17 @@ frc2::CommandPtr RobotContainer::PointCommandFactory(RobotContainer *container) void RobotContainer::AutonomousInit() noexcept { m_driveSubsystem.ClearFaults(); - m_feederSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); + // m_feederSubsystem.ClearFaults(); + // m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, {&m_driveSubsystem})); - m_feederSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, - {&m_feederSubsystem})); - m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, - {&m_shooterSubsystem})); + // m_feederSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, + // {&m_feederSubsystem})); + // m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, + // {&m_shooterSubsystem})); m_infrastructureSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, {&m_infrastructureSubsystem})); } @@ -107,19 +107,19 @@ void RobotContainer::TeleopInit() noexcept //std:string frc::SmartDashboard::PutNumber("TeleopInitTest", 1); m_driveSubsystem.ClearFaults(); - m_feederSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); + // m_feederSubsystem.ClearFaults(); + // m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.ZeroHeading(); m_driveSubsystem.SetDefaultCommand(DriveCommandFactory(this)); - m_feederSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void - { m_feederSubsystem.Default(m_xbox.GetRightTriggerAxis()); }, - {&m_feederSubsystem})); - m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void - { m_shooterSubsystem.Default(m_xbox.GetLeftTriggerAxis(), m_shooterVelocity); }, - {&m_shooterSubsystem})); + // m_feederSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void + // { m_feederSubsystem.Default(m_xbox.GetRightTriggerAxis()); }, + // {&m_feederSubsystem})); + // m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void + // { m_shooterSubsystem.Default(m_xbox.GetLeftTriggerAxis(), m_shooterVelocity); }, + // {&m_shooterSubsystem})); m_infrastructureSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void { m_infrastructureSubsystem.SetLEDPattern(m_LEDPattern); }, {&m_infrastructureSubsystem})); @@ -146,83 +146,83 @@ void RobotContainer::ConfigureBindings() noexcept {&m_driveSubsystem}) .ToPtr()); - m_xbox.LeftBumper().WhileTrue(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.Fire(); }, - {&m_feederSubsystem}) - .ToPtr()); - m_xbox.LeftBumper().OnFalse(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.NoFeed(); }, - {&m_feederSubsystem}) - .ToPtr()); - - m_xbox.RightBumper().WhileTrue(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.Eject(); }, - {&m_feederSubsystem}) - .ToPtr()); - m_xbox.RightBumper().OnFalse(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.NoFeed(); }, - {&m_feederSubsystem}) - .ToPtr()); - - m_xbox.Start().WhileTrue(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.Raise(); }, - {&m_feederSubsystem}) - .ToPtr()); - - m_xbox.Back().WhileTrue(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.Lower(); }, - {&m_feederSubsystem}) - .ToPtr()); - - frc2::POVButton(&m_xbox, 90).WhileTrue(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.LockIntake(); }, - {&m_feederSubsystem}) - .ToPtr()); - - frc2::POVButton(&m_xbox, 270).WhileTrue(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.DropIntake(); }, - {&m_feederSubsystem}) - .ToPtr()); - - frc2::POVButton(&m_xbox, 0).WhileTrue(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.RaiseIntake(); }, - {&m_feederSubsystem}) - .ToPtr()); - - frc2::POVButton(&m_xbox, 180).WhileTrue(frc2::InstantCommand([&]() -> void - { m_feederSubsystem.LowerIntake(); }, - {&m_feederSubsystem}) - .ToPtr()); - - frc2::JoystickButton(&m_buttonBoard, 5).OnTrue(frc2::InstantCommand([&]() -> void - { m_shooterVelocity = -500.0; }, - {}) - .ToPtr()); - - frc2::JoystickButton(&m_buttonBoard, 6).OnTrue(frc2::InstantCommand([&]() -> void - { m_lock = true; }, - {}) - .ToPtr()); - - frc2::JoystickButton(&m_buttonBoard, 6).OnFalse(frc2::InstantCommand([&]() -> void - { m_lock = false; }, - {}) - .ToPtr()); - - frc2::JoystickButton(&m_buttonBoard, 10).OnTrue(frc2::InstantCommand([&]() -> void - { m_shooterVelocity = 1320.0; }, - {}) - .ToPtr()); - - frc2::JoystickButton(&m_buttonBoard, 11).OnTrue(frc2::InstantCommand([&]() -> void - { m_shooterVelocity = 930.0; }, - {}) - .ToPtr()); - - frc2::JoystickButton(&m_buttonBoard, 12).OnTrue(frc2::InstantCommand([&]() -> void - { m_shooterVelocity = 400.0; }, - {}) - .ToPtr()); + // m_xbox.LeftBumper().WhileTrue(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.Fire(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + // m_xbox.LeftBumper().OnFalse(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.NoFeed(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + + // m_xbox.RightBumper().WhileTrue(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.Eject(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + // m_xbox.RightBumper().OnFalse(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.NoFeed(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + + // m_xbox.Start().WhileTrue(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.Raise(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + + // m_xbox.Back().WhileTrue(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.Lower(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + + // frc2::POVButton(&m_xbox, 90).WhileTrue(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.LockIntake(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + + // frc2::POVButton(&m_xbox, 270).WhileTrue(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.DropIntake(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + + // frc2::POVButton(&m_xbox, 0).WhileTrue(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.RaiseIntake(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + + // frc2::POVButton(&m_xbox, 180).WhileTrue(frc2::InstantCommand([&]() -> void + // { m_feederSubsystem.LowerIntake(); }, + // {&m_feederSubsystem}) + // .ToPtr()); + + // frc2::JoystickButton(&m_buttonBoard, 5).OnTrue(frc2::InstantCommand([&]() -> void + // { m_shooterVelocity = -500.0; }, + // {}) + // .ToPtr()); + + // frc2::JoystickButton(&m_buttonBoard, 6).OnTrue(frc2::InstantCommand([&]() -> void + // { m_lock = true; }, + // {}) + // .ToPtr()); + + // frc2::JoystickButton(&m_buttonBoard, 6).OnFalse(frc2::InstantCommand([&]() -> void + // { m_lock = false; }, + // {}) + // .ToPtr()); + + // frc2::JoystickButton(&m_buttonBoard, 10).OnTrue(frc2::InstantCommand([&]() -> void + // { m_shooterVelocity = 1320.0; }, + // {}) + // .ToPtr()); + + // frc2::JoystickButton(&m_buttonBoard, 11).OnTrue(frc2::InstantCommand([&]() -> void + // { m_shooterVelocity = 930.0; }, + // {}) + // .ToPtr()); + + // frc2::JoystickButton(&m_buttonBoard, 12).OnTrue(frc2::InstantCommand([&]() -> void + // { m_shooterVelocity = 400.0; }, + // {}) + // .ToPtr()); frc2::JoystickButton(&m_buttonBoard, 7).OnTrue(frc2::InstantCommand([&]() -> void { ++m_LEDPattern; @@ -343,14 +343,14 @@ void RobotContainer::TestInit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_feederSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); + // m_feederSubsystem.ClearFaults(); + // m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.TestInit(); - m_feederSubsystem.TestInit(); - m_shooterSubsystem.TestInit(); + // m_feederSubsystem.TestInit(); + // m_shooterSubsystem.TestInit(); frc::SendableChooser> *chooser{m_driveSubsystem.TestModeChooser()}; @@ -375,25 +375,25 @@ void RobotContainer::TestExit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_feederSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); + // m_feederSubsystem.ClearFaults(); + // m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.TestExit(); - m_feederSubsystem.TestExit(); - m_shooterSubsystem.TestExit(); + // m_feederSubsystem.TestExit(); + // m_shooterSubsystem.TestExit(); m_driveSubsystem.BurnConfig(); - m_feederSubsystem.BurnConfig(); - m_shooterSubsystem.BurnConfig(); + // m_feederSubsystem.BurnConfig(); + // m_shooterSubsystem.BurnConfig(); } void RobotContainer::TestPeriodic() noexcept { m_driveSubsystem.TestPeriodic(); - m_feederSubsystem.TestPeriodic(); - m_shooterSubsystem.TestPeriodic(); + // m_feederSubsystem.TestPeriodic(); + // m_shooterSubsystem.TestPeriodic(); } void RobotContainer::DisabledInit() noexcept @@ -401,8 +401,8 @@ void RobotContainer::DisabledInit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_feederSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); + // m_feederSubsystem.ClearFaults(); + // m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); @@ -415,8 +415,8 @@ void RobotContainer::DisabledInit() noexcept void RobotContainer::DisabledExit() noexcept { m_driveSubsystem.ClearFaults(); - m_feederSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); + // m_feederSubsystem.ClearFaults(); + // m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index 43c377b..3618e85 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -337,6 +337,11 @@ bool SwerveModule::CheckTurningPosition(const units::angle::degree_t tolerance) return error >= -tolerance && error < tolerance; } +void SwerveModule::StopTurning() noexcept +{ + m_turningMotor->Set(0); +} + // Drive position and velocity are in rotations and rotations/second, // respectively. Brake/coast also applies here; brake only in distance mode, // once distance has been reached or in velocity mode, if commanded velocity is @@ -782,8 +787,8 @@ void SwerveModule::TurningPositionPID(double P, double I, double IZ, double IM, m_turningPosition_D = D; m_turningPosition_DF = DF; m_turningPosition_F = F; - m_turningPosition_V = V; - m_turningPosition_A = A; + m_turningPosition_V = units::degrees_per_second_t{V}; + m_turningPosition_A = units::degrees_per_second_squared_t{A}; SetTurningPositionPID(); } diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index cc1bca2..1de5eb8 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -1056,6 +1056,16 @@ void DriveSubsystem::SetModuleStates(std::array &desi m_commandedStateRearLeft = rearLeft; m_commandedStateRearRight = rearRight; + frc::SmartDashboard::PutNumber("LeftFront Desired Speed", frontLeft.speed.value()); + frc::SmartDashboard::PutNumber("RightFront Desired Speed", frontRight.speed.value()); + frc::SmartDashboard::PutNumber("LeftRear Desired Speed", rearLeft.speed.value()); + frc::SmartDashboard::PutNumber("RightRear Desired Speed", rearRight.speed.value()); + + frc::SmartDashboard::PutNumber("LeftFront Desired Angle", frontLeft.angle.Degrees().value()); + frc::SmartDashboard::PutNumber("RightFront Desired Angle", frontRight.angle.Degrees().value()); + frc::SmartDashboard::PutNumber("LeftRear Desired Angle", rearLeft.angle.Degrees().value()); + frc::SmartDashboard::PutNumber("RightRear Desired Angle", rearRight.angle.Degrees().value()); + // Don't command turning if there is no drive; this is used from Drive(), and // it winds up causing the modules to all home to zero any time there is no // joystick input. This check causes them to stay where they are, which is @@ -1071,6 +1081,11 @@ void DriveSubsystem::SetModuleStates(std::array &desi m_rearLeftSwerveModule->SetDriveVelocity(0.0_mps); m_rearRightSwerveModule->SetDriveVelocity(0.0_mps); + m_frontLeftSwerveModule->StopTurning(); + m_frontRightSwerveModule->StopTurning(); + m_rearLeftSwerveModule->StopTurning(); + m_rearRightSwerveModule->StopTurning(); + return; } @@ -1103,17 +1118,6 @@ void DriveSubsystem::SetModuleStates(std::array &desi m_frontRightSwerveModule->SetDesiredState(frontRight); m_rearLeftSwerveModule->SetDesiredState(rearLeft); m_rearRightSwerveModule->SetDesiredState(rearRight); - - frc::SmartDashboard::PutNumber("LeftFront Desired Speed", frontLeft.speed.value()); - frc::SmartDashboard::PutNumber("RightFront Desired Speed", frontRight.speed.value()); - frc::SmartDashboard::PutNumber("LeftRear Desired Speed", rearLeft.speed.value()); - frc::SmartDashboard::PutNumber("RightRear Desired Speed", rearRight.speed.value()); - - frc::SmartDashboard::PutNumber("LeftFront Desired Angle", frontLeft.angle.Degrees().value()); - frc::SmartDashboard::PutNumber("RightFront Desired Angle", frontRight.angle.Degrees().value()); - frc::SmartDashboard::PutNumber("LeftRear Desired Angle", rearLeft.angle.Degrees().value()); - frc::SmartDashboard::PutNumber("RightRear Desired Angle", rearRight.angle.Degrees().value()); - } units::degree_t DriveSubsystem::GetHeading() noexcept diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 90ac611..3b868fd 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -59,8 +59,8 @@ namespace physical // Drivebase geometry: distance between centers of right and left wheels on // robot; distance between centers of front and back wheels on robot. - constexpr units::meter_t kTrackWidth = 22.5_in; - constexpr units::meter_t kWheelBase = 22.5_in; + constexpr units::meter_t kTrackWidth = 24.5_in; + constexpr units::meter_t kWheelBase = 24.5_in; // CAN ID and Digital I/O Port assignments. constexpr int kFrontLeftTurningMotorCanID = 8; diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index 842d247..f3a8483 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -62,9 +62,9 @@ class RobotContainer // The robot's subsystems and commands are defined here... DriveSubsystem m_driveSubsystem; - FeederSubsystem m_feederSubsystem; + // FeederSubsystem m_feederSubsystem; InfrastructureSubsystem m_infrastructureSubsystem; - ShooterSubsystem m_shooterSubsystem; + // ShooterSubsystem m_shooterSubsystem; frc2::CommandXboxController m_xbox{0}; frc2::CommandGenericHID m_buttonBoard{1}; diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 15ee388..65b2b9f 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -179,6 +179,9 @@ class SwerveModule // specified tolerance. bool CheckTurningPosition(const units::angle::degree_t tolerance = 2.5_deg) noexcept; + // Stop all output to turning motors to save power when stationary. + void StopTurning() noexcept; + // Drive is normally oriented around velocity, but distance enables odometry, // simple dead reckoning, etc. Possibly useful for autonomous driving. @@ -285,8 +288,8 @@ class SwerveModule double m_turningPosition_D{pidf::kTurningPositionD}; double m_turningPosition_DF{pidf::kTurningPositionDF}; double m_turningPosition_F{pidf::kTurningPositionF}; - double m_turningPosition_V{pidf::kTurningPositionMaxVelocity}; - double m_turningPosition_A{pidf::kTurningPositionMaxAcceleration}; + units::degrees_per_second_t m_turningPosition_V{pidf::kTurningPositionMaxVelocity}; + units::degrees_per_second_squared_t m_turningPosition_A{pidf::kTurningPositionMaxAcceleration}; // Drive position PID double m_drivePosition_P{pidf::kDrivePositionP}; From 8ed1c4d19dd34c60490406dce840d5190c8bfbad Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:50:22 -0500 Subject: [PATCH 042/126] update angle modulus division --- .../cpp/infrastructure/PWMAngleSensor.cpp | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index 40ed066..c2f0621 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -4,6 +4,7 @@ #include #include #include +#include AngleSensor::AngleSensor(int deviceID, units::turn_t alignment) noexcept : canCoder_(deviceID) { @@ -144,16 +145,10 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep // Use the device ID in the SmartDashboard key frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Absolute ID: " + deviceIdStr, position.value()); - double normalizedPosition = std::fmod(position.value() + 360.0, 360.0); // Normalize to [0, 360) range - - // If normalizedPosition is greater than 180, adjust to [-180, 180) range - if (normalizedPosition > 180.0) { - normalizedPosition -= 360.0; - } - - // Use the device ID in the adjusted position SmartDashboard key as well - frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Adjusted ID: " + deviceIdStr, normalizedPosition); - return units::angle::degree_t(normalizedPosition); // Construct a new degree_t with the normalized value + // Convert to radians and back to use MathUtil AngleModulus + units::degree_t normalizedPosition = frc::AngleModulus(position); + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Adjusted ID: " + deviceIdStr, normalizedPosition.value()); + return normalizedPosition; } std::optional AngleSensor::GetAbsolutePositionWithoutAlignment() noexcept @@ -166,12 +161,8 @@ std::optional AngleSensor::GetAbsolutePositionWithoutAlign position -= GetAlignment(); - double normalizedPosition = std::fmod(position.value() + 360.0, 360.0); // Normalize to [0, 360) range - - // If normalizedPosition is greater than 180, adjust to [-180, 180) range - if (normalizedPosition > 180.0) { - normalizedPosition -= 360.0; - } - frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePositionWithoutAlignment Adjusted ID: " + deviceIdStr, normalizedPosition); - return units::angle::degree_t(normalizedPosition); // Construct a new degree_t with the normalized value + // Convert to radians and back to use MathUtil AngleModulus + units::degree_t normalizedPosition = frc::AngleModulus(position); + frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePositionWithoutAlignment Adjusted ID: " + deviceIdStr, normalizedPosition.value()); + return normalizedPosition; } From b417a93c3f4616556020deb9dfba4ea6eed95523 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Sat, 10 Feb 2024 18:54:18 -0500 Subject: [PATCH 043/126] misc corrections Co-Authored-By: Scott Co-Authored-By: Alan Everett --- src/main/cpp/RobotContainer.cpp | 16 ++++++++-------- src/main/cpp/infrastructure/PWMAngleSensor.cpp | 2 ++ src/main/cpp/infrastructure/SwerveModule.cpp | 14 +++++++++++++- src/main/cpp/subsystems/DriveSubsystem.cpp | 4 +++- src/main/include/Constants.h | 4 ++-- src/main/include/RobotContainer.h | 2 +- src/main/include/infrastructure/SwerveModule.h | 1 + 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 564e8da..e23c4ed 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -224,12 +224,12 @@ void RobotContainer::ConfigureBindings() noexcept // {}) // .ToPtr()); - frc2::JoystickButton(&m_buttonBoard, 7).OnTrue(frc2::InstantCommand([&]() -> void - { ++m_LEDPattern; - if (m_LEDPattern >= m_LEDPatternCount) { m_LEDPattern = 0; } - std::printf("LED Pattern[%u]: %s\n", m_LEDPattern, std::string(m_infrastructureSubsystem.GetLEDPatternDescription(m_LEDPattern)).c_str()); }, - {}) - .ToPtr()); + // frc2::JoystickButton(&m_buttonBoard, 7).OnTrue(frc2::InstantCommand([&]() -> void + // { ++m_LEDPattern; + // if (m_LEDPattern >= m_LEDPatternCount) { m_LEDPattern = 0; } + // std::printf("LED Pattern[%u]: %s\n", m_LEDPattern, std::string(m_infrastructureSubsystem.GetLEDPatternDescription(m_LEDPattern)).c_str()); }, + // {}) + // .ToPtr()); } std::optional RobotContainer::GetAutonomousCommand() noexcept @@ -293,7 +293,7 @@ std::tuple RobotContainer::GetDriveTeleopControls( auto shape = [](double raw, double mixer = 0.75) -> double { // Input deadband around 0.0 (+/- range). - constexpr double range = 0.05; + constexpr double range = 0.07; constexpr double slope = 1.0 / (1.0 - range); @@ -319,7 +319,7 @@ std::tuple RobotContainer::GetDriveTeleopControls( y = shape(y); z = shape(z, 0.0); - if (m_slow || m_buttonBoard.GetRawButton(9)) + if (m_slow) // || m_buttonBoard.GetRawButton(9) { x *= 0.50; y *= 0.50; diff --git a/src/main/cpp/infrastructure/PWMAngleSensor.cpp b/src/main/cpp/infrastructure/PWMAngleSensor.cpp index c2f0621..d0b4f0b 100644 --- a/src/main/cpp/infrastructure/PWMAngleSensor.cpp +++ b/src/main/cpp/infrastructure/PWMAngleSensor.cpp @@ -147,6 +147,8 @@ std::optional AngleSensor::GetAbsolutePosition() noexcep // Convert to radians and back to use MathUtil AngleModulus units::degree_t normalizedPosition = frc::AngleModulus(position); + + // Use the device ID in the adjusted position SmartDashboard key as well frc::SmartDashboard::PutNumber("AngleSensor::GetAbsolutePosition Adjusted ID: " + deviceIdStr, normalizedPosition.value()); return normalizedPosition; } diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index 3618e85..5f2b1f8 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -214,6 +214,14 @@ void SwerveModule::Periodic() noexcept return; } + // If no drive speed is applied, don't bother turning + if (m_turningStopped) + { + m_turningMotor->Set(0); + m_turningStopped = false; + return; + } + // Update (and apply below) turning position PID. double calculated = m_rioPIDController->Calculate(m_turningPosition); @@ -231,6 +239,8 @@ void SwerveModule::Periodic() noexcept calculated -= m_rioPID_F; } + frc::SmartDashboard::PutNumber(std::string(m_name) + "_TurningOutput", calculated); + // Use voltage compensation, to offset low battery voltage. m_turningMotor->SetVoltage(calculated * 12.0_V); } @@ -339,7 +349,7 @@ bool SwerveModule::CheckTurningPosition(const units::angle::degree_t tolerance) void SwerveModule::StopTurning() noexcept { - m_turningMotor->Set(0); + m_turningStopped = true; } // Drive position and velocity are in rotations and rotations/second, @@ -414,6 +424,8 @@ void SwerveModule::SetDriveVelocity(units::velocity::meters_per_second_t velocit const units::angle::degree_t angleError = m_turningPosition - m_commandedHeading; const double vectorAlignment = std::cos(units::angle::radian_t{angleError}.to()); + frc::SmartDashboard::PutNumber(std::string(m_name) + "_VectorAlignment", vectorAlignment); + #if 0 m_driveMotor->SeekVelocity(velocity * vectorAlignment); #else diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index 1de5eb8..3b7eb49 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -1076,6 +1076,7 @@ void DriveSubsystem::SetModuleStates(std::array &desi rearLeft.speed == 0.0_mps && rearRight.speed == 0.0_mps) { + frc::SmartDashboard::PutBoolean("Stopped", true); m_frontLeftSwerveModule->SetDriveVelocity(0.0_mps); m_frontRightSwerveModule->SetDriveVelocity(0.0_mps); m_rearLeftSwerveModule->SetDriveVelocity(0.0_mps); @@ -1085,10 +1086,11 @@ void DriveSubsystem::SetModuleStates(std::array &desi m_frontRightSwerveModule->StopTurning(); m_rearLeftSwerveModule->StopTurning(); m_rearRightSwerveModule->StopTurning(); - return; } + frc::SmartDashboard::PutBoolean("Stopped", false); + // m_limit is always unity, except in Test Mode. So, by default, it does not // modify anything here. In Test Mode, it can be used to slow things down. frontLeft.speed *= m_limit; diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 3b868fd..3dca135 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -90,8 +90,8 @@ namespace pidf { constexpr units::degrees_per_second_t kTurningPositionMaxVelocity = 2750.0_deg_per_s; constexpr units::degrees_per_second_squared_t kTurningPositionMaxAcceleration = 20000.0_deg_per_s_sq; - constexpr double kTurningPositionP = 0.006; - constexpr double kTurningPositionF = 0.003; + constexpr double kTurningPositionP = 0.002; // originally 0.006 + constexpr double kTurningPositionF = 0.000; // originally 0.003 constexpr double kTurningPositionI = 0.0; constexpr double kTurningPositionIZ = 0.0; constexpr double kTurningPositionIM = 0.0; diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index f3a8483..98093da 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -67,5 +67,5 @@ class RobotContainer // ShooterSubsystem m_shooterSubsystem; frc2::CommandXboxController m_xbox{0}; - frc2::CommandGenericHID m_buttonBoard{1}; + // frc2::CommandGenericHID m_buttonBoard{1}; }; diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index 65b2b9f..a75586c 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -277,6 +277,7 @@ class SwerveModule // roboRIO. Setting m_rio false allows testing turning PID on the SPARK MAX. const bool m_rio{true}; bool m_brakeApplied{true}; + bool m_turningStopped{false}; double m_rioPID_F{pidf::kTurningPositionF}; std::unique_ptr> m_rioPIDController; From 6ae9f01a0733a2615863dfbd8cdef5497f810354 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 21:15:11 -0500 Subject: [PATCH 044/126] remove unneeded debug statements --- src/main/cpp/subsystems/DriveSubsystem.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index 3b7eb49..f799757 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -992,9 +992,6 @@ void DriveSubsystem::Drive(units::meters_per_second_t xSpeed, m_rotation = rot / physical::kMaxTurnRate; m_x = xSpeed / physical::kMaxDriveSpeed; m_y = ySpeed / physical::kMaxDriveSpeed; - frc::SmartDashboard::PutNumber("DriveSubXSpeed Pre ToSwerveModuleStates", m_x); - frc::SmartDashboard::PutNumber("DriveSubYSpeed Pre ToSwerveModuleStates", m_y); - frc::SmartDashboard::PutNumber("rotation Pre ToSwerveModuleStates", m_rotation); frc::Rotation2d botRot; DoSafeIMU("GetRotation2d()", [&]() -> void @@ -1020,21 +1017,6 @@ void DriveSubsystem::Drive(units::meters_per_second_t xSpeed, kDriveKinematics.DesaturateWheelSpeeds(&states, physical::kMaxDriveSpeed); - - // Iterate over the states array and put each module's speed and angle to SmartDashboard - std::array moduleNames = {"FrontLeft", "FrontRight", "RearLeft", "RearRight"}; - for (size_t i = 0; i < states.size(); ++i) { - std::string speedKey = moduleNames[i] + " Speed Post ToSwerveModuleStates"; - std::string angleKey = moduleNames[i] + " Angle Post ToSwerveModuleStates"; - - // Convert speed to meters per second and angle to degrees for display - double speedMps = states[i].speed.to(); - double angleDeg = states[i].angle.Degrees().value(); - - frc::SmartDashboard::PutNumber(speedKey, speedMps); - frc::SmartDashboard::PutNumber(angleKey, angleDeg); - } - SetModuleStates(states); } From ad63fd2df07c179809cff5b2cf3269e080359c89 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 21:16:34 -0500 Subject: [PATCH 045/126] added field relative Enabled printout --- src/main/cpp/subsystems/DriveSubsystem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index f799757..c67cb52 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -1007,6 +1007,8 @@ void DriveSubsystem::Drive(units::meters_per_second_t xSpeed, fieldRelative = false; } + frc::SmartDashboard::PutBoolean("Field Relative", fieldRelative); + // Center of rotation argument is defaulted to the center of the robot above, // but it is also possible to rotate about a different point. wpi::array states = kDriveKinematics.ToSwerveModuleStates( From 82329d1c4cf978367dc230eda3dea1dc9416ee59 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 21:38:23 -0500 Subject: [PATCH 046/126] removed unneeded printout --- src/main/cpp/RobotContainer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index e23c4ed..ec6353d 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -38,7 +38,6 @@ RobotContainer::RobotContainer() noexcept frc2::CommandPtr RobotContainer::DriveCommandFactory(RobotContainer *container) noexcept { - frc::SmartDashboard::PutNumber("DCF", 0); // Set up default drive command; non-owning pointer is passed by value. auto driveRequirements = {dynamic_cast(&container->m_driveSubsystem)}; From 0fdbb41d5485d454c140547aeafab4f6170d821c Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 22:07:40 -0500 Subject: [PATCH 047/126] make debug print label more consistent --- src/main/cpp/infrastructure/SwerveModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index 5f2b1f8..86720fa 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -462,7 +462,7 @@ void SwerveModule::SetDesiredState(const frc::SwerveModuleState &referenceState) // test printout for alignment calibration if (position.has_value()) { - frc::SmartDashboard::PutNumber("Wheel Position " + std::string(m_name), position.value().value()); + frc::SmartDashboard::PutNumber(std::string(m_name) + " Actual Angle", position.value().value()); } if (position.has_value()) From 270967365b9a28654e194a78ff99298bceaababb Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 23:26:51 -0500 Subject: [PATCH 048/126] Create DriveTrainCodePaths.txt --- Docs/DriveTrainCodePaths.txt | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Docs/DriveTrainCodePaths.txt diff --git a/Docs/DriveTrainCodePaths.txt b/Docs/DriveTrainCodePaths.txt new file mode 100644 index 0000000..475788a --- /dev/null +++ b/Docs/DriveTrainCodePaths.txt @@ -0,0 +1,35 @@ +Init Path (Runs Once) + RobotContainer::TeleopInit + sets default run command to RobotContainer::DriveCommandFactory + + SwerveModule::SwerveModule + PID inits + +Periodic Paths (Runs at 20Hz) + SwerveModule::Periodic + calculates the next PID step + applies calculated step to turning motor + + RobotContainer::GetDriveTeleopControls + gets controller input and applies deadzone + returns the modified controller input + RobotContainer::DriveCommandFactory + multiplies controller output by max drive/turn speed in constants file + sends the multiplied controller input to drive + DriveSubsystem::Drive + divides controller output by max drive/turn speed in constants file + disables field relative if no gyro + convert controller output to swerve module states (hold speed and angle) + DriveSubsystem::SetModuleStates + stops drive and turn motors if all desired speeds are zero + sends desired speed and angle to individual modules + SwerveModule::SetDesiredState + calls AngleSensor::GetAbsolutePosition + returns current angle bounded [-180, 180) + performs turning optimization + calls SwerveModule::SetTurningPosition + bounds angle between [-180, 180) + sets m_rioPIDController set point to bounded angle + calls SwerveModule::SetDriveVelocity + does some questionable math to set a voltage + I did not see a PID in this code path From a97460aecf68b18ece8ea3c9b7a62a013eebec87 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 10 Feb 2024 23:36:02 -0500 Subject: [PATCH 049/126] Update DriveTrainCodePaths.txt --- Docs/DriveTrainCodePaths.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/DriveTrainCodePaths.txt b/Docs/DriveTrainCodePaths.txt index 475788a..1ce9464 100644 --- a/Docs/DriveTrainCodePaths.txt +++ b/Docs/DriveTrainCodePaths.txt @@ -5,7 +5,7 @@ Init Path (Runs Once) SwerveModule::SwerveModule PID inits -Periodic Paths (Runs at 20Hz) +Periodic Paths (Runs at 50Hz) SwerveModule::Periodic calculates the next PID step applies calculated step to turning motor From b200819682f0a9a00b0fe8930e069d79867a404c Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 11 Feb 2024 00:13:11 -0500 Subject: [PATCH 050/126] PID problem debug statements --- src/main/cpp/infrastructure/SwerveModule.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index 86720fa..cc40ad1 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -322,6 +322,7 @@ void SwerveModule::SetTurningPosition(const units::angle::degree_t position) noe m_commandedHeading = adjustedPosition; m_rioPIDController->SetGoal(adjustedPosition); + frc::SmartDashboard::PutNumber(m_name + " Turning PID Setpoint Angle: ", adjustedPosition.value()); if (m_rio || m_testModeControl || m_testModeTurningVoltage != 0.0) { @@ -460,15 +461,12 @@ void SwerveModule::SetDesiredState(const frc::SwerveModuleState &referenceState) const std::optional position = m_turningPositionPWM->GetAbsolutePosition(); - // test printout for alignment calibration - if (position.has_value()) { - frc::SmartDashboard::PutNumber(std::string(m_name) + " Actual Angle", position.value().value()); - } - if (position.has_value()) { m_turningPosition = position.value(); + frc::SmartDashboard::PutNumber(std::string(m_name) + " Turning PID Measured Angle", m_turningPosition.value()); state = frc::SwerveModuleState::Optimize(referenceState, frc::Rotation2d(m_turningPosition)); + frc::SmartDashboard::PutNumber(std::string(m_name) + " Angle Optimization Delta", m_turningPosition.value() - state.angle.Degrees().value()); } SetTurningPosition(state.angle.Degrees()); From dc7e78c95c220b6828549e3f54635d813eae5212 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 11 Feb 2024 01:06:54 -0500 Subject: [PATCH 051/126] remove unneeded debug --- src/main/cpp/RobotContainer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index ec6353d..7ae49f3 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -103,8 +103,6 @@ void RobotContainer::AutonomousInit() noexcept void RobotContainer::TeleopInit() noexcept { - //std:string - frc::SmartDashboard::PutNumber("TeleopInitTest", 1); m_driveSubsystem.ClearFaults(); // m_feederSubsystem.ClearFaults(); // m_shooterSubsystem.ClearFaults(); From 811ca1e7031eb85bf605eb3727bfcd01464d8260 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Wed, 14 Feb 2024 22:28:59 -0500 Subject: [PATCH 052/126] added PID error print --- src/main/cpp/infrastructure/SwerveModule.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index cc40ad1..917e135 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -224,6 +224,7 @@ void SwerveModule::Periodic() noexcept // Update (and apply below) turning position PID. double calculated = m_rioPIDController->Calculate(m_turningPosition); + frc::SmartDashboard::PutNumber(std::string(m_name) + "_PID Error", m_rioPIDController->GetPositionError().value()); // Feedforward is a form of open-loop control. For turning, there is not // much to do, but add in a constant value based only on the direction of From 1350a253db71976eb874972068f6fd6dae00c0a2 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Thu, 15 Feb 2024 18:58:11 -0500 Subject: [PATCH 053/126] Changed PID Constants, increased tolerance for PID sensors, added needed(?) includes for shootersubsytem.h --- src/main/cpp/RobotContainer.cpp | 2 +- src/main/cpp/infrastructure/SwerveModule.cpp | 4 ++-- src/main/include/Constants.h | 20 +++++++++---------- .../include/infrastructure/SwerveModule.h | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 7ae49f3..fcf4412 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -316,7 +316,7 @@ std::tuple RobotContainer::GetDriveTeleopControls( y = shape(y); z = shape(z, 0.0); - if (m_slow) // || m_buttonBoard.GetRawButton(9) + if (true) // || m_buttonBoard.GetRawButton(9) { x *= 0.50; y *= 0.50; diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index cc40ad1..aa9b0df 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -224,6 +224,7 @@ void SwerveModule::Periodic() noexcept // Update (and apply below) turning position PID. double calculated = m_rioPIDController->Calculate(m_turningPosition); + frc::SmartDashboard::PutNumber(std::string(m_name) + " Turning PID Measured Angle", m_turningPosition.value()); // Feedforward is a form of open-loop control. For turning, there is not // much to do, but add in a constant value based only on the direction of @@ -464,9 +465,8 @@ void SwerveModule::SetDesiredState(const frc::SwerveModuleState &referenceState) if (position.has_value()) { m_turningPosition = position.value(); - frc::SmartDashboard::PutNumber(std::string(m_name) + " Turning PID Measured Angle", m_turningPosition.value()); state = frc::SwerveModuleState::Optimize(referenceState, frc::Rotation2d(m_turningPosition)); - frc::SmartDashboard::PutNumber(std::string(m_name) + " Angle Optimization Delta", m_turningPosition.value() - state.angle.Degrees().value()); + frc::SmartDashboard::PutNumber(std::string(m_name) + " Angle Optimization Delta", referenceState.angle.Degrees().value() - state.angle.Degrees().value()); } SetTurningPosition(state.angle.Degrees()); diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 3dca135..dbffdcf 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -8,12 +8,12 @@ namespace physical { - // Alignment constants, for each swerve module. Specified on [-2048, 2048) - // "count" scale, in (dimensionless) angular units. - constexpr units::degree_t kFrontLeftAlignmentOffset = -50_deg; - constexpr units::degree_t kFrontRightAlignmentOffset = -309_deg; - constexpr units::degree_t kRearLeftAlignmentOffset = -332_deg; - constexpr units::degree_t kRearRightAlignmentOffset = -276_deg; + // Alignment constants, for each swerve module. Specified on [-180, 180) + // "count" scale, in deg. From driver station view: +clockwise -counterclockwise. + constexpr units::degree_t kFrontLeftAlignmentOffset = -92_deg; + constexpr units::degree_t kFrontRightAlignmentOffset = 93_deg; + constexpr units::degree_t kRearLeftAlignmentOffset = 26_deg; + constexpr units::degree_t kRearRightAlignmentOffset = 131_deg; // SDS Mk3 Standard (or Fast) Gear Ratio: 8.16:1 (or 6.86:1); // Nominal Wheel Diameter (4"): =0.1016m; @@ -90,17 +90,17 @@ namespace pidf { constexpr units::degrees_per_second_t kTurningPositionMaxVelocity = 2750.0_deg_per_s; constexpr units::degrees_per_second_squared_t kTurningPositionMaxAcceleration = 20000.0_deg_per_s_sq; - constexpr double kTurningPositionP = 0.002; // originally 0.006 - constexpr double kTurningPositionF = 0.000; // originally 0.003 + constexpr double kTurningPositionP = 0.00395; // originally 0.006 concrete 0.00395 + constexpr double kTurningPositionF = 0.0; // originally 0.003 concrete 0.003 constexpr double kTurningPositionI = 0.0; constexpr double kTurningPositionIZ = 0.0; constexpr double kTurningPositionIM = 0.0; - constexpr double kTurningPositionD = 0.0; + constexpr double kTurningPositionD = 0.000; constexpr double kTurningPositionDF = 0.0; constexpr double kDrivePositionMaxVelocity = 5700.0; // Rotations per minute. constexpr double kDrivePositionMaxAcceleration = 1000.0; // Rotations per minute per second. - constexpr double kDrivePositionP = 0.004; + constexpr double kDrivePositionP = 0.000; constexpr double kDrivePositionF = 0.0; constexpr double kDrivePositionI = 0.0; constexpr double kDrivePositionIZ = 0.0; diff --git a/src/main/include/infrastructure/SwerveModule.h b/src/main/include/infrastructure/SwerveModule.h index a75586c..c9c1927 100644 --- a/src/main/include/infrastructure/SwerveModule.h +++ b/src/main/include/infrastructure/SwerveModule.h @@ -177,7 +177,7 @@ class SwerveModule // Determine if commanded turning position has been achieved, to within // specified tolerance. - bool CheckTurningPosition(const units::angle::degree_t tolerance = 2.5_deg) noexcept; + bool CheckTurningPosition(const units::angle::degree_t tolerance = 5_deg) noexcept; // Stop all output to turning motors to save power when stationary. void StopTurning() noexcept; From 0bd9d3bcdbd5d0d971a5ea7e4292ea57962dd386 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Thu, 15 Feb 2024 18:58:26 -0500 Subject: [PATCH 054/126] Update ShooterSubsystem.h --- src/main/include/subsystems/ShooterSubsystem.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/include/subsystems/ShooterSubsystem.h b/src/main/include/subsystems/ShooterSubsystem.h index 9d3df4a..55e058e 100644 --- a/src/main/include/subsystems/ShooterSubsystem.h +++ b/src/main/include/subsystems/ShooterSubsystem.h @@ -1,11 +1,26 @@ #pragma once +#include "Constants.h" #include "infrastructure/SparkMax.h" + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include +#include +#include +#include +#include class ShooterSubsystem : public frc2::SubsystemBase { From f5b8d6ae103a00c81b4911701b8c91cf8574b111 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 15 Feb 2024 19:09:06 -0500 Subject: [PATCH 055/126] Create NotesOnPID.txt --- Docs/NotesOnPID.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Docs/NotesOnPID.txt diff --git a/Docs/NotesOnPID.txt b/Docs/NotesOnPID.txt new file mode 100644 index 0000000..dd29368 --- /dev/null +++ b/Docs/NotesOnPID.txt @@ -0,0 +1,9 @@ +P ~= 1/360 + +update frequency likely too high for anything other than P + +things to try + output could be capped + increased low error sensitivity + nonlinear output scaling + increased low/high error sensitivity, decreased opposite From 030e8f7618ca0016c4185bdd56d5521b839da337 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Thu, 15 Feb 2024 19:38:10 -0500 Subject: [PATCH 056/126] OCD Reorginization of Can IDs, added Shooter and intake Can IDs --- src/main/include/Constants.h | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index dbffdcf..ff9a855 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -63,18 +63,24 @@ namespace physical constexpr units::meter_t kWheelBase = 24.5_in; // CAN ID and Digital I/O Port assignments. - constexpr int kFrontLeftTurningMotorCanID = 8; - constexpr int kFrontLeftDriveMotorCanID = 7; - constexpr int kFrontRightTurningMotorCanID = 2; constexpr int kFrontRightDriveMotorCanID = 1; - constexpr int kRearLeftTurningMotorCanID = 6; - constexpr int kRearLeftDriveMotorCanID = 5; - constexpr int kRearRightTurningMotorCanID = 4; + constexpr int kFrontRightTurningMotorCanID = 2; constexpr int kRearRightDriveMotorCanID = 3; - constexpr int kFrontLeftTurningEncoderPort = 13; + constexpr int kRearRightTurningMotorCanID = 4; + constexpr int kRearLeftDriveMotorCanID = 5; + constexpr int kRearLeftTurningMotorCanID = 6; + constexpr int kFrontLeftDriveMotorCanID = 7; + constexpr int kFrontLeftTurningMotorCanID = 8; constexpr int kFrontRightTurningEncoderPort = 10; - constexpr int kRearLeftTurningEncoderPort = 12; constexpr int kRearRightTurningEncoderPort = 11; + constexpr int kRearLeftTurningEncoderPort = 12; + constexpr int kFrontLeftTurningEncoderPort = 13; + constexpr int kLeftShooterMotorCanID = 14; + constexpr int kRightShooterMotorCanID = 15; + constexpr int kIntakeArmMotorCanID = 16; + constexpr int kIntakeSpinMotorCanID = 17; + + // These can flip because of gearing. constexpr bool kDriveMotorInverted = false; From edb6ff16441aa6790f9cb9794b2dd03bbef23787 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Thu, 15 Feb 2024 20:06:23 -0500 Subject: [PATCH 057/126] Update Constants.h --- src/main/include/Constants.h | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index ff9a855..bbef786 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -75,10 +75,7 @@ namespace physical constexpr int kRearRightTurningEncoderPort = 11; constexpr int kRearLeftTurningEncoderPort = 12; constexpr int kFrontLeftTurningEncoderPort = 13; - constexpr int kLeftShooterMotorCanID = 14; - constexpr int kRightShooterMotorCanID = 15; - constexpr int kIntakeArmMotorCanID = 16; - constexpr int kIntakeSpinMotorCanID = 17; + @@ -135,8 +132,27 @@ namespace pidf namespace nonDrive { - constexpr int kFeederOneCanID = 23; - constexpr int kFeederTwoCanID = 22; - constexpr int kShooterOneCanID = 21; - constexpr int kShooterTwoCanID = 20; + +} + +namespace shooter +{ + constexpr int kLeftShooterMotorCanID = 14; + constexpr int kRightShooterMotorCanID = 15; + + constexpr int kShooterMotorSpeed = 0; + +} + +namespace intake +{ + constexpr int kIntakeArmMotorCanID = 16; + constexpr int kIntakeSpinMotorCanID = 17; + + /* NOTE!!!: The intake arm values are NOT final, are subject to change + after testing*/ + constexpr units::degree_t kIntakeArmHome = 90.0_deg; + constexpr units::degree_t kIntakeArmPickup = 180.0_deg; + constexpr units::degree_t kIntakeArmLoad = 0.0_deg; } + From 9533ccce1a4ed79f5c1e59a0518b84bb1950aafb Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:49:04 -0500 Subject: [PATCH 058/126] Create SubsystemNotes.txt --- Docs/SubsystemNotes.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Docs/SubsystemNotes.txt diff --git a/Docs/SubsystemNotes.txt b/Docs/SubsystemNotes.txt new file mode 100644 index 0000000..0f1714d --- /dev/null +++ b/Docs/SubsystemNotes.txt @@ -0,0 +1,8 @@ +Shooter subsystems + 2 motors + both shoot and one inverted + +Intake subsystems + 2 motors + one moves arm position and needs control loop + one moves intake From 807eb2bf3c21bf32db25a614d051547e7300b0ee Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:50:30 -0500 Subject: [PATCH 059/126] autonomous command refactor --- src/main/cpp/RobotContainer.cpp | 12 +- src/main/cpp/commands/AutonomousCommands.cpp | 248 +----------------- .../include/commands/AutonomousCommands.h | 34 +-- 3 files changed, 4 insertions(+), 290 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index fcf4412..0ef57f5 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -231,22 +231,12 @@ void RobotContainer::ConfigureBindings() noexcept std::optional RobotContainer::GetAutonomousCommand() noexcept { -#if 0 - if (m_buttonBoard.GetRawButton(9)) - { - return TwoBallAuto::TwoBallAutoCommandFactory(&m_driveSubsystem, &m_feederSubsystem, &m_infrastructureSubsystem, &m_shooterSubsystem); - } - else - { - return OneBallAuto::OneBallAutoCommandFactory(&m_driveSubsystem, &m_feederSubsystem, &m_infrastructureSubsystem, &m_shooterSubsystem); - } -#endif - frc::TrajectoryConfig trajectoryConfig{4.0_mps, 2.0_mps_sq}; frc::SwerveDriveKinematics<4> kinematics{m_driveSubsystem.kDriveKinematics}; trajectoryConfig.SetKinematics(kinematics); + // TODO: Update trajectory path to our autonomous path frc::Trajectory trajectory = frc::TrajectoryGenerator::GenerateTrajectory( {frc::Pose2d{}, frc::Pose2d{1.0_m, 0.0_m, frc::Rotation2d{}}}, diff --git a/src/main/cpp/commands/AutonomousCommands.cpp b/src/main/cpp/commands/AutonomousCommands.cpp index 8602b06..8722a57 100644 --- a/src/main/cpp/commands/AutonomousCommands.cpp +++ b/src/main/cpp/commands/AutonomousCommands.cpp @@ -14,15 +14,13 @@ void TimedAutoBase::Initialize() noexcept m_drive->ResetDrive(); m_drive->ZeroHeading(); + // TODO: update to match our intake m_feeder->Default(0.0); m_feeder->LockIntake(); m_feeder->RaiseIntake(); m_shooter->Stop(); - m_infrastructure->SetLEDPattern(95); - - pressure_ = false; finished_ = false; FPGATime_ = frc::RobotController::GetFPGATime(); counter_ = 0; @@ -35,256 +33,12 @@ void TimedAutoBase::End(bool interrupted) noexcept m_feeder->Default(0.0); m_shooter->Stop(); - - m_infrastructure->SetLEDPattern(95); } void TimedAutoBase::Execute() noexcept { const uint64_t FPGATime = frc::RobotController::GetFPGATime(); const uint deltaTime = (FPGATime - FPGATime_) / 1000; // ms - - if (deltaTime < 100) // 100ms - { - return; - } - - FPGATime_ = FPGATime; - - // Will arrive here every 100ms, for 15s autonomous period. - - const units::pressure::pounds_per_square_inch_t pressure = m_infrastructure->GetPressure(); - - if (pressure >= 75_psi) - { - pressure_ = true; - } - - if (!pressure_) - { - m_infrastructure->SetLEDPattern(78); - - return; - } - - // Assuming pneumatics were prefilled, counter runs 1 - ~150. - if (Iteration(++counter_)) - { - m_infrastructure->SetLEDPattern(95); - - finished_ = true; - } -} - -bool OneBallAuto::Iteration(const uint counter) noexcept -{ - // Sit still and run intake/elevator. - if (counter <= 5) // 0.0 - 0.5s - { - m_drive->Drive(0_mps, 0_mps, 0_deg_per_s, false); - - m_feeder->Default(1.0); - m_feeder->LockIntake(); - m_feeder->RaiseIntake(); - - m_shooter->Stop(); - - m_infrastructure->SetLEDPattern(79); - - return false; - } - - // Drop intake (first ball is preloaded). - if (counter <= 10) // 0.5 - 1.0s - { - m_feeder->DropIntake(); - - m_infrastructure->SetLEDPattern(80); - - return false; - } - - // Reextend drop catches, lower intake. - if (counter <= 15) // 1.0 - 1.5s - { - m_feeder->LockIntake(); - m_feeder->LowerIntake(); - - m_infrastructure->SetLEDPattern(81); - - return false; - } - - // Back up and spin up shooter. - if (counter <= 35) // 1.5 - 3.5s - { - m_drive->Drive(-0.55_mps, 0_mps, 0_deg_per_s, false); - - m_feeder->Default(0.0); - - m_shooter->Default(1.0, 1125.0); - - m_infrastructure->SetLEDPattern(82); - - return false; - } - - // Stop. - if (counter <= 40) // 3.5 - 4.0s - { - m_drive->Drive(0_mps, 0_mps, 0_deg_per_s, false); - - m_infrastructure->SetLEDPattern(83); - - return false; - } - - // Take the shot! - if (counter <= 60) // 4.0 - 6.0s - { - m_feeder->Fire(); - - m_infrastructure->SetLEDPattern(84); - - return false; - } - - return true; -} - -bool TwoBallAuto::Iteration(const uint counter) noexcept -{ - // Sit still. - if (counter <= 5) // 0.0 - 0.5s - { - m_drive->Drive(0_mps, 0_mps, 0_deg_per_s, false); - - m_feeder->Default(0.0); - m_feeder->LockIntake(); - m_feeder->RaiseIntake(); - - m_shooter->Stop(); - - m_infrastructure->SetLEDPattern(79); - - return false; - } - - // Drop intake (first ball is preloaded). - if (counter <= 10) // 0.5 - 1.0s - { - m_feeder->DropIntake(); - - m_infrastructure->SetLEDPattern(80); - - return false; - } - - // Reextend drop catches, lower intake. - if (counter <= 15) // 1.0 - 1.5s - { - m_feeder->LockIntake(); - m_feeder->LowerIntake(); - - m_infrastructure->SetLEDPattern(81); - - return false; - } - - // Drive forward, run intake. - if (counter <= 30) // 1.5 - 3.0s - { - m_drive->Drive(0.55_mps, 0_mps, 0_deg_per_s, false); - - m_feeder->Default(1.0); - - m_infrastructure->SetLEDPattern(82); - - return false; - } - - // Stop. - if (counter <= 35) // 3.0 - 3.5s - { - m_drive->Drive(0_mps, 0_mps, 0_deg_per_s, false); - - m_infrastructure->SetLEDPattern(83); - - return false; - } - - // Turn around and spin up shooter. - if (counter < 85) // 3.5 - 8.5s - { - if (m_drive->SetTurnToAngle(180_deg)) - { - m_infrastructure->SetLEDPattern(85); - } - else - { - m_infrastructure->SetLEDPattern(84); - } - - m_shooter->Default(1.0, 1125.0); - - return false; - } - else if (counter == 85) - { - m_drive->Drive(0_mps, 0_mps, 0_deg_per_s, false); - m_drive->ResetDrive(); - m_drive->ZeroHeading(); - - m_infrastructure->SetLEDPattern(86); - - m_shooter->Default(1.0, 1125.0); - - return false; - } - - // Spin up shooter. - if (counter <= 90) // 8.5 - 9.0s - { - m_shooter->Default(1.0, 1125.0); - - m_feeder->Default(0.0); - - m_infrastructure->SetLEDPattern(87); - - return false; - } - - // Take first shot! - if (counter <= 110) // 9.0 - 11.0s - { - m_feeder->Fire(); - - m_infrastructure->SetLEDPattern(88); - - return false; - } - - // Feed and hold second shot. - if (counter <= 120) // 11.0 - 12.0s - { - m_feeder->Default(1.0); - - m_infrastructure->SetLEDPattern(89); - - return false; - } - - // Take second, buzzer beater shot! - if (counter <= 150) // 13.0 - 15.0s - { - m_feeder->Fire(); - - m_infrastructure->SetLEDPattern(90); - - return false; - } - - return true; } frc2::CommandPtr TrajectoryAuto::TrajectoryAutoCommandFactory(DriveSubsystem *const drive, std::string_view name, frc::Trajectory &trajectory) noexcept diff --git a/src/main/include/commands/AutonomousCommands.h b/src/main/include/commands/AutonomousCommands.h index 2d0d8e4..501b928 100644 --- a/src/main/include/commands/AutonomousCommands.h +++ b/src/main/include/commands/AutonomousCommands.h @@ -21,8 +21,8 @@ class TimedAutoBase : public frc2::CommandHelper { protected: - TimedAutoBase(DriveSubsystem *const drive, FeederSubsystem *const feeder, InfrastructureSubsystem *const infrastructure, ShooterSubsystem *const shooter, std::string_view name) noexcept - : m_drive{drive}, m_feeder{feeder}, m_infrastructure{infrastructure}, m_shooter{shooter} { SetName(name); } + TimedAutoBase(DriveSubsystem *const drive, FeederSubsystem *const feeder, ShooterSubsystem *const shooter, std::string_view name) noexcept + : m_drive{drive}, m_feeder{feeder}, m_shooter{shooter} { SetName(name); } public: virtual ~TimedAutoBase() noexcept = default; @@ -40,44 +40,14 @@ class TimedAutoBase : public frc2::CommandHelper protected: DriveSubsystem *const m_drive; FeederSubsystem *const m_feeder; - InfrastructureSubsystem *const m_infrastructure; ShooterSubsystem *const m_shooter; private: - bool pressure_{false}; bool finished_{false}; uint64_t FPGATime_{0}; uint counter_{0}; }; -class OneBallAuto : public TimedAutoBase -{ -public: - OneBallAuto(DriveSubsystem *const drive, FeederSubsystem *const feeder, InfrastructureSubsystem *const infrastructure, ShooterSubsystem *const shooter) noexcept - : TimedAutoBase(drive, feeder, infrastructure, shooter, "OneBallAuto") {} - - bool Iteration(const uint counter) noexcept override; - - static frc2::CommandPtr OneBallAutoCommandFactory(DriveSubsystem *const drive, FeederSubsystem *const feeder, InfrastructureSubsystem *const infrastructure, ShooterSubsystem *const shooter) noexcept - { - return frc2::CommandPtr{std::make_unique(drive, feeder, infrastructure, shooter)}; - } -}; - -class TwoBallAuto : public TimedAutoBase -{ -public: - TwoBallAuto(DriveSubsystem *const drive, FeederSubsystem *const feeder, InfrastructureSubsystem *const infrastructure, ShooterSubsystem *const shooter) noexcept - : TimedAutoBase(drive, feeder, infrastructure, shooter, "TwoBallAuto") {} - - bool Iteration(const uint counter) noexcept override; - - static frc2::CommandPtr TwoBallAutoCommandFactory(DriveSubsystem *const drive, FeederSubsystem *const feeder, InfrastructureSubsystem *const infrastructure, ShooterSubsystem *const shooter) noexcept - { - return frc2::CommandPtr{std::make_unique(drive, feeder, infrastructure, shooter)}; - } -}; - namespace TrajectoryAuto { frc2::CommandPtr TrajectoryAutoCommandFactory(DriveSubsystem *const drive, std::string_view name, frc::Trajectory &trajectory) noexcept; From 85ce527a4a1dce213f37c8c715c4ebb83877a1ed Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:51:10 -0500 Subject: [PATCH 060/126] shooter subsystem update --- src/main/cpp/subsystems/ShooterSubsystem.cpp | 98 +++++++------------ src/main/include/Constants.h | 13 +-- .../include/subsystems/ShooterSubsystem.h | 16 ++- 3 files changed, 47 insertions(+), 80 deletions(-) diff --git a/src/main/cpp/subsystems/ShooterSubsystem.cpp b/src/main/cpp/subsystems/ShooterSubsystem.cpp index 265f615..9a0c1cb 100644 --- a/src/main/cpp/subsystems/ShooterSubsystem.cpp +++ b/src/main/cpp/subsystems/ShooterSubsystem.cpp @@ -14,8 +14,14 @@ ShooterSubsystem::ShooterSubsystem() noexcept {"kIdleMode", uint{0}}, }; - m_shooterMotorBase = SparkFactory::CreateSparkMax("Shooter", 13, false); - m_backspinMotorBase = SparkFactory::CreateSparkMax("Backspin", 14, true); + m_shooterMotorBase = SparkFactory::CreateSparkMax( + shooter::kLeftShooterMotorName, + shooter::kLeftShooterMotorCanID, + shooter::kLeftShooterMotorIsInverted); + m_backspinMotorBase = SparkFactory::CreateSparkMax( + shooter::kRightShooterMotorName, + shooter::kRightShooterMotorCanID, + shooter::kRightShooterMotorIsInverted); m_shooterMotor = std::make_unique>(*m_shooterMotorBase); m_backspinMotor = std::make_unique>(*m_backspinMotorBase); @@ -32,12 +38,37 @@ void ShooterSubsystem::Periodic() noexcept m_backspinMotor->Periodic(); } +void ShooterSubsystem::Default(const double percent) noexcept +{ + m_shooterMotor->SetVoltage(percent * 12.00_V); + m_backspinMotor->SetVoltage(percent * 12.00_V); +} + +void ShooterSubsystem::Stop() noexcept +{ + m_shooterMotor->Stop(); + m_backspinMotor->Stop(); +} + +void ShooterSubsystem::BurnConfig() noexcept +{ + m_shooterMotor->ApplyConfig(true); + m_backspinMotor->ApplyConfig(true); +} + +void ShooterSubsystem::ClearFaults() noexcept +{ + m_shooterMotor->ClearFaults(); + m_backspinMotor->ClearFaults(); +} + bool ShooterSubsystem::GetStatus() const noexcept { return m_shooterMotor->GetStatus() || m_backspinMotor->GetStatus(); } +#pragma region Test void ShooterSubsystem::TestInit() noexcept {} void ShooterSubsystem::TestExit() noexcept {} void ShooterSubsystem::TestPeriodic() noexcept @@ -53,65 +84,4 @@ void ShooterSubsystem::TestPeriodic() noexcept m_backspinMotor->CheckConfig(); } } - -void ShooterSubsystem::Default(const double percent, const double velocity) noexcept -{ - // Manual control (backup). - if (velocity == 0.0) - { - m_shooterMotor->SetVoltage(percent * 12.00_V); - m_backspinMotor->SetVoltage(percent * 11.25_V); - - return; - } - - // On/off trigger control (off here). - if (percent < 0.25) - { - m_shooterMotor->Stop(); - m_backspinMotor->Stop(); - - return; - } - -// Unfortunately, SPARK MAX has too much velocity measurement latency for this. -#if 0 - // RPM. - const double shooterVelocity = m_shooterMotor->GetVelocityRaw(); - // const double backspinVelocity = m_backspinMotor->GetVelocityRaw(); - - // Bang-bang velocity control (simple). - if (shooterVelocity < velocity) - { - m_shooterMotor->Set(1.0); - } - else - { - m_shooterMotor->Stop(); - } - -#else - - m_shooterMotor->SetVoltage(velocity / 1500.0 * 12.00_V); - m_backspinMotor->SetVoltage(velocity / 1500.0 * 11.25_V); - -#endif -} - -void ShooterSubsystem::Stop() noexcept -{ - m_shooterMotor->Stop(); - m_backspinMotor->Stop(); -} - -void ShooterSubsystem::BurnConfig() noexcept -{ - m_shooterMotor->ApplyConfig(true); - m_backspinMotor->ApplyConfig(true); -} - -void ShooterSubsystem::ClearFaults() noexcept -{ - m_shooterMotor->ClearFaults(); - m_backspinMotor->ClearFaults(); -} +#pragma endregion diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index bbef786..d7858d0 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -130,18 +130,19 @@ namespace pidf constexpr double kDriveThetaD = 0.0; } -namespace nonDrive -{ - -} - namespace shooter { + // Left Motor Config + constexpr std::string_view kLeftShooterMotorName = "LeftShooterMotor"; constexpr int kLeftShooterMotorCanID = 14; + constexpr bool kLeftShooterMotorIsInverted = true; + + // Right Motor Config + constexpr std::string_view kRightShooterMotorName = "RightShooterMotor"; constexpr int kRightShooterMotorCanID = 15; + constexpr bool kRightShooterMotorIsInverted = false; constexpr int kShooterMotorSpeed = 0; - } namespace intake diff --git a/src/main/include/subsystems/ShooterSubsystem.h b/src/main/include/subsystems/ShooterSubsystem.h index 55e058e..df79113 100644 --- a/src/main/include/subsystems/ShooterSubsystem.h +++ b/src/main/include/subsystems/ShooterSubsystem.h @@ -25,27 +25,23 @@ class ShooterSubsystem : public frc2::SubsystemBase { public: - // The only ctor of the FeederSubsystem class. ShooterSubsystem() noexcept; ShooterSubsystem(const ShooterSubsystem &) = delete; ShooterSubsystem &operator=(const ShooterSubsystem &) = delete; void Periodic() noexcept override; - + void Default(const double percent) noexcept; + void Stop() noexcept; + void BurnConfig() noexcept; + void ClearFaults() noexcept; bool GetStatus() const noexcept; +#pragma region Test void TestInit() noexcept; void TestExit() noexcept; void TestPeriodic() noexcept; - - void Default(const double percent, const double velocity) noexcept; - - void Stop() noexcept; - - void BurnConfig() noexcept; - - void ClearFaults() noexcept; +#pragma endregion private: std::unique_ptr m_shooterMotorBase; From bf964393be33883bc22513ecceb47bc7c3e83276 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 15 Feb 2024 22:46:42 -0500 Subject: [PATCH 061/126] intake refactor and update --- src/main/cpp/RobotContainer.cpp | 10 +- src/main/cpp/commands/AutonomousCommands.cpp | 7 +- src/main/cpp/subsystems/FeederSubsystem.cpp | 192 ------------------ src/main/cpp/subsystems/IntakeSubsystem.cpp | 94 +++++++++ src/main/include/Constants.h | 11 +- src/main/include/RobotContainer.h | 4 +- .../include/commands/AutonomousCommands.h | 8 +- src/main/include/subsystems/FeederSubsystem.h | 65 ------ src/main/include/subsystems/IntakeSubsystem.h | 45 ++++ 9 files changed, 161 insertions(+), 275 deletions(-) delete mode 100644 src/main/cpp/subsystems/FeederSubsystem.cpp create mode 100644 src/main/cpp/subsystems/IntakeSubsystem.cpp delete mode 100644 src/main/include/subsystems/FeederSubsystem.h create mode 100644 src/main/include/subsystems/IntakeSubsystem.h diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 0ef57f5..ce880b8 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -112,10 +112,10 @@ void RobotContainer::TeleopInit() noexcept m_driveSubsystem.SetDefaultCommand(DriveCommandFactory(this)); // m_feederSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void - // { m_feederSubsystem.Default(m_xbox.GetRightTriggerAxis()); }, + // { m_feederSubsystem.SetSpinMotorVoltagePercent(m_xbox.GetRightTriggerAxis()); }, // {&m_feederSubsystem})); // m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void - // { m_shooterSubsystem.Default(m_xbox.GetLeftTriggerAxis(), m_shooterVelocity); }, + // { m_shooterSubsystem.SetSpinMotorVoltagePercent(m_xbox.GetLeftTriggerAxis(), m_shooterVelocity); }, // {&m_shooterSubsystem})); m_infrastructureSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void { m_infrastructureSubsystem.SetLEDPattern(m_LEDPattern); }, @@ -148,7 +148,7 @@ void RobotContainer::ConfigureBindings() noexcept // {&m_feederSubsystem}) // .ToPtr()); // m_xbox.LeftBumper().OnFalse(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.NoFeed(); }, + // { m_feederSubsystem.DisableIntake(); }, // {&m_feederSubsystem}) // .ToPtr()); @@ -157,12 +157,12 @@ void RobotContainer::ConfigureBindings() noexcept // {&m_feederSubsystem}) // .ToPtr()); // m_xbox.RightBumper().OnFalse(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.NoFeed(); }, + // { m_feederSubsystem.DisableIntake(); }, // {&m_feederSubsystem}) // .ToPtr()); // m_xbox.Start().WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.Raise(); }, + // { m_feederSubsystem.SetArmMotorVoltagePercent(); }, // {&m_feederSubsystem}) // .ToPtr()); diff --git a/src/main/cpp/commands/AutonomousCommands.cpp b/src/main/cpp/commands/AutonomousCommands.cpp index 8722a57..61e1804 100644 --- a/src/main/cpp/commands/AutonomousCommands.cpp +++ b/src/main/cpp/commands/AutonomousCommands.cpp @@ -14,10 +14,7 @@ void TimedAutoBase::Initialize() noexcept m_drive->ResetDrive(); m_drive->ZeroHeading(); - // TODO: update to match our intake - m_feeder->Default(0.0); - m_feeder->LockIntake(); - m_feeder->RaiseIntake(); + m_Intake->DisableIntake(); m_shooter->Stop(); @@ -30,7 +27,7 @@ void TimedAutoBase::End(bool interrupted) noexcept { m_drive->Drive(0_mps, 0_mps, 0_deg_per_s, false); - m_feeder->Default(0.0); + m_Intake->DisableIntake(); m_shooter->Stop(); } diff --git a/src/main/cpp/subsystems/FeederSubsystem.cpp b/src/main/cpp/subsystems/FeederSubsystem.cpp deleted file mode 100644 index e1412a0..0000000 --- a/src/main/cpp/subsystems/FeederSubsystem.cpp +++ /dev/null @@ -1,192 +0,0 @@ -#include "subsystems/FeederSubsystem.h" - -#include "Constants.h" - -#include - -FeederSubsystem::FeederSubsystem() noexcept -{ - // NEO 550. - const SmartMotorBase::ConfigMap config = { - {"kStatus1", uint{250}}, - {"kStatus2", uint{250}}, - {"kIdleMode", uint{0}}, - {"kRampRate", double{0.1}}, - {"kSmartCurrentStallLimit", uint{25}}, // Amps - {"kSmartCurrentFreeLimit", uint{10}}, // Amps - }; - - const SmartMotorBase::ConfigMap climberConfig = { - {"kStatus1", uint{250}}, - {"kStatus2", uint{250}}, - {"kRampRate", double{0.5}}, - {"kSmartCurrentStallLimit", uint{40}}, // Amps - // {"kSoftLimitFwd", double{0.0}}, - // {"kSoftLimitRev", double{0.0}}, - }; - - const SmartMotorBase::ConfigMap moreTorque = { - {"kSmartCurrentStallLimit", uint{30}}, // Amps - }; - - m_intakeMotorBase = SparkFactory::CreateSparkMax("Intake", 9, false); - m_elevatorMotorBase = SparkFactory::CreateSparkMax("Elevator", 10, true); - m_feederMotorBase = SparkFactory::CreateSparkMax("Feeder", 11, true); - m_climberMotorBase = SparkFactory::CreateSparkMax("Climber", 12, false); - m_intakeMotor = std::make_unique>(*m_intakeMotorBase); - m_elevatorMotor = std::make_unique>(*m_elevatorMotorBase); - m_feederMotor = std::make_unique>(*m_feederMotorBase); - m_climberMotor = std::make_unique>(*m_climberMotorBase); - - m_intakeRaise = std::make_unique(1, frc::PneumaticsModuleType::REVPH, 0, 1); - m_intakeRelease = std::make_unique(1, frc::PneumaticsModuleType::REVPH, 13, 12); - - m_intakeMotor->SetConfig(config); - m_elevatorMotor->SetConfig(config); - m_feederMotor->SetConfig(config); - m_climberMotor->SetConfig(climberConfig); - m_elevatorMotor->AddConfig(moreTorque); - - m_intakeMotor->ApplyConfig(false); - m_elevatorMotor->ApplyConfig(false); - m_feederMotor->ApplyConfig(false); - m_climberMotor->ApplyConfig(false); - - Default(0.0); -} - -void FeederSubsystem::Periodic() noexcept -{ - m_intakeMotor->Periodic(); - m_elevatorMotor->Periodic(); - m_feederMotor->Periodic(); - m_climberMotor->Periodic(); -} - -bool FeederSubsystem::GetStatus() const noexcept -{ - return m_intakeMotor->GetStatus() || - m_elevatorMotor->GetStatus() || - m_feederMotor->GetStatus() || - m_climberMotor->GetStatus(); -} - -void FeederSubsystem::TestInit() noexcept {} -void FeederSubsystem::TestExit() noexcept {} - -void FeederSubsystem::TestPeriodic() noexcept -{ - const std::chrono::time_point now = std::chrono::steady_clock::now(); - if (now >= m_verifyMotorControllersWhen) - { - using namespace std::chrono_literals; - - m_verifyMotorControllersWhen = now + 15s; - - m_intakeMotor->CheckConfig(); - m_elevatorMotor->CheckConfig(); - m_feederMotor->CheckConfig(); - m_climberMotor->CheckConfig(); - } -} - -void FeederSubsystem::Default(const double percent) noexcept -{ - m_intakeMotor->SetVoltage(percent * 12_V); - m_elevatorMotor->SetVoltage(percent * 12_V); - - m_climberMotor->Stop(); - - m_intakeRelease->Set(frc::DoubleSolenoid::Value::kOff); - m_intakeRaise->Set(frc::DoubleSolenoid::Value::kOff); -} - -void FeederSubsystem::NoFeed() noexcept -{ - m_feederMotor->Stop(); -} - -void FeederSubsystem::Eject() noexcept -{ - m_intakeMotor->SetVoltage(-12.0_V); - m_elevatorMotor->SetVoltage(-12.0_V); - m_feederMotor->SetVoltage(-12.0_V); - - m_climberMotor->Stop(); - - m_intakeRelease->Set(frc::DoubleSolenoid::Value::kOff); - m_intakeRaise->Set(frc::DoubleSolenoid::Value::kOff); -} - -void FeederSubsystem::Fire() noexcept -{ - m_intakeMotor->Stop(); - m_elevatorMotor->Stop(); - - m_feederMotor->SetVoltage(12_V); - - m_climberMotor->Stop(); - - m_intakeRelease->Set(frc::DoubleSolenoid::Value::kOff); - m_intakeRaise->Set(frc::DoubleSolenoid::Value::kOff); -} - -void FeederSubsystem::Raise() noexcept -{ - m_intakeMotor->Stop(); - m_elevatorMotor->Stop(); - m_feederMotor->Stop(); - - m_climberMotor->SetVoltage(-10.0_V); - - m_intakeRelease->Set(frc::DoubleSolenoid::Value::kOff); - m_intakeRaise->Set(frc::DoubleSolenoid::Value::kOff); -} - -void FeederSubsystem::Lower() noexcept -{ - m_intakeMotor->Stop(); - m_elevatorMotor->Stop(); - m_feederMotor->Stop(); - - m_climberMotor->SetVoltage(10.0_V); - - m_intakeRelease->Set(frc::DoubleSolenoid::Value::kOff); - m_intakeRaise->Set(frc::DoubleSolenoid::Value::kOff); -} - -void FeederSubsystem::LockIntake() noexcept -{ - m_intakeRelease->Set(frc::DoubleSolenoid::Value::kForward); -} - -void FeederSubsystem::DropIntake() noexcept -{ - m_intakeRelease->Set(frc::DoubleSolenoid::Value::kReverse); -} - -void FeederSubsystem::RaiseIntake() noexcept -{ - m_intakeRaise->Set(frc::DoubleSolenoid::Value::kForward); -} - -void FeederSubsystem::LowerIntake() noexcept -{ - m_intakeRaise->Set(frc::DoubleSolenoid::Value::kReverse); -} - -void FeederSubsystem::BurnConfig() noexcept -{ - m_intakeMotor->ApplyConfig(true); - m_elevatorMotor->ApplyConfig(true); - m_feederMotor->ApplyConfig(true); - m_climberMotor->ApplyConfig(true); -} - -void FeederSubsystem::ClearFaults() noexcept -{ - m_intakeMotor->ClearFaults(); - m_elevatorMotor->ClearFaults(); - m_feederMotor->ClearFaults(); - m_climberMotor->ClearFaults(); -} diff --git a/src/main/cpp/subsystems/IntakeSubsystem.cpp b/src/main/cpp/subsystems/IntakeSubsystem.cpp new file mode 100644 index 0000000..30ad3d4 --- /dev/null +++ b/src/main/cpp/subsystems/IntakeSubsystem.cpp @@ -0,0 +1,94 @@ +#include "subsystems/IntakeSubsystem.h" + +#include "Constants.h" + +#include + +IntakeSubsystem::IntakeSubsystem() noexcept +{ + // TODO: add comment here saying what motor this is for + const SmartMotorBase::ConfigMap config = { + {"kStatus1", uint{250}}, + {"kStatus2", uint{250}}, + {"kIdleMode", uint{0}}, + {"kRampRate", double{0.1}}, + {"kSmartCurrentStallLimit", uint{15}}, // Amps + {"kSmartCurrentFreeLimit", uint{10}}, // Amps + }; + + const SmartMotorBase::ConfigMap moreTorque = { + {"kSmartCurrentStallLimit", uint{30}}, // Amps + }; + + m_ArmMotorBase = SparkFactory::CreateSparkMax(intake::kIntakeArmMotorName, intake::kIntakeArmMotorCanID, intake::kIntakeArmMotorIsInverted); + m_SpinMotorBase = SparkFactory::CreateSparkMax(intake::kIntakeSpinMotorName, intake::kIntakeSpinMotorCanID, intake::kIntakeSpinMotorIsInverted); + m_ArmMotor = std::make_unique>(*m_ArmMotorBase); + m_SpinMotor = std::make_unique>(*m_SpinMotorBase); + + m_ArmMotor->SetConfig(config); + m_SpinMotor->SetConfig(config); + m_SpinMotor->AddConfig(moreTorque); + + DisableIntake(); +} + +void IntakeSubsystem::Periodic() noexcept +{ + m_ArmMotor->Periodic(); + m_SpinMotor->Periodic(); +} + +void IntakeSubsystem::DisableIntake() noexcept +{ + m_ArmMotor->Stop(); + m_SpinMotorBase->Stop(); +} + +void IntakeSubsystem::SetSpinMotorVoltagePercent(const double percent) noexcept +{ + m_SpinMotor->SetVoltage(percent * 12_V); +} + +void IntakeSubsystem::SetArmMotorVoltagePercent(const double percent) noexcept +{ + m_ArmMotor->SetVoltage(percent * 12_V); +} + +#pragma region Utility +bool IntakeSubsystem::GetStatus() const noexcept +{ + return m_ArmMotor->GetStatus() || + m_SpinMotor->GetStatus(); +} + +void IntakeSubsystem::BurnConfig() noexcept +{ + m_ArmMotor->ApplyConfig(true); + m_SpinMotor->ApplyConfig(true); +} + +void IntakeSubsystem::ClearFaults() noexcept +{ + m_ArmMotor->ClearFaults(); + m_SpinMotor->ClearFaults(); +} +#pragma endregion + +#pragma region Test +void IntakeSubsystem::TestInit() noexcept {} + +void IntakeSubsystem::TestExit() noexcept {} + +void IntakeSubsystem::TestPeriodic() noexcept +{ + const std::chrono::time_point now = std::chrono::steady_clock::now(); + if (now >= m_verifyMotorControllersWhen) + { + using namespace std::chrono_literals; + + m_verifyMotorControllersWhen = now + 15s; + + // m_motor->CheckConfig(); + } +} +#pragma endregion diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index d7858d0..3c252a5 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -132,12 +132,12 @@ namespace pidf namespace shooter { - // Left Motor Config + // Left Motor Constructor Parameters constexpr std::string_view kLeftShooterMotorName = "LeftShooterMotor"; constexpr int kLeftShooterMotorCanID = 14; constexpr bool kLeftShooterMotorIsInverted = true; - // Right Motor Config + // Right Motor Constructor Parameters constexpr std::string_view kRightShooterMotorName = "RightShooterMotor"; constexpr int kRightShooterMotorCanID = 15; constexpr bool kRightShooterMotorIsInverted = false; @@ -147,8 +147,15 @@ namespace shooter namespace intake { + // Arm Motor Constructor Parameters + constexpr std::string_view kIntakeArmMotorName = "ArmMotor"; constexpr int kIntakeArmMotorCanID = 16; + constexpr bool kIntakeArmMotorIsInverted = false; + + // Intake Spin Motor Constructor Parameters + constexpr std::string_view kIntakeSpinMotorName = "IntakeSpinMotor"; constexpr int kIntakeSpinMotorCanID = 17; + constexpr bool kIntakeSpinMotorIsInverted = false; /* NOTE!!!: The intake arm values are NOT final, are subject to change after testing*/ diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index 98093da..fe8de94 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -11,7 +11,7 @@ #include "commands/AutonomousCommands.h" #include "commands/TestModeCommands.h" #include "subsystems/DriveSubsystem.h" -#include "subsystems/FeederSubsystem.h" +#include "subsystems/IntakeSubsystem.h" #include "subsystems/Infrastructure.h" #include "subsystems/ShooterSubsystem.h" #include @@ -62,7 +62,7 @@ class RobotContainer // The robot's subsystems and commands are defined here... DriveSubsystem m_driveSubsystem; - // FeederSubsystem m_feederSubsystem; + // IntakeSubsystem m_IntakeSubsystem; InfrastructureSubsystem m_infrastructureSubsystem; // ShooterSubsystem m_shooterSubsystem; diff --git a/src/main/include/commands/AutonomousCommands.h b/src/main/include/commands/AutonomousCommands.h index 501b928..3939f31 100644 --- a/src/main/include/commands/AutonomousCommands.h +++ b/src/main/include/commands/AutonomousCommands.h @@ -11,7 +11,7 @@ #include #include "subsystems/DriveSubsystem.h" -#include "subsystems/FeederSubsystem.h" +#include "subsystems/IntakeSubsystem.h" #include "subsystems/Infrastructure.h" #include "subsystems/ShooterSubsystem.h" @@ -21,8 +21,8 @@ class TimedAutoBase : public frc2::CommandHelper { protected: - TimedAutoBase(DriveSubsystem *const drive, FeederSubsystem *const feeder, ShooterSubsystem *const shooter, std::string_view name) noexcept - : m_drive{drive}, m_feeder{feeder}, m_shooter{shooter} { SetName(name); } + TimedAutoBase(DriveSubsystem *const drive, IntakeSubsystem *const Intake, ShooterSubsystem *const shooter, std::string_view name) noexcept + : m_drive{drive}, m_Intake{Intake}, m_shooter{shooter} { SetName(name); } public: virtual ~TimedAutoBase() noexcept = default; @@ -39,7 +39,7 @@ class TimedAutoBase : public frc2::CommandHelper protected: DriveSubsystem *const m_drive; - FeederSubsystem *const m_feeder; + IntakeSubsystem *const m_Intake; ShooterSubsystem *const m_shooter; private: diff --git a/src/main/include/subsystems/FeederSubsystem.h b/src/main/include/subsystems/FeederSubsystem.h deleted file mode 100644 index b5cc955..0000000 --- a/src/main/include/subsystems/FeederSubsystem.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "infrastructure/SparkMax.h" - -#include -#include - -#include -#include - -class FeederSubsystem : public frc2::SubsystemBase -{ -public: - // The only ctor of the FeederSubsystem class. - FeederSubsystem() noexcept; - - FeederSubsystem(const FeederSubsystem &) = delete; - FeederSubsystem &operator=(const FeederSubsystem &) = delete; - - void Periodic() noexcept override; - - bool GetStatus() const noexcept; - - void TestInit() noexcept; - void TestExit() noexcept; - void TestPeriodic() noexcept; - - void Default(const double percent) noexcept; - void NoFeed() noexcept; - - void Eject() noexcept; - - void Fire() noexcept; - - void Raise() noexcept; - - void Lower() noexcept; - - void LockIntake() noexcept; - - void DropIntake() noexcept; - - void RaiseIntake() noexcept; - - void LowerIntake() noexcept; - - void BurnConfig() noexcept; - - void ClearFaults() noexcept; - -private: - std::unique_ptr m_intakeMotorBase; - std::unique_ptr m_elevatorMotorBase; - std::unique_ptr m_feederMotorBase; - std::unique_ptr m_climberMotorBase; - std::unique_ptr> m_intakeMotor; - std::unique_ptr> m_elevatorMotor; - std::unique_ptr> m_feederMotor; - std::unique_ptr> m_climberMotor; - - std::unique_ptr m_intakeRelease; - std::unique_ptr m_intakeRaise; - - std::chrono::steady_clock::time_point m_verifyMotorControllersWhen; -}; diff --git a/src/main/include/subsystems/IntakeSubsystem.h b/src/main/include/subsystems/IntakeSubsystem.h new file mode 100644 index 0000000..9903499 --- /dev/null +++ b/src/main/include/subsystems/IntakeSubsystem.h @@ -0,0 +1,45 @@ +#pragma once + +#include "infrastructure/SparkMax.h" + +#include +#include + +#include +#include + +class IntakeSubsystem : public frc2::SubsystemBase +{ +public: + IntakeSubsystem() noexcept; + + IntakeSubsystem(const IntakeSubsystem &) = delete; + IntakeSubsystem &operator=(const IntakeSubsystem &) = delete; + + void Periodic() noexcept override; + void DisableIntake() noexcept; + void SetSpinMotorVoltagePercent(const double percent) noexcept; + void SetArmMotorVoltagePercent(const double percent) noexcept; // must be used in a PID loop to set arm position + +private: + std::unique_ptr m_ArmMotorBase; + std::unique_ptr m_SpinMotorBase; + std::unique_ptr> m_ArmMotor; + std::unique_ptr> m_SpinMotor; + +#pragma region Utility +public: + bool GetStatus() const noexcept; + void BurnConfig() noexcept; + void ClearFaults() noexcept; +#pragma endregion + +#pragma region Test +public: + void TestInit() noexcept; + void TestExit() noexcept; + void TestPeriodic() noexcept; +private: + std::chrono::steady_clock::time_point m_verifyMotorControllersWhen; +#pragma endregion +}; From 5b9194ccb7f2cf448b02af731fb1f5ca3ee13269 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Thu, 15 Feb 2024 23:54:48 -0500 Subject: [PATCH 062/126] refactor and QoL --- src/main/cpp/subsystems/IntakeSubsystem.cpp | 13 +++- src/main/cpp/subsystems/ShooterSubsystem.cpp | 64 +++++++++++-------- .../include/subsystems/ShooterSubsystem.h | 23 ++++--- 3 files changed, 60 insertions(+), 40 deletions(-) diff --git a/src/main/cpp/subsystems/IntakeSubsystem.cpp b/src/main/cpp/subsystems/IntakeSubsystem.cpp index 30ad3d4..66865bb 100644 --- a/src/main/cpp/subsystems/IntakeSubsystem.cpp +++ b/src/main/cpp/subsystems/IntakeSubsystem.cpp @@ -20,8 +20,14 @@ IntakeSubsystem::IntakeSubsystem() noexcept {"kSmartCurrentStallLimit", uint{30}}, // Amps }; - m_ArmMotorBase = SparkFactory::CreateSparkMax(intake::kIntakeArmMotorName, intake::kIntakeArmMotorCanID, intake::kIntakeArmMotorIsInverted); - m_SpinMotorBase = SparkFactory::CreateSparkMax(intake::kIntakeSpinMotorName, intake::kIntakeSpinMotorCanID, intake::kIntakeSpinMotorIsInverted); + m_ArmMotorBase = SparkFactory::CreateSparkMax( + intake::kIntakeArmMotorName, + intake::kIntakeArmMotorCanID, + intake::kIntakeArmMotorIsInverted); + m_SpinMotorBase = SparkFactory::CreateSparkMax( + intake::kIntakeSpinMotorName, + intake::kIntakeSpinMotorCanID, + intake::kIntakeSpinMotorIsInverted); m_ArmMotor = std::make_unique>(*m_ArmMotorBase); m_SpinMotor = std::make_unique>(*m_SpinMotorBase); @@ -88,7 +94,8 @@ void IntakeSubsystem::TestPeriodic() noexcept m_verifyMotorControllersWhen = now + 15s; - // m_motor->CheckConfig(); + m_ArmMotor->CheckConfig(); + m_SpinMotor->CheckConfig(); } } #pragma endregion diff --git a/src/main/cpp/subsystems/ShooterSubsystem.cpp b/src/main/cpp/subsystems/ShooterSubsystem.cpp index 9a0c1cb..848e0b9 100644 --- a/src/main/cpp/subsystems/ShooterSubsystem.cpp +++ b/src/main/cpp/subsystems/ShooterSubsystem.cpp @@ -6,71 +6,79 @@ ShooterSubsystem::ShooterSubsystem() noexcept { - // These motors use bang-bang control, thus an increased velocity reporting - // rate (kStatus1). + // TODO: add comment here saying what motor this is for const SmartMotorBase::ConfigMap config = { {"kStatus1", uint{250}}, {"kStatus2", uint{250}}, {"kIdleMode", uint{0}}, + {"kRampRate", double{0.1}}, + {"kSmartCurrentStallLimit", uint{25}}, // Amps + {"kSmartCurrentFreeLimit", uint{10}}, }; - m_shooterMotorBase = SparkFactory::CreateSparkMax( + m_LeftShooterMotorBase = SparkFactory::CreateSparkMax( shooter::kLeftShooterMotorName, shooter::kLeftShooterMotorCanID, shooter::kLeftShooterMotorIsInverted); - m_backspinMotorBase = SparkFactory::CreateSparkMax( + m_RightShooterMotorBase = SparkFactory::CreateSparkMax( shooter::kRightShooterMotorName, shooter::kRightShooterMotorCanID, shooter::kRightShooterMotorIsInverted); - m_shooterMotor = std::make_unique>(*m_shooterMotorBase); - m_backspinMotor = std::make_unique>(*m_backspinMotorBase); + m_LeftShooterMotor = std::make_unique>(*m_LeftShooterMotorBase); + m_RightShooterMotor = std::make_unique>(*m_RightShooterMotorBase); - m_shooterMotor->SetConfig(config); - m_backspinMotor->SetConfig(config); + m_LeftShooterMotor->SetConfig(config); + m_RightShooterMotor->SetConfig(config); - m_shooterMotor->ApplyConfig(false); - m_backspinMotor->ApplyConfig(false); + m_LeftShooterMotor->ApplyConfig(false); + m_RightShooterMotor->ApplyConfig(false); + + Stop(); } void ShooterSubsystem::Periodic() noexcept { - m_shooterMotor->Periodic(); - m_backspinMotor->Periodic(); + m_LeftShooterMotor->Periodic(); + m_RightShooterMotor->Periodic(); } -void ShooterSubsystem::Default(const double percent) noexcept +void ShooterSubsystem::SetShooterMotorVoltagePercent(const double percent) noexcept { - m_shooterMotor->SetVoltage(percent * 12.00_V); - m_backspinMotor->SetVoltage(percent * 12.00_V); + m_LeftShooterMotor->SetVoltage(percent * 12.00_V); + m_RightShooterMotor->SetVoltage(percent * 12.00_V); } void ShooterSubsystem::Stop() noexcept { - m_shooterMotor->Stop(); - m_backspinMotor->Stop(); + m_LeftShooterMotor->Stop(); + m_RightShooterMotor->Stop(); } -void ShooterSubsystem::BurnConfig() noexcept +#pragma region Utility +bool ShooterSubsystem::GetStatus() const noexcept { - m_shooterMotor->ApplyConfig(true); - m_backspinMotor->ApplyConfig(true); + return m_LeftShooterMotor->GetStatus() || + m_RightShooterMotor->GetStatus(); } -void ShooterSubsystem::ClearFaults() noexcept +void ShooterSubsystem::BurnConfig() noexcept { - m_shooterMotor->ClearFaults(); - m_backspinMotor->ClearFaults(); + m_LeftShooterMotor->ApplyConfig(true); + m_RightShooterMotor->ApplyConfig(true); } -bool ShooterSubsystem::GetStatus() const noexcept +void ShooterSubsystem::ClearFaults() noexcept { - return m_shooterMotor->GetStatus() || - m_backspinMotor->GetStatus(); + m_LeftShooterMotor->ClearFaults(); + m_RightShooterMotor->ClearFaults(); } +#pragma endregion #pragma region Test void ShooterSubsystem::TestInit() noexcept {} + void ShooterSubsystem::TestExit() noexcept {} + void ShooterSubsystem::TestPeriodic() noexcept { const std::chrono::time_point now = std::chrono::steady_clock::now(); @@ -80,8 +88,8 @@ void ShooterSubsystem::TestPeriodic() noexcept m_verifyMotorControllersWhen = now + 15s; - m_shooterMotor->CheckConfig(); - m_backspinMotor->CheckConfig(); + m_LeftShooterMotor->CheckConfig(); + m_RightShooterMotor->CheckConfig(); } } #pragma endregion diff --git a/src/main/include/subsystems/ShooterSubsystem.h b/src/main/include/subsystems/ShooterSubsystem.h index df79113..443068f 100644 --- a/src/main/include/subsystems/ShooterSubsystem.h +++ b/src/main/include/subsystems/ShooterSubsystem.h @@ -31,23 +31,28 @@ class ShooterSubsystem : public frc2::SubsystemBase ShooterSubsystem &operator=(const ShooterSubsystem &) = delete; void Periodic() noexcept override; - void Default(const double percent) noexcept; + void SetShooterMotorVoltagePercent(const double percent) noexcept; void Stop() noexcept; + +private: + std::unique_ptr m_LeftShooterMotorBase; + std::unique_ptr m_RightShooterMotorBase; + std::unique_ptr> m_LeftShooterMotor; + std::unique_ptr> m_RightShooterMotor; + +#pragma region Utility +public: + bool GetStatus() const noexcept; void BurnConfig() noexcept; void ClearFaults() noexcept; - bool GetStatus() const noexcept; +#pragma endregion #pragma region Test +public: void TestInit() noexcept; void TestExit() noexcept; void TestPeriodic() noexcept; -#pragma endregion - private: - std::unique_ptr m_shooterMotorBase; - std::unique_ptr m_backspinMotorBase; - std::unique_ptr> m_shooterMotor; - std::unique_ptr> m_backspinMotor; - std::chrono::steady_clock::time_point m_verifyMotorControllersWhen; +#pragma endregion }; From d7364a0d0a1b127167cbd67f744da9911fc97d98 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:32:27 -0500 Subject: [PATCH 063/126] removed unnecessary commands --- src/main/cpp/subsystems/ShooterSubsystem.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/cpp/subsystems/ShooterSubsystem.cpp b/src/main/cpp/subsystems/ShooterSubsystem.cpp index 848e0b9..04806b0 100644 --- a/src/main/cpp/subsystems/ShooterSubsystem.cpp +++ b/src/main/cpp/subsystems/ShooterSubsystem.cpp @@ -30,9 +30,6 @@ ShooterSubsystem::ShooterSubsystem() noexcept m_LeftShooterMotor->SetConfig(config); m_RightShooterMotor->SetConfig(config); - m_LeftShooterMotor->ApplyConfig(false); - m_RightShooterMotor->ApplyConfig(false); - Stop(); } From 305f44928f303786366e1a43ee3601cd8bb71e87 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:16:58 -0500 Subject: [PATCH 064/126] major container refactor --- src/main/cpp/RobotContainer.cpp | 322 ++++++++++++------------------ src/main/include/RobotContainer.h | 56 ++++-- 2 files changed, 165 insertions(+), 213 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index ce880b8..2e44971 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -36,213 +36,94 @@ RobotContainer::RobotContainer() noexcept ConfigureBindings(); } -frc2::CommandPtr RobotContainer::DriveCommandFactory(RobotContainer *container) noexcept -{ - // Set up default drive command; non-owning pointer is passed by value. - auto driveRequirements = {dynamic_cast(&container->m_driveSubsystem)}; - - // Drive, as commanded by operator joystick controls. - return frc2::CommandPtr{std::make_unique( - [container]() -> void - { - if (container->m_lock) - { - (void)container->m_driveSubsystem.SetLockWheelsX(); - - return; - } - - const auto controls = container->GetDriveTeleopControls(); - - container->m_driveSubsystem.Drive( - std::get<0>(controls) * physical::kMaxDriveSpeed, - std::get<1>(controls) * physical::kMaxDriveSpeed, - std::get<2>(controls) * physical::kMaxTurnRate, - std::get<3>(controls)); - }, - driveRequirements)}; -} - -frc2::CommandPtr RobotContainer::PointCommandFactory(RobotContainer *container) noexcept -{ - // Set up default drive command; non-owning pointer is passed by value. - auto driveRequirements = {dynamic_cast(&container->m_driveSubsystem)}; - - // Point swerve modules, but do not actually drive. - return frc2::CommandPtr{std::make_unique( - [container]() -> void - { - const auto controls = container->GetDriveTeleopControls(); - - units::angle::radian_t angle{std::atan2(std::get<0>(controls), std::get<1>(controls))}; - - // Ingnore return (bool); no need to check that commanded angle has - // been reached. - (void)container->m_driveSubsystem.SetTurningPosition(angle); - }, - driveRequirements)}; -} - +#pragma region Autonomous void RobotContainer::AutonomousInit() noexcept { m_driveSubsystem.ClearFaults(); - // m_feederSubsystem.ClearFaults(); - // m_shooterSubsystem.ClearFaults(); + m_intakeSubsystem.ClearFaults(); + m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, {&m_driveSubsystem})); - // m_feederSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, - // {&m_feederSubsystem})); - // m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, - // {&m_shooterSubsystem})); + m_intakeSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, + {&m_intakeSubsystem})); + m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, + {&m_shooterSubsystem})); m_infrastructureSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, {&m_infrastructureSubsystem})); } +void RobotContainer::AutonomousPeriodic() noexcept {} + +void RobotContainer::AutonomousExit() noexcept {} + +std::optional RobotContainer::GetAutonomousCommand() noexcept +{ + frc::TrajectoryConfig trajectoryConfig{4.0_mps, 2.0_mps_sq}; + frc::SwerveDriveKinematics<4> kinematics{m_driveSubsystem.kDriveKinematics}; + + trajectoryConfig.SetKinematics(kinematics); + + // TODO: Update trajectory path to our autonomous path + frc::Trajectory trajectory = frc::TrajectoryGenerator::GenerateTrajectory( + {frc::Pose2d{}, + frc::Pose2d{1.0_m, 0.0_m, frc::Rotation2d{}}}, + trajectoryConfig); + + return TrajectoryAuto::TrajectoryAutoCommandFactory(&m_driveSubsystem, "Test Trajectory", trajectory); +} +#pragma endregion + +#pragma region Teleop void RobotContainer::TeleopInit() noexcept { m_driveSubsystem.ClearFaults(); - // m_feederSubsystem.ClearFaults(); - // m_shooterSubsystem.ClearFaults(); + m_intakeSubsystem.ClearFaults(); + m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.ZeroHeading(); m_driveSubsystem.SetDefaultCommand(DriveCommandFactory(this)); - // m_feederSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void - // { m_feederSubsystem.SetSpinMotorVoltagePercent(m_xbox.GetRightTriggerAxis()); }, - // {&m_feederSubsystem})); - // m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void - // { m_shooterSubsystem.SetSpinMotorVoltagePercent(m_xbox.GetLeftTriggerAxis(), m_shooterVelocity); }, - // {&m_shooterSubsystem})); - m_infrastructureSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void - { m_infrastructureSubsystem.SetLEDPattern(m_LEDPattern); }, + m_intakeSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, + {&m_intakeSubsystem})); + m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, + {&m_shooterSubsystem})); + m_infrastructureSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, {&m_infrastructureSubsystem})); } -void RobotContainer::ConfigureBindings() noexcept -{ - m_xbox.A().OnTrue(frc2::InstantCommand([&]() -> void - { m_slow = true; }, - {}) - .ToPtr()); - m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void - { m_slow = false; }, - {}) - .ToPtr()); +void RobotContainer::TeleopPeriodic() noexcept {} - m_xbox.X().OnTrue(frc2::InstantCommand([&]() -> void - { m_fieldOriented = false; }, - {}) - .ToPtr()); - m_xbox.Y().OnTrue(frc2::InstantCommand([&]() -> void - { m_driveSubsystem.ZeroHeading(); - m_fieldOriented = true; }, - {&m_driveSubsystem}) - .ToPtr()); - - // m_xbox.LeftBumper().WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.Fire(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - // m_xbox.LeftBumper().OnFalse(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.DisableIntake(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - - // m_xbox.RightBumper().WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.Eject(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - // m_xbox.RightBumper().OnFalse(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.DisableIntake(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - - // m_xbox.Start().WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.SetArmMotorVoltagePercent(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - - // m_xbox.Back().WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.Lower(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - - // frc2::POVButton(&m_xbox, 90).WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.LockIntake(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - - // frc2::POVButton(&m_xbox, 270).WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.DropIntake(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - - // frc2::POVButton(&m_xbox, 0).WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.RaiseIntake(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - - // frc2::POVButton(&m_xbox, 180).WhileTrue(frc2::InstantCommand([&]() -> void - // { m_feederSubsystem.LowerIntake(); }, - // {&m_feederSubsystem}) - // .ToPtr()); - - // frc2::JoystickButton(&m_buttonBoard, 5).OnTrue(frc2::InstantCommand([&]() -> void - // { m_shooterVelocity = -500.0; }, - // {}) - // .ToPtr()); - - // frc2::JoystickButton(&m_buttonBoard, 6).OnTrue(frc2::InstantCommand([&]() -> void - // { m_lock = true; }, - // {}) - // .ToPtr()); - - // frc2::JoystickButton(&m_buttonBoard, 6).OnFalse(frc2::InstantCommand([&]() -> void - // { m_lock = false; }, - // {}) - // .ToPtr()); - - // frc2::JoystickButton(&m_buttonBoard, 10).OnTrue(frc2::InstantCommand([&]() -> void - // { m_shooterVelocity = 1320.0; }, - // {}) - // .ToPtr()); - - // frc2::JoystickButton(&m_buttonBoard, 11).OnTrue(frc2::InstantCommand([&]() -> void - // { m_shooterVelocity = 930.0; }, - // {}) - // .ToPtr()); - - // frc2::JoystickButton(&m_buttonBoard, 12).OnTrue(frc2::InstantCommand([&]() -> void - // { m_shooterVelocity = 400.0; }, - // {}) - // .ToPtr()); - - // frc2::JoystickButton(&m_buttonBoard, 7).OnTrue(frc2::InstantCommand([&]() -> void - // { ++m_LEDPattern; - // if (m_LEDPattern >= m_LEDPatternCount) { m_LEDPattern = 0; } - // std::printf("LED Pattern[%u]: %s\n", m_LEDPattern, std::string(m_infrastructureSubsystem.GetLEDPatternDescription(m_LEDPattern)).c_str()); }, - // {}) - // .ToPtr()); -} +void RobotContainer::TeleopExit() noexcept {} -std::optional RobotContainer::GetAutonomousCommand() noexcept +frc2::CommandPtr RobotContainer::DriveCommandFactory(RobotContainer *container) noexcept { - frc::TrajectoryConfig trajectoryConfig{4.0_mps, 2.0_mps_sq}; - frc::SwerveDriveKinematics<4> kinematics{m_driveSubsystem.kDriveKinematics}; + // Set up default drive command; non-owning pointer is passed by value. + auto driveRequirements = {dynamic_cast(&container->m_driveSubsystem)}; - trajectoryConfig.SetKinematics(kinematics); + // Drive, as commanded by operator joystick controls. + return frc2::CommandPtr{std::make_unique( + [container]() -> void + { + if (container->m_lock) + { + (void)container->m_driveSubsystem.SetLockWheelsX(); - // TODO: Update trajectory path to our autonomous path - frc::Trajectory trajectory = frc::TrajectoryGenerator::GenerateTrajectory( - {frc::Pose2d{}, - frc::Pose2d{1.0_m, 0.0_m, frc::Rotation2d{}}}, - trajectoryConfig); + return; + } - return TrajectoryAuto::TrajectoryAutoCommandFactory(&m_driveSubsystem, "Test Trajectory", trajectory); + const auto controls = container->GetDriveTeleopControls(); + + container->m_driveSubsystem.Drive( + std::get<0>(controls) * physical::kMaxDriveSpeed, + std::get<1>(controls) * physical::kMaxDriveSpeed, + std::get<2>(controls) * physical::kMaxTurnRate, + std::get<3>(controls)); + }, + driveRequirements)}; } std::tuple RobotContainer::GetDriveTeleopControls() noexcept @@ -325,19 +206,43 @@ std::tuple RobotContainer::GetDriveTeleopControls( return std::make_tuple(x, y, z, m_fieldOriented); } +void RobotContainer::ConfigureBindings() noexcept +{ + m_xbox.A().OnTrue(frc2::InstantCommand([&]() -> void + { m_slow = true; }, + {}) + .ToPtr()); + m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void + { m_slow = false; }, + {}) + .ToPtr()); + + m_xbox.X().OnTrue(frc2::InstantCommand([&]() -> void + { m_fieldOriented = false; }, + {}) + .ToPtr()); + m_xbox.Y().OnTrue(frc2::InstantCommand([&]() -> void + { m_driveSubsystem.ZeroHeading(); + m_fieldOriented = true; }, + {&m_driveSubsystem}) + .ToPtr()); +} +#pragma endregion + +#pragma region Test void RobotContainer::TestInit() noexcept { frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - // m_feederSubsystem.ClearFaults(); - // m_shooterSubsystem.ClearFaults(); + m_intakeSubsystem.ClearFaults(); + m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.TestInit(); - // m_feederSubsystem.TestInit(); - // m_shooterSubsystem.TestInit(); + m_intakeSubsystem.TestInit(); + m_shooterSubsystem.TestInit(); frc::SendableChooser> *chooser{m_driveSubsystem.TestModeChooser()}; @@ -357,39 +262,61 @@ void RobotContainer::TestInit() noexcept frc2::CommandScheduler::GetInstance().Enable(); } +void RobotContainer::TestPeriodic() noexcept +{ + m_driveSubsystem.TestPeriodic(); + m_intakeSubsystem.TestPeriodic(); + m_shooterSubsystem.TestPeriodic(); +} + void RobotContainer::TestExit() noexcept { frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - // m_feederSubsystem.ClearFaults(); - // m_shooterSubsystem.ClearFaults(); + m_intakeSubsystem.ClearFaults(); + m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.TestExit(); - // m_feederSubsystem.TestExit(); - // m_shooterSubsystem.TestExit(); + m_intakeSubsystem.TestExit(); + m_shooterSubsystem.TestExit(); m_driveSubsystem.BurnConfig(); - // m_feederSubsystem.BurnConfig(); - // m_shooterSubsystem.BurnConfig(); + m_intakeSubsystem.BurnConfig(); + m_shooterSubsystem.BurnConfig(); } -void RobotContainer::TestPeriodic() noexcept +frc2::CommandPtr RobotContainer::PointCommandFactory(RobotContainer *container) noexcept { - m_driveSubsystem.TestPeriodic(); - // m_feederSubsystem.TestPeriodic(); - // m_shooterSubsystem.TestPeriodic(); + // Set up default drive command; non-owning pointer is passed by value. + auto driveRequirements = {dynamic_cast(&container->m_driveSubsystem)}; + + // Point swerve modules, but do not actually drive. + return frc2::CommandPtr{std::make_unique( + [container]() -> void + { + const auto controls = container->GetDriveTeleopControls(); + + units::angle::radian_t angle{std::atan2(std::get<0>(controls), std::get<1>(controls))}; + + // Ingnore return (bool); no need to check that commanded angle has + // been reached. + (void)container->m_driveSubsystem.SetTurningPosition(angle); + }, + driveRequirements)}; } +#pragma endregion +#pragma region Disabled void RobotContainer::DisabledInit() noexcept { frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - // m_feederSubsystem.ClearFaults(); - // m_shooterSubsystem.ClearFaults(); + m_intakeSubsystem.ClearFaults(); + m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); @@ -399,13 +326,16 @@ void RobotContainer::DisabledInit() noexcept m_driveSubsystem.DisabledInit(); } +void RobotContainer::DisabledPeriodic() noexcept {} + void RobotContainer::DisabledExit() noexcept { m_driveSubsystem.ClearFaults(); - // m_feederSubsystem.ClearFaults(); - // m_shooterSubsystem.ClearFaults(); + m_intakeSubsystem.ClearFaults(); + m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.DisabledExit(); } +#pragma endregion diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index fe8de94..599dc8d 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -34,38 +34,60 @@ class RobotContainer RobotContainer(const RobotContainer &) = delete; RobotContainer &operator=(const RobotContainer &) = delete; +#pragma region Autonomous +public: + void AutonomousInit() noexcept; + void AutonomousPeriodic() noexcept; + void AutonomousExit() noexcept; std::optional GetAutonomousCommand() noexcept; +#pragma endregion - void TestInit() noexcept; - void TestExit() noexcept; - void TestPeriodic() noexcept; - - void DisabledInit() noexcept; - void DisabledExit() noexcept; - void AutonomousInit() noexcept; +#pragma region Teleop +public: void TeleopInit() noexcept; + void TeleopPeriodic() noexcept; + void TeleopExit() noexcept; private: static frc2::CommandPtr DriveCommandFactory(RobotContainer *container) noexcept; - static frc2::CommandPtr PointCommandFactory(RobotContainer *container) noexcept; - std::tuple GetDriveTeleopControls() noexcept; - void ConfigureBindings() noexcept; bool m_fieldOriented{true}; bool m_lock{false}; bool m_slow{false}; double m_shooterVelocity{0.0}; - uint m_LEDPattern{29}; - uint m_LEDPatternCount{0}; - // The robot's subsystems and commands are defined here... + frc2::CommandXboxController m_xbox{0}; + // frc2::CommandXboxController m_xbox{1}; TODO: update for second controller +#pragma endregion + +#pragma region Test +public: + void TestInit() noexcept; + void TestPeriodic() noexcept; + void TestExit() noexcept; +private: + static frc2::CommandPtr PointCommandFactory(RobotContainer *container) noexcept; +#pragma endregion + +#pragma region Disabled +public: + void DisabledInit() noexcept; + void DisabledPeriodic() noexcept; + void DisabledExit() noexcept; +#pragma endregion + +#pragma region Subsystem Declare +private: + // The robot's subsystems and commands are declared here DriveSubsystem m_driveSubsystem; - // IntakeSubsystem m_IntakeSubsystem; InfrastructureSubsystem m_infrastructureSubsystem; - // ShooterSubsystem m_shooterSubsystem; + IntakeSubsystem m_intakeSubsystem; + ShooterSubsystem m_shooterSubsystem; - frc2::CommandXboxController m_xbox{0}; - // frc2::CommandGenericHID m_buttonBoard{1}; + // declared for the infrastructure subsystem + uint m_LEDPattern{29}; + uint m_LEDPatternCount{0}; +#pragma endregion }; From 408f7a46704c97ce130444e6fe71cc01b12d59e5 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:52:36 -0500 Subject: [PATCH 065/126] manually merging in riley's branch --- src/main/cpp/RobotContainer.cpp | 192 ++++++++++++++++++------------ src/main/include/RobotContainer.h | 5 +- 2 files changed, 122 insertions(+), 75 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 2e44971..e84ca79 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -128,95 +128,139 @@ frc2::CommandPtr RobotContainer::DriveCommandFactory(RobotContainer *container) std::tuple RobotContainer::GetDriveTeleopControls() noexcept { - // The robot's frame of reference is the standard unit circle, from - // trigonometry. However, the front of the robot is facing along the positve - // X axis. This means the poitive Y axis extends outward from the left (or - // port) side of the robot. Poitive rotation is counter-clockwise. On the - // other hand, as the controller is held, the Y axis is aligned with forward. - // And, specifically, it is the negative Y axis which extends forward. So, - // thne robot's X is the controllers inverted Y. On the controller, the X - // axis lines up with the robot's Y axis. And, the controller's positive X - // extends to the right. So, the robot's Y is the controller's inverted X. - // Finally, the other controller joystick is used for commanding rotation and - // things work out so that this is also an inverted X axis. - double x = -m_xbox.GetLeftY(); - double y = -m_xbox.GetLeftX(); - double z = -m_xbox.GetRightX(); - - // PlayStation controllers seem to do this strange thing with the rotation: - // double z = -m_xbox.GetLeftTriggerAxis(); - // Note: there is now a PS4Controller class. - - // Add some deadzone, so the robot doesn't drive when the joysticks are - // released and return to "zero". These implement a continuous deadband, one - // in which the full range of outputs may be generated, once joysticks move - // outside the deadband. - - // Also, cube the result, to provide more opertor control. Just cubing the - // raw value does a pretty good job with the deadband, but doing both is easy - // and guarantees no movement in the deadband. Cubing makes it easier to - // command smaller/slower movements, while still being able to command full - // power. The 'mixer` parameter is used to shape the `raw` input, some mix - // between out = in^3.0 and out = in. - auto shape = [](double raw, double mixer = 0.75) -> double + /* + The robot's frame of reference is the standard unit circle, from + trigonometry. However, the front of the robot is facing along the positve + X axis. This means the poitive Y axis extends outward from the left (or + port) side of the robot. Poitive rotation is counter-clockwise. On the + other hand, as the controller is held, the Y axis is aligned with forward. + And, specifically, it is the negative Y axis which extends forward. So, + the robot's X is the controllers inverted Y. On the controller, the X + axis lines up with the robot's Y axis. And, the controller's positive X + extends to the right. So, the robot's Y is the controller's inverted X. + Finally, the other controller joystick is used for commanding rotation and + things work out so that this is also an inverted X axis. + */ + double LeftStickX = -m_xbox.GetLeftY(); + double LeftStickY = -m_xbox.GetLeftX(); + double rightStickRot = -m_xbox.GetRightX(); + + if (triggerSpeedEnabled) // scale speed by analog trigger { - // Input deadband around 0.0 (+/- range). - constexpr double range = 0.07; + double RightTrigAnalogVal = m_xbox.GetRightTriggerAxis(); + RightTrigAnalogVal = ConditionRawTriggerInput(RightTrigAnalogVal); - constexpr double slope = 1.0 / (1.0 - range); - - if (raw >= -range && raw <= +range) - { - raw = 0.0; - } - else if (raw < -range) - { - raw += range; - raw *= slope; - } - else if (raw > +range) + if (LeftStickX != 0 || LeftStickY != 0) { - raw -= range; - raw *= slope; + if (LeftStickX != 0) + { + double LeftStickTheta = atan(LeftStickY / LeftStickX); + LeftStickX = RightTrigAnalogVal * cos(LeftStickTheta); + LeftStickY = RightTrigAnalogVal * sin(LeftStickTheta); + } + else + { + LeftStickY = std::copysign(RightTrigAnalogVal, LeftStickY); + } } + } + else // scale speed by analog stick + { + LeftStickX = ConditionRawJoystickInput(LeftStickX); + LeftStickY = ConditionRawJoystickInput(LeftStickY); + } + + rightStickRot = ConditionRawJoystickInput(rightStickRot, 0.0); + + // TODO: decide if this is still needed + LeftStickX *= 2.0; + LeftStickY *= 2.0; + rightStickRot *= 1.6; + + frc::SmartDashboard::PutNumber("X", LeftStickX); + frc::SmartDashboard::PutNumber("Y", LeftStickY); + frc::SmartDashboard::PutNumber("Z", rightStickRot); - return mixer * std::pow(raw, 3.0) + (1.0 - mixer) * raw; - }; + return std::make_tuple(LeftStickX, LeftStickY, rightStickRot, m_fieldOriented); +} - x = shape(x); - y = shape(y); - z = shape(z, 0.0); +double RobotContainer::ConditionRawTriggerInput(double RawTrigVal) noexcept +{ + // Set a raw trigger value using the right trigger + RawTrigVal = m_xbox.GetRightTriggerAxis(); + double deadZoneVal = 0.05; + double deadZoneCorrection = 1.0 / (1.0 - deadZoneVal); - if (true) // || m_buttonBoard.GetRawButton(9) + if (RawTrigVal < deadZoneVal) { - x *= 0.50; - y *= 0.50; - z *= 0.40; + /*if the trigger value is less than deadzonevalue, it will + set the right trigger value to zero to correct drifting*/ + return 0; } else - { // XXX Still needed? - x *= 2.0; - y *= 2.0; - z *= 1.6; + { + /* if the trigger value is greater than the deadzonevalue, it will + make a small correction to stop the increase in speed from being + too big */ + RawTrigVal -= deadZoneVal; + RawTrigVal *= deadZoneCorrection; + return std::pow(RawTrigVal, 3.0); // std::pow(RawTrigVal, 3.0) == RawTrigVal^3 + } +} + +double RobotContainer::ConditionRawJoystickInput(double RawJoystickVal, double mixer) noexcept +{ + /* + PlayStation controllers seem to do this strange thing with the rotation: + double z = -m_xbox.GetLeftTriggerAxis(); + Note: there is now a PS4Controller class. + + Add some deadzone, so the robot doesn't drive when the joysticks are + released and return to "zero". These implement a continuous deadband, one + in which the full range of outputs may be generated, once joysticks move + outside the deadband. + + Also, cube the result, to provide more opertor control. Just cubing the + raw value does a pretty good job with the deadband, but doing both is easy + and guarantees no movement in the deadband. Cubing makes it easier to + command smaller/slower movements, while still being able to command full + power. The 'mixer` parameter is used to shape the `raw` input, some mix + between out = in^3.0 and out = in. + */ + + // Input deadband around 0.0 (+/- range). + constexpr double deadZoneVal = 0.05; + + constexpr double slope = 1.0 / (1.0 - deadZoneVal); + + if (RawJoystickVal >= -deadZoneVal && RawJoystickVal <= +deadZoneVal) + { + RawJoystickVal = 0.0; + } + else if (RawJoystickVal < -deadZoneVal) + { + RawJoystickVal += deadZoneVal; + RawJoystickVal *= slope; + } + else if (RawJoystickVal > +deadZoneVal) + { + RawJoystickVal -= deadZoneVal; + RawJoystickVal *= slope; } - frc::SmartDashboard::PutNumber("X", x); - frc::SmartDashboard::PutNumber("Y", y); - frc::SmartDashboard::PutNumber("Z", z); - return std::make_tuple(x, y, z, m_fieldOriented); + return mixer * std::pow(RawJoystickVal, 3.0) + (1.0 - mixer) * RawJoystickVal; } void RobotContainer::ConfigureBindings() noexcept { - m_xbox.A().OnTrue(frc2::InstantCommand([&]() -> void - { m_slow = true; }, - {}) - .ToPtr()); - m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void - { m_slow = false; }, - {}) - .ToPtr()); - + // TODO: define Keybindings here + m_xbox.Start().OnTrue( + frc2::InstantCommand([&]() -> void + { triggerSpeedEnabled = !triggerSpeedEnabled; }, + {} + ).ToPtr()); + + // TODO: decide if we want this m_xbox.X().OnTrue(frc2::InstantCommand([&]() -> void { m_fieldOriented = false; }, {}) @@ -226,6 +270,8 @@ void RobotContainer::ConfigureBindings() noexcept m_fieldOriented = true; }, {&m_driveSubsystem}) .ToPtr()); + + // TODO: decide if we want to bind wheel lock } #pragma endregion diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index 599dc8d..deb4a00 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -51,12 +51,13 @@ class RobotContainer private: static frc2::CommandPtr DriveCommandFactory(RobotContainer *container) noexcept; std::tuple GetDriveTeleopControls() noexcept; + double ConditionRawTriggerInput(double RawTrigVal) noexcept; + double ConditionRawJoystickInput(double RawJoystickVal, double mixer = 0.75) noexcept; void ConfigureBindings() noexcept; bool m_fieldOriented{true}; bool m_lock{false}; - bool m_slow{false}; - double m_shooterVelocity{0.0}; + bool triggerSpeedEnabled{false}; frc2::CommandXboxController m_xbox{0}; // frc2::CommandXboxController m_xbox{1}; TODO: update for second controller From 5cf87b9a59961d30305889af72ff71c637b48d67 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Fri, 16 Feb 2024 18:14:38 -0500 Subject: [PATCH 066/126] comprehensively linking robot and container --- src/main/cpp/Robot.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/cpp/Robot.cpp b/src/main/cpp/Robot.cpp index aa97add..0b25e97 100644 --- a/src/main/cpp/Robot.cpp +++ b/src/main/cpp/Robot.cpp @@ -46,7 +46,10 @@ void Robot::DisabledInit() noexcept m_container.DisabledInit(); } -void Robot::DisabledPeriodic() noexcept {} +void Robot::DisabledPeriodic() noexcept +{ + m_container.DisabledPeriodic(); +} void Robot::DisabledExit() noexcept { @@ -71,9 +74,15 @@ void Robot::AutonomousInit() noexcept m_container.AutonomousInit(); } -void Robot::AutonomousPeriodic() noexcept {} +void Robot::AutonomousPeriodic() noexcept +{ + m_container.AutonomousPeriodic(); +} -void Robot::AutonomousExit() noexcept {} +void Robot::AutonomousExit() noexcept +{ + m_container.AutonomousExit(); +} void Robot::TeleopInit() noexcept { @@ -95,9 +104,15 @@ void Robot::TeleopInit() noexcept /** * This function is called periodically during operator control. */ -void Robot::TeleopPeriodic() noexcept {} +void Robot::TeleopPeriodic() noexcept +{ + m_container.TeleopPeriodic(); +} -void Robot::TeleopExit() noexcept {} +void Robot::TeleopExit() noexcept +{ + m_container.TeleopExit(); +} void Robot::TestInit() noexcept { From 2e387f2c5ffb4c0fffa7e56c70922a97f2cadbeb Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Fri, 16 Feb 2024 18:22:37 -0500 Subject: [PATCH 067/126] increase PID loop period --- src/main/cpp/infrastructure/SwerveModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index 15b7a7f..2207d74 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -56,7 +56,7 @@ SwerveModule::SwerveModule( pidf::kTurningPositionD, std::move(frc::TrapezoidProfile::Constraints{ pidf::kTurningPositionMaxVelocity, - pidf::kTurningPositionMaxAcceleration})); + pidf::kTurningPositionMaxAcceleration}), 100_ms); m_rioPIDController->EnableContinuousInput(-180.0_deg, +180.0_deg); From b8588bfc9ca5f4ba6316f527636f910d2290d9e9 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Fri, 16 Feb 2024 19:17:36 -0500 Subject: [PATCH 068/126] created drive commands --- src/main/cpp/commands/DriveCommands.cpp | 29 +++++++++++ src/main/include/commands/DriveCommands.h | 59 +++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/main/cpp/commands/DriveCommands.cpp create mode 100644 src/main/include/commands/DriveCommands.h diff --git a/src/main/cpp/commands/DriveCommands.cpp b/src/main/cpp/commands/DriveCommands.cpp new file mode 100644 index 0000000..e3de59f --- /dev/null +++ b/src/main/cpp/commands/DriveCommands.cpp @@ -0,0 +1,29 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/DriveCommands.h" + +#include + +void ZeroTurningModules::Execute() noexcept { (void)driveSubsystem->ZeroModules(); } + +void DriveCommand::Initialize() noexcept +{ + driveSubsystem->Drive(0.0_fps, 0.0_fps, 0.0_fps, true); +} + +void DriveCommand::Execute() noexcept +{ + // TODO: call handle drive subsystem drive + // this will need to accept a parameter in the constructor + // and will be implemented similar to RobotContainer.cpp + // drivecommandfactory. + // Instead of reading the values from the controller input + // they will be passed in as parameters. +} + +void DriveCommand::End(bool interrupted) noexcept +{ + driveSubsystem->Drive(0.0_fps, 0.0_fps, 0.0_fps, true); +} diff --git a/src/main/include/commands/DriveCommands.h b/src/main/include/commands/DriveCommands.h new file mode 100644 index 0000000..e00c61e --- /dev/null +++ b/src/main/include/commands/DriveCommands.h @@ -0,0 +1,59 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include + +#include "subsystems/DriveSubsystem.h" + +#include + +// Home all swerve modules to zero heading. +class ZeroTurningModules + : public frc2::CommandHelper +{ +public: + explicit ZeroTurningModules(DriveSubsystem *driveSubsystem) noexcept + : driveSubsystem{driveSubsystem} { SetName("Zero"); } + + void Initialize() noexcept override {} + void Execute() noexcept override; + void End(bool interrupted) noexcept override {} + bool IsFinished() noexcept override { return finished; } + + static frc2::CommandPtr ZeroCommandFactory(DriveSubsystem *driveSubsystem) noexcept + { + return frc2::CommandPtr{std::make_unique(driveSubsystem)}; + } + +private: + DriveSubsystem *driveSubsystem{nullptr}; + bool finished{false}; +}; + +// Expose turning maximum velocity and acceleration. +class DriveCommand + : public frc2::CommandHelper +{ +public: + explicit DriveCommand(DriveSubsystem *driveSubsystem) noexcept + : driveSubsystem{driveSubsystem} { SetName("DriveCommand"); } + + void Initialize() noexcept override; + void Execute() noexcept override; + void End(bool interrupted) noexcept override; + bool IsFinished() noexcept override { return finished; } + + static frc2::CommandPtr DriveCommandFactory(DriveSubsystem *driveSubsystem) noexcept + { + return frc2::CommandPtr{std::make_unique(driveSubsystem)}; + } + +private: + DriveSubsystem *driveSubsystem{nullptr}; + bool finished{false}; +}; From 1128d9b212da784aad702f9243208ab3967296d2 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Fri, 16 Feb 2024 19:27:22 -0500 Subject: [PATCH 069/126] drive commands compile now --- src/main/cpp/commands/DriveCommands.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/commands/DriveCommands.cpp b/src/main/cpp/commands/DriveCommands.cpp index e3de59f..b8e2c44 100644 --- a/src/main/cpp/commands/DriveCommands.cpp +++ b/src/main/cpp/commands/DriveCommands.cpp @@ -10,7 +10,7 @@ void ZeroTurningModules::Execute() noexcept { (void)driveSubsystem->ZeroModules( void DriveCommand::Initialize() noexcept { - driveSubsystem->Drive(0.0_fps, 0.0_fps, 0.0_fps, true); + driveSubsystem->Drive(0.0_mps, 0.0_mps, 0.0_rad_per_s, true); } void DriveCommand::Execute() noexcept @@ -25,5 +25,5 @@ void DriveCommand::Execute() noexcept void DriveCommand::End(bool interrupted) noexcept { - driveSubsystem->Drive(0.0_fps, 0.0_fps, 0.0_fps, true); + driveSubsystem->Drive(0.0_mps, 0.0_mps, 0.0_rad_per_s, true); } From 73dc6e07b86d33bdbcaca25b9ed30fa275f77f4d Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 17 Feb 2024 10:17:48 -0800 Subject: [PATCH 070/126] update turning motor inversion --- src/main/include/Constants.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 3c252a5..5db46be 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -75,13 +75,10 @@ namespace physical constexpr int kRearRightTurningEncoderPort = 11; constexpr int kRearLeftTurningEncoderPort = 12; constexpr int kFrontLeftTurningEncoderPort = 13; - - - // These can flip because of gearing. constexpr bool kDriveMotorInverted = false; - constexpr bool kTurningMotorInverted = false; + constexpr bool kTurningMotorInverted = true; } namespace firmware @@ -98,7 +95,7 @@ namespace pidf constexpr double kTurningPositionI = 0.0; constexpr double kTurningPositionIZ = 0.0; constexpr double kTurningPositionIM = 0.0; - constexpr double kTurningPositionD = 0.000; + constexpr double kTurningPositionD = 0.000395; constexpr double kTurningPositionDF = 0.0; constexpr double kDrivePositionMaxVelocity = 5700.0; // Rotations per minute. From 1e2913d7c1df1706be84fd99c62cb07d8e51ad10 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:35:22 -0800 Subject: [PATCH 071/126] add intake binding --- src/main/cpp/RobotContainer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index e84ca79..d1ea702 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -272,6 +272,11 @@ void RobotContainer::ConfigureBindings() noexcept .ToPtr()); // TODO: decide if we want to bind wheel lock + + m_xbox.A().OnTrue(frc2::InstantCommand([&]() -> void + { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorVoltagePercent); }, + {&m_intakeSubsystem}) + .ToPtr()); } #pragma endregion From e3e26ca5c6361c8bfef713a5f3d1c77f0c22ca80 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:36:58 -0800 Subject: [PATCH 072/126] Update Constants.h --- src/main/include/Constants.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 5db46be..e6133da 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -159,5 +159,7 @@ namespace intake constexpr units::degree_t kIntakeArmHome = 90.0_deg; constexpr units::degree_t kIntakeArmPickup = 180.0_deg; constexpr units::degree_t kIntakeArmLoad = 0.0_deg; + + constexpr double kIntakeSpinMotorVoltagePercent = .30; } From ef1d86dcaf839be4f7038d0461fc44ee661fa8ec Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Sat, 17 Feb 2024 15:42:17 -0500 Subject: [PATCH 073/126] Add CurrentStall Limits for drive and turning motors --- src/main/cpp/infrastructure/SwerveModule.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/cpp/infrastructure/SwerveModule.cpp b/src/main/cpp/infrastructure/SwerveModule.cpp index 2207d74..030e817 100644 --- a/src/main/cpp/infrastructure/SwerveModule.cpp +++ b/src/main/cpp/infrastructure/SwerveModule.cpp @@ -88,6 +88,7 @@ SwerveModule::SwerveModule( {"kStatus2", uint{250}}, // ms {"kPositionConversionFactor", double{360.0}}, {"kVelocityConversionFactor", double{360.0 / 60.0}}, + {"kSmartCurrentStallLimit", uint{40}}, // Amps }); m_turningMotor->ApplyConfig(false); @@ -97,6 +98,7 @@ SwerveModule::SwerveModule( {"kStatus2", uint{250}}, // ms {"kPositionConversionFactor", double{physical::kDriveMetersPerRotation}}, {"kVelocityConversionFactor", double{physical::kDriveMetersPerRotation / 60.0}}, + {"kSmartCurrentStallLimit", uint{40}}, // Amps }); m_driveMotor->ApplyConfig(false); From 19194f8d80d629c075eff77358545d0be91cd6e1 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:50:11 -0500 Subject: [PATCH 074/126] fix field relative --- src/main/cpp/subsystems/DriveSubsystem.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/cpp/subsystems/DriveSubsystem.cpp b/src/main/cpp/subsystems/DriveSubsystem.cpp index c67cb52..ab32fb0 100644 --- a/src/main/cpp/subsystems/DriveSubsystem.cpp +++ b/src/main/cpp/subsystems/DriveSubsystem.cpp @@ -163,7 +163,7 @@ void DriveSubsystem::Periodic() noexcept { if (m_ahrs) { - botRot = -m_ahrs->GetRotation2d(); + botRot = m_ahrs->GetRotation2d(); } else { @@ -223,7 +223,7 @@ void DriveSubsystem::ResetOdometry(frc::Pose2d pose) noexcept { if (m_ahrs) { - botRot = -m_ahrs->GetRotation2d(); + botRot = m_ahrs->GetRotation2d(); } }); m_odometry->ResetPosition(botRot, GetModulePositions(), pose); @@ -998,7 +998,7 @@ void DriveSubsystem::Drive(units::meters_per_second_t xSpeed, { if (m_ahrs) { - botRot = -m_ahrs->GetRotation2d(); + botRot = m_ahrs->GetRotation2d(); frc::SmartDashboard::PutNumber("BotRot", botRot.Degrees().value()); } }); @@ -1114,7 +1114,7 @@ units::degree_t DriveSubsystem::GetHeading() noexcept { if (m_ahrs) { - heading = units::degree_t{-m_ahrs->GetAngle()}; // In degrees already. + heading = units::degree_t{m_ahrs->GetAngle()}; // In degrees already. } }); return heading; @@ -1138,7 +1138,7 @@ double DriveSubsystem::GetTurnRate() noexcept { if (m_ahrs) { - rate = -m_ahrs->GetRate(); // In degrees/second (units not used in WPILib). + rate = m_ahrs->GetRate(); // In degrees/second (units not used in WPILib). } }); return rate; From 990452517a60d235a4e005cb1158ad4168d49b8c Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:50:21 -0500 Subject: [PATCH 075/126] Update Constants.h --- src/main/include/Constants.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index e6133da..025d431 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -144,14 +144,9 @@ namespace shooter namespace intake { - // Arm Motor Constructor Parameters - constexpr std::string_view kIntakeArmMotorName = "ArmMotor"; - constexpr int kIntakeArmMotorCanID = 16; - constexpr bool kIntakeArmMotorIsInverted = false; - // Intake Spin Motor Constructor Parameters constexpr std::string_view kIntakeSpinMotorName = "IntakeSpinMotor"; - constexpr int kIntakeSpinMotorCanID = 17; + constexpr int kIntakeSpinMotorCanID = 16; constexpr bool kIntakeSpinMotorIsInverted = false; /* NOTE!!!: The intake arm values are NOT final, are subject to change @@ -160,6 +155,11 @@ namespace intake constexpr units::degree_t kIntakeArmPickup = 180.0_deg; constexpr units::degree_t kIntakeArmLoad = 0.0_deg; - constexpr double kIntakeSpinMotorVoltagePercent = .30; + constexpr double kIntakeSpinMotorVoltagePercent = 1; + + // Arm Motor Constructor Parameters + constexpr std::string_view kIntakeArmMotorName = "ArmMotor"; + constexpr int kIntakeArmMotorCanID = 17; + constexpr bool kIntakeArmMotorIsInverted = false; } From dab562799db668845d64f40ec8131e685aa1ab57 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:10:03 -0500 Subject: [PATCH 076/126] reduce default current limit --- src/main/include/infrastructure/SparkMax.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/include/infrastructure/SparkMax.h b/src/main/include/infrastructure/SparkMax.h index ca52a57..93897e2 100644 --- a/src/main/include/infrastructure/SparkMax.h +++ b/src/main/include/infrastructure/SparkMax.h @@ -412,7 +412,7 @@ namespace SparkFactory {"kAltEncoderVelocityFactor", double{1.0}}, {"kCurrentChop", double{115.0}}, // Amps {"kCurrentChopCycles", uint{0}}, - {"kSmartCurrentStallLimit", uint{80}}, // Amps + {"kSmartCurrentStallLimit", uint{40}}, // Amps {"kSmartCurrentFreeLimit", uint{20}}, // Amps {"kSmartCurrentConfig", uint{10000}}, // RPM {"kP_0", double{0.0}}, From 6fdb326a82d774788ce1cf3faf1ac33336bbc08f Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sat, 17 Feb 2024 17:10:40 -0500 Subject: [PATCH 077/126] various intake improvements --- src/main/cpp/RobotContainer.cpp | 4 ++++ src/main/cpp/subsystems/IntakeSubsystem.cpp | 1 + src/main/include/Constants.h | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index d1ea702..1b925c0 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -277,6 +277,10 @@ void RobotContainer::ConfigureBindings() noexcept { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorVoltagePercent); }, {&m_intakeSubsystem}) .ToPtr()); + m_xbox.A().OnFalse(frc2::InstantCommand([&]() -> void + { m_intakeSubsystem.SetSpinMotorVoltagePercent(0.0); }, + {&m_intakeSubsystem}) + .ToPtr()); } #pragma endregion diff --git a/src/main/cpp/subsystems/IntakeSubsystem.cpp b/src/main/cpp/subsystems/IntakeSubsystem.cpp index 66865bb..29552e2 100644 --- a/src/main/cpp/subsystems/IntakeSubsystem.cpp +++ b/src/main/cpp/subsystems/IntakeSubsystem.cpp @@ -18,6 +18,7 @@ IntakeSubsystem::IntakeSubsystem() noexcept const SmartMotorBase::ConfigMap moreTorque = { {"kSmartCurrentStallLimit", uint{30}}, // Amps + {"kMotorType", uint{0}} // Brushed }; m_ArmMotorBase = SparkFactory::CreateSparkMax( diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 025d431..48a662f 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -147,7 +147,7 @@ namespace intake // Intake Spin Motor Constructor Parameters constexpr std::string_view kIntakeSpinMotorName = "IntakeSpinMotor"; constexpr int kIntakeSpinMotorCanID = 16; - constexpr bool kIntakeSpinMotorIsInverted = false; + constexpr bool kIntakeSpinMotorIsInverted = true; /* NOTE!!!: The intake arm values are NOT final, are subject to change after testing*/ @@ -155,7 +155,7 @@ namespace intake constexpr units::degree_t kIntakeArmPickup = 180.0_deg; constexpr units::degree_t kIntakeArmLoad = 0.0_deg; - constexpr double kIntakeSpinMotorVoltagePercent = 1; + constexpr double kIntakeSpinMotorVoltagePercent = .15; // Arm Motor Constructor Parameters constexpr std::string_view kIntakeArmMotorName = "ArmMotor"; From 3453bbb0d4efe3ccb1a46fc178845d7626cfc7bf Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Sat, 17 Feb 2024 18:27:54 -0500 Subject: [PATCH 078/126] split intake and arm subsystems --- src/main/cpp/subsystems/IntakeSubsystem.cpp | 27 +------ .../cpp/subsystems/TransferArmSubsystem.cpp | 80 +++++++++++++++++++ src/main/include/subsystems/IntakeSubsystem.h | 5 +- .../include/subsystems/TransferArmSubsystem.h | 42 ++++++++++ 4 files changed, 125 insertions(+), 29 deletions(-) create mode 100644 src/main/cpp/subsystems/TransferArmSubsystem.cpp create mode 100644 src/main/include/subsystems/TransferArmSubsystem.h diff --git a/src/main/cpp/subsystems/IntakeSubsystem.cpp b/src/main/cpp/subsystems/IntakeSubsystem.cpp index 29552e2..afa74fd 100644 --- a/src/main/cpp/subsystems/IntakeSubsystem.cpp +++ b/src/main/cpp/subsystems/IntakeSubsystem.cpp @@ -12,42 +12,28 @@ IntakeSubsystem::IntakeSubsystem() noexcept {"kStatus2", uint{250}}, {"kIdleMode", uint{0}}, {"kRampRate", double{0.1}}, - {"kSmartCurrentStallLimit", uint{15}}, // Amps - {"kSmartCurrentFreeLimit", uint{10}}, // Amps - }; - - const SmartMotorBase::ConfigMap moreTorque = { {"kSmartCurrentStallLimit", uint{30}}, // Amps - {"kMotorType", uint{0}} // Brushed + {"kSmartCurrentFreeLimit", uint{10}}, // Amps }; - m_ArmMotorBase = SparkFactory::CreateSparkMax( - intake::kIntakeArmMotorName, - intake::kIntakeArmMotorCanID, - intake::kIntakeArmMotorIsInverted); m_SpinMotorBase = SparkFactory::CreateSparkMax( intake::kIntakeSpinMotorName, intake::kIntakeSpinMotorCanID, intake::kIntakeSpinMotorIsInverted); - m_ArmMotor = std::make_unique>(*m_ArmMotorBase); m_SpinMotor = std::make_unique>(*m_SpinMotorBase); - m_ArmMotor->SetConfig(config); m_SpinMotor->SetConfig(config); - m_SpinMotor->AddConfig(moreTorque); DisableIntake(); } void IntakeSubsystem::Periodic() noexcept { - m_ArmMotor->Periodic(); m_SpinMotor->Periodic(); } void IntakeSubsystem::DisableIntake() noexcept { - m_ArmMotor->Stop(); m_SpinMotorBase->Stop(); } @@ -56,27 +42,19 @@ void IntakeSubsystem::SetSpinMotorVoltagePercent(const double percent) noexcept m_SpinMotor->SetVoltage(percent * 12_V); } -void IntakeSubsystem::SetArmMotorVoltagePercent(const double percent) noexcept -{ - m_ArmMotor->SetVoltage(percent * 12_V); -} - #pragma region Utility bool IntakeSubsystem::GetStatus() const noexcept { - return m_ArmMotor->GetStatus() || - m_SpinMotor->GetStatus(); + return m_SpinMotor->GetStatus(); } void IntakeSubsystem::BurnConfig() noexcept { - m_ArmMotor->ApplyConfig(true); m_SpinMotor->ApplyConfig(true); } void IntakeSubsystem::ClearFaults() noexcept { - m_ArmMotor->ClearFaults(); m_SpinMotor->ClearFaults(); } #pragma endregion @@ -95,7 +73,6 @@ void IntakeSubsystem::TestPeriodic() noexcept m_verifyMotorControllersWhen = now + 15s; - m_ArmMotor->CheckConfig(); m_SpinMotor->CheckConfig(); } } diff --git a/src/main/cpp/subsystems/TransferArmSubsystem.cpp b/src/main/cpp/subsystems/TransferArmSubsystem.cpp new file mode 100644 index 0000000..c080f06 --- /dev/null +++ b/src/main/cpp/subsystems/TransferArmSubsystem.cpp @@ -0,0 +1,80 @@ +#include "subsystems/TransferArmSubsystem.h" + +#include "Constants.h" + +#include + +TransferArmSubsystem::TransferArmSubsystem() noexcept +{ + // TODO: add comment here saying what motor this is for + const SmartMotorBase::ConfigMap config = { + {"kStatus1", uint{250}}, + {"kStatus2", uint{250}}, + {"kIdleMode", uint{0}}, + {"kRampRate", double{0.1}}, + {"kSmartCurrentStallLimit", uint{15}}, // Amps + {"kSmartCurrentFreeLimit", uint{10}}, // Amps + }; + + m_ArmMotorBase = SparkFactory::CreateSparkMax( + intake::kIntakeArmMotorName, + intake::kIntakeArmMotorCanID, + intake::kIntakeArmMotorIsInverted); + m_ArmMotor = std::make_unique>(*m_ArmMotorBase); + + m_ArmMotor->SetConfig(config); + + DisableIntake(); +} + +void TransferArmSubsystem::Periodic() noexcept +{ + m_ArmMotor->Periodic(); +} + +void TransferArmSubsystem::DisableIntake() noexcept +{ + m_ArmMotor->Stop(); +} + + +void TransferArmSubsystem::SetArmMotorVoltagePercent(const double percent) noexcept +{ + m_ArmMotor->SetVoltage(percent * 12_V); +} + +#pragma region Utility +bool TransferArmSubsystem::GetStatus() const noexcept +{ + return m_ArmMotor->GetStatus(); +} + +void TransferArmSubsystem::BurnConfig() noexcept +{ + m_ArmMotor->ApplyConfig(true); +} + +void TransferArmSubsystem::ClearFaults() noexcept +{ + m_ArmMotor->ClearFaults(); +} +#pragma endregion + +#pragma region Test +void TransferArmSubsystem::TestInit() noexcept {} + +void TransferArmSubsystem::TestExit() noexcept {} + +void TransferArmSubsystem::TestPeriodic() noexcept +{ + const std::chrono::time_point now = std::chrono::steady_clock::now(); + if (now >= m_verifyMotorControllersWhen) + { + using namespace std::chrono_literals; + + m_verifyMotorControllersWhen = now + 15s; + + m_ArmMotor->CheckConfig(); + } +} +#pragma endregion diff --git a/src/main/include/subsystems/IntakeSubsystem.h b/src/main/include/subsystems/IntakeSubsystem.h index 9903499..fe0b42b 100644 --- a/src/main/include/subsystems/IntakeSubsystem.h +++ b/src/main/include/subsystems/IntakeSubsystem.h @@ -19,12 +19,9 @@ class IntakeSubsystem : public frc2::SubsystemBase void Periodic() noexcept override; void DisableIntake() noexcept; void SetSpinMotorVoltagePercent(const double percent) noexcept; - void SetArmMotorVoltagePercent(const double percent) noexcept; // must be used in a PID loop to set arm position - + private: - std::unique_ptr m_ArmMotorBase; std::unique_ptr m_SpinMotorBase; - std::unique_ptr> m_ArmMotor; std::unique_ptr> m_SpinMotor; #pragma region Utility diff --git a/src/main/include/subsystems/TransferArmSubsystem.h b/src/main/include/subsystems/TransferArmSubsystem.h new file mode 100644 index 0000000..37c0643 --- /dev/null +++ b/src/main/include/subsystems/TransferArmSubsystem.h @@ -0,0 +1,42 @@ +#pragma once + +#include "infrastructure/SparkMax.h" + +#include +#include + +#include +#include + +class TransferArmSubsystem : public frc2::SubsystemBase +{ +public: + TransferArmSubsystem() noexcept; + + TransferArmSubsystem(const TransferArmSubsystem &) = delete; + TransferArmSubsystem &operator=(const TransferArmSubsystem &) = delete; + + void Periodic() noexcept override; + void DisableIntake() noexcept; + void SetArmMotorVoltagePercent(const double percent) noexcept; // must be used in a PID loop to set arm position + +private: + std::unique_ptr m_ArmMotorBase; + std::unique_ptr> m_ArmMotor; + +#pragma region Utility +public: + bool GetStatus() const noexcept; + void BurnConfig() noexcept; + void ClearFaults() noexcept; +#pragma endregion + +#pragma region Test +public: + void TestInit() noexcept; + void TestExit() noexcept; + void TestPeriodic() noexcept; +private: + std::chrono::steady_clock::time_point m_verifyMotorControllersWhen; +#pragma endregion +}; From 8fb559b0db8ce63c989b12741f67d73a7ef6e299 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Sat, 17 Feb 2024 18:42:01 -0500 Subject: [PATCH 079/126] set shooter brushed motors --- src/main/cpp/subsystems/ShooterSubsystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/cpp/subsystems/ShooterSubsystem.cpp b/src/main/cpp/subsystems/ShooterSubsystem.cpp index 04806b0..77d4280 100644 --- a/src/main/cpp/subsystems/ShooterSubsystem.cpp +++ b/src/main/cpp/subsystems/ShooterSubsystem.cpp @@ -14,6 +14,7 @@ ShooterSubsystem::ShooterSubsystem() noexcept {"kRampRate", double{0.1}}, {"kSmartCurrentStallLimit", uint{25}}, // Amps {"kSmartCurrentFreeLimit", uint{10}}, + {"kMotorType", bool(0)}, //Set brushed motor for shooters }; m_LeftShooterMotorBase = SparkFactory::CreateSparkMax( From 1cc975e91b937412a87ca3fc8d22047a1000fd32 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sat, 17 Feb 2024 19:54:10 -0500 Subject: [PATCH 080/126] auto format --- src/main/cpp/RobotContainer.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 1b925c0..84746ba 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -85,7 +85,7 @@ void RobotContainer::TeleopInit() noexcept m_driveSubsystem.ResetEncoders(); m_driveSubsystem.ZeroHeading(); - + m_driveSubsystem.SetDefaultCommand(DriveCommandFactory(this)); m_intakeSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, {&m_intakeSubsystem})); @@ -227,7 +227,7 @@ double RobotContainer::ConditionRawJoystickInput(double RawJoystickVal, double m power. The 'mixer` parameter is used to shape the `raw` input, some mix between out = in^3.0 and out = in. */ - + // Input deadband around 0.0 (+/- range). constexpr double deadZoneVal = 0.05; @@ -255,10 +255,10 @@ void RobotContainer::ConfigureBindings() noexcept { // TODO: define Keybindings here m_xbox.Start().OnTrue( - frc2::InstantCommand([&]() -> void - { triggerSpeedEnabled = !triggerSpeedEnabled; }, - {} - ).ToPtr()); + frc2::InstantCommand([&]() -> void + { triggerSpeedEnabled = !triggerSpeedEnabled; }, + {}) + .ToPtr()); // TODO: decide if we want this m_xbox.X().OnTrue(frc2::InstantCommand([&]() -> void @@ -278,9 +278,9 @@ void RobotContainer::ConfigureBindings() noexcept {&m_intakeSubsystem}) .ToPtr()); m_xbox.A().OnFalse(frc2::InstantCommand([&]() -> void - { m_intakeSubsystem.SetSpinMotorVoltagePercent(0.0); }, - {&m_intakeSubsystem}) - .ToPtr()); + { m_intakeSubsystem.SetSpinMotorVoltagePercent(0.0); }, + {&m_intakeSubsystem}) + .ToPtr()); } #pragma endregion From 5ff94a8806394eefffd13b74d7f5769eaf4983c8 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sat, 17 Feb 2024 19:54:22 -0500 Subject: [PATCH 081/126] add motor type config --- src/main/cpp/subsystems/IntakeSubsystem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/cpp/subsystems/IntakeSubsystem.cpp b/src/main/cpp/subsystems/IntakeSubsystem.cpp index afa74fd..b35e19e 100644 --- a/src/main/cpp/subsystems/IntakeSubsystem.cpp +++ b/src/main/cpp/subsystems/IntakeSubsystem.cpp @@ -13,7 +13,8 @@ IntakeSubsystem::IntakeSubsystem() noexcept {"kIdleMode", uint{0}}, {"kRampRate", double{0.1}}, {"kSmartCurrentStallLimit", uint{30}}, // Amps - {"kSmartCurrentFreeLimit", uint{10}}, // Amps + {"kSmartCurrentFreeLimit", uint{10}}, // Amps + {"kMotorType", uint{0}}, // Brushed }; m_SpinMotorBase = SparkFactory::CreateSparkMax( From eb053c3fd9156503fa450d2d73ceba90ddad2469 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 18 Feb 2024 09:58:40 -0500 Subject: [PATCH 082/126] update intake motor to brushed --- src/main/cpp/RobotContainer.cpp | 12 +--- src/main/cpp/commands/AutonomousCommands.cpp | 4 +- src/main/cpp/subsystems/IntakeSubsystem.cpp | 68 ++----------------- src/main/include/subsystems/IntakeSubsystem.h | 24 ++----- 4 files changed, 11 insertions(+), 97 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 84746ba..5f71518 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -40,7 +40,6 @@ RobotContainer::RobotContainer() noexcept void RobotContainer::AutonomousInit() noexcept { m_driveSubsystem.ClearFaults(); - m_intakeSubsystem.ClearFaults(); m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); @@ -80,7 +79,6 @@ std::optional RobotContainer::GetAutonomousCommand() noexcept void RobotContainer::TeleopInit() noexcept { m_driveSubsystem.ClearFaults(); - m_intakeSubsystem.ClearFaults(); m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); @@ -278,7 +276,7 @@ void RobotContainer::ConfigureBindings() noexcept {&m_intakeSubsystem}) .ToPtr()); m_xbox.A().OnFalse(frc2::InstantCommand([&]() -> void - { m_intakeSubsystem.SetSpinMotorVoltagePercent(0.0); }, + { m_intakeSubsystem.StopIntake(); }, {&m_intakeSubsystem}) .ToPtr()); } @@ -290,13 +288,11 @@ void RobotContainer::TestInit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_intakeSubsystem.ClearFaults(); m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.TestInit(); - m_intakeSubsystem.TestInit(); m_shooterSubsystem.TestInit(); frc::SendableChooser> *chooser{m_driveSubsystem.TestModeChooser()}; @@ -320,7 +316,6 @@ void RobotContainer::TestInit() noexcept void RobotContainer::TestPeriodic() noexcept { m_driveSubsystem.TestPeriodic(); - m_intakeSubsystem.TestPeriodic(); m_shooterSubsystem.TestPeriodic(); } @@ -329,17 +324,14 @@ void RobotContainer::TestExit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_intakeSubsystem.ClearFaults(); m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.TestExit(); - m_intakeSubsystem.TestExit(); m_shooterSubsystem.TestExit(); m_driveSubsystem.BurnConfig(); - m_intakeSubsystem.BurnConfig(); m_shooterSubsystem.BurnConfig(); } @@ -370,7 +362,6 @@ void RobotContainer::DisabledInit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_intakeSubsystem.ClearFaults(); m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); @@ -386,7 +377,6 @@ void RobotContainer::DisabledPeriodic() noexcept {} void RobotContainer::DisabledExit() noexcept { m_driveSubsystem.ClearFaults(); - m_intakeSubsystem.ClearFaults(); m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); diff --git a/src/main/cpp/commands/AutonomousCommands.cpp b/src/main/cpp/commands/AutonomousCommands.cpp index 61e1804..8cb19ed 100644 --- a/src/main/cpp/commands/AutonomousCommands.cpp +++ b/src/main/cpp/commands/AutonomousCommands.cpp @@ -14,7 +14,7 @@ void TimedAutoBase::Initialize() noexcept m_drive->ResetDrive(); m_drive->ZeroHeading(); - m_Intake->DisableIntake(); + m_Intake->StopIntake(); m_shooter->Stop(); @@ -27,7 +27,7 @@ void TimedAutoBase::End(bool interrupted) noexcept { m_drive->Drive(0_mps, 0_mps, 0_deg_per_s, false); - m_Intake->DisableIntake(); + m_Intake->StopIntake(); m_shooter->Stop(); } diff --git a/src/main/cpp/subsystems/IntakeSubsystem.cpp b/src/main/cpp/subsystems/IntakeSubsystem.cpp index b35e19e..d412655 100644 --- a/src/main/cpp/subsystems/IntakeSubsystem.cpp +++ b/src/main/cpp/subsystems/IntakeSubsystem.cpp @@ -6,75 +6,15 @@ IntakeSubsystem::IntakeSubsystem() noexcept { - // TODO: add comment here saying what motor this is for - const SmartMotorBase::ConfigMap config = { - {"kStatus1", uint{250}}, - {"kStatus2", uint{250}}, - {"kIdleMode", uint{0}}, - {"kRampRate", double{0.1}}, - {"kSmartCurrentStallLimit", uint{30}}, // Amps - {"kSmartCurrentFreeLimit", uint{10}}, // Amps - {"kMotorType", uint{0}}, // Brushed - }; - - m_SpinMotorBase = SparkFactory::CreateSparkMax( - intake::kIntakeSpinMotorName, - intake::kIntakeSpinMotorCanID, - intake::kIntakeSpinMotorIsInverted); - m_SpinMotor = std::make_unique>(*m_SpinMotorBase); - - m_SpinMotor->SetConfig(config); - - DisableIntake(); -} - -void IntakeSubsystem::Periodic() noexcept -{ - m_SpinMotor->Periodic(); + StopIntake(); } -void IntakeSubsystem::DisableIntake() noexcept +void IntakeSubsystem::StopIntake() noexcept { - m_SpinMotorBase->Stop(); + m_motor.StopMotor(); } void IntakeSubsystem::SetSpinMotorVoltagePercent(const double percent) noexcept { - m_SpinMotor->SetVoltage(percent * 12_V); -} - -#pragma region Utility -bool IntakeSubsystem::GetStatus() const noexcept -{ - return m_SpinMotor->GetStatus(); -} - -void IntakeSubsystem::BurnConfig() noexcept -{ - m_SpinMotor->ApplyConfig(true); -} - -void IntakeSubsystem::ClearFaults() noexcept -{ - m_SpinMotor->ClearFaults(); -} -#pragma endregion - -#pragma region Test -void IntakeSubsystem::TestInit() noexcept {} - -void IntakeSubsystem::TestExit() noexcept {} - -void IntakeSubsystem::TestPeriodic() noexcept -{ - const std::chrono::time_point now = std::chrono::steady_clock::now(); - if (now >= m_verifyMotorControllersWhen) - { - using namespace std::chrono_literals; - - m_verifyMotorControllersWhen = now + 15s; - - m_SpinMotor->CheckConfig(); - } + m_motor.SetVoltage(percent * 12_V); } -#pragma endregion diff --git a/src/main/include/subsystems/IntakeSubsystem.h b/src/main/include/subsystems/IntakeSubsystem.h index fe0b42b..befaeff 100644 --- a/src/main/include/subsystems/IntakeSubsystem.h +++ b/src/main/include/subsystems/IntakeSubsystem.h @@ -1,9 +1,11 @@ #pragma once #include "infrastructure/SparkMax.h" +#include "rev/CANSparkMax.h" #include #include +#include "Constants.h" #include #include @@ -16,27 +18,9 @@ class IntakeSubsystem : public frc2::SubsystemBase IntakeSubsystem(const IntakeSubsystem &) = delete; IntakeSubsystem &operator=(const IntakeSubsystem &) = delete; - void Periodic() noexcept override; - void DisableIntake() noexcept; + void StopIntake() noexcept; void SetSpinMotorVoltagePercent(const double percent) noexcept; private: - std::unique_ptr m_SpinMotorBase; - std::unique_ptr> m_SpinMotor; - -#pragma region Utility -public: - bool GetStatus() const noexcept; - void BurnConfig() noexcept; - void ClearFaults() noexcept; -#pragma endregion - -#pragma region Test -public: - void TestInit() noexcept; - void TestExit() noexcept; - void TestPeriodic() noexcept; -private: - std::chrono::steady_clock::time_point m_verifyMotorControllersWhen; -#pragma endregion + rev::CANSparkMax m_motor{intake::kIntakeSpinMotorCanID, rev::CANSparkMax::MotorType::kBrushed}; }; From bcd814f5d2b001a508c27c14ff2be2c21e5f723c Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 18 Feb 2024 09:33:03 -0800 Subject: [PATCH 083/126] remove unnecessary include --- src/main/include/subsystems/IntakeSubsystem.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/include/subsystems/IntakeSubsystem.h b/src/main/include/subsystems/IntakeSubsystem.h index befaeff..027118b 100644 --- a/src/main/include/subsystems/IntakeSubsystem.h +++ b/src/main/include/subsystems/IntakeSubsystem.h @@ -1,6 +1,5 @@ #pragma once -#include "infrastructure/SparkMax.h" #include "rev/CANSparkMax.h" #include From 7d8a5f22c81f0645d2971c395c68e1e66e24a586 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 18 Feb 2024 09:33:20 -0800 Subject: [PATCH 084/126] convert to standard rev code --- .../cpp/subsystems/TransferArmSubsystem.cpp | 68 ++----------------- .../include/subsystems/TransferArmSubsystem.h | 25 ++----- 2 files changed, 8 insertions(+), 85 deletions(-) diff --git a/src/main/cpp/subsystems/TransferArmSubsystem.cpp b/src/main/cpp/subsystems/TransferArmSubsystem.cpp index c080f06..bae3e8f 100644 --- a/src/main/cpp/subsystems/TransferArmSubsystem.cpp +++ b/src/main/cpp/subsystems/TransferArmSubsystem.cpp @@ -6,75 +6,15 @@ TransferArmSubsystem::TransferArmSubsystem() noexcept { - // TODO: add comment here saying what motor this is for - const SmartMotorBase::ConfigMap config = { - {"kStatus1", uint{250}}, - {"kStatus2", uint{250}}, - {"kIdleMode", uint{0}}, - {"kRampRate", double{0.1}}, - {"kSmartCurrentStallLimit", uint{15}}, // Amps - {"kSmartCurrentFreeLimit", uint{10}}, // Amps - }; - - m_ArmMotorBase = SparkFactory::CreateSparkMax( - intake::kIntakeArmMotorName, - intake::kIntakeArmMotorCanID, - intake::kIntakeArmMotorIsInverted); - m_ArmMotor = std::make_unique>(*m_ArmMotorBase); - - m_ArmMotor->SetConfig(config); - - DisableIntake(); -} - -void TransferArmSubsystem::Periodic() noexcept -{ - m_ArmMotor->Periodic(); + StopIntake(); } -void TransferArmSubsystem::DisableIntake() noexcept +void TransferArmSubsystem::StopIntake() noexcept { - m_ArmMotor->Stop(); + m_motor.StopMotor(); } - void TransferArmSubsystem::SetArmMotorVoltagePercent(const double percent) noexcept { - m_ArmMotor->SetVoltage(percent * 12_V); -} - -#pragma region Utility -bool TransferArmSubsystem::GetStatus() const noexcept -{ - return m_ArmMotor->GetStatus(); -} - -void TransferArmSubsystem::BurnConfig() noexcept -{ - m_ArmMotor->ApplyConfig(true); -} - -void TransferArmSubsystem::ClearFaults() noexcept -{ - m_ArmMotor->ClearFaults(); -} -#pragma endregion - -#pragma region Test -void TransferArmSubsystem::TestInit() noexcept {} - -void TransferArmSubsystem::TestExit() noexcept {} - -void TransferArmSubsystem::TestPeriodic() noexcept -{ - const std::chrono::time_point now = std::chrono::steady_clock::now(); - if (now >= m_verifyMotorControllersWhen) - { - using namespace std::chrono_literals; - - m_verifyMotorControllersWhen = now + 15s; - - m_ArmMotor->CheckConfig(); - } + m_motor.SetVoltage(percent * 12_V); } -#pragma endregion diff --git a/src/main/include/subsystems/TransferArmSubsystem.h b/src/main/include/subsystems/TransferArmSubsystem.h index 37c0643..1a3dc5f 100644 --- a/src/main/include/subsystems/TransferArmSubsystem.h +++ b/src/main/include/subsystems/TransferArmSubsystem.h @@ -1,9 +1,10 @@ #pragma once -#include "infrastructure/SparkMax.h" +#include "rev/CANSparkMax.h" #include #include +#include "Constants.h" #include #include @@ -16,27 +17,9 @@ class TransferArmSubsystem : public frc2::SubsystemBase TransferArmSubsystem(const TransferArmSubsystem &) = delete; TransferArmSubsystem &operator=(const TransferArmSubsystem &) = delete; - void Periodic() noexcept override; - void DisableIntake() noexcept; + void StopIntake() noexcept; void SetArmMotorVoltagePercent(const double percent) noexcept; // must be used in a PID loop to set arm position private: - std::unique_ptr m_ArmMotorBase; - std::unique_ptr> m_ArmMotor; - -#pragma region Utility -public: - bool GetStatus() const noexcept; - void BurnConfig() noexcept; - void ClearFaults() noexcept; -#pragma endregion - -#pragma region Test -public: - void TestInit() noexcept; - void TestExit() noexcept; - void TestPeriodic() noexcept; -private: - std::chrono::steady_clock::time_point m_verifyMotorControllersWhen; -#pragma endregion + rev::CANSparkMax m_motor{intake::kIntakeArmMotorCanID, rev::CANSparkMax::MotorType::kBrushless}; }; From 32792a46ee6e828be56857964762e853d1df25a4 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:55:03 -0800 Subject: [PATCH 085/126] initial transfer arm tuning commit --- .../commands/PositionTransferArmCommand.cpp | 25 ++++++++ .../cpp/subsystems/TransferArmSubsystem.cpp | 60 +++++++++++++++++-- .../commands/PositionTransferArmCommand.h | 44 ++++++++++++++ .../include/subsystems/TransferArmSubsystem.h | 9 ++- 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 src/main/cpp/commands/PositionTransferArmCommand.cpp create mode 100644 src/main/include/commands/PositionTransferArmCommand.h diff --git a/src/main/cpp/commands/PositionTransferArmCommand.cpp b/src/main/cpp/commands/PositionTransferArmCommand.cpp new file mode 100644 index 0000000..fa368a1 --- /dev/null +++ b/src/main/cpp/commands/PositionTransferArmCommand.cpp @@ -0,0 +1,25 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/PositionTransferArmCommand.h" +#include + +#include + +void PositionTransferArm::Initialize() noexcept +{ + transferArmSubsystem->SetTransferArmPosition(position); + timer.Reset(); +} + +void PositionTransferArm::Execute() noexcept +{ + transferArmSubsystem->UpdatePIDValues(); + if (timer.HasElapsed(5_s)) { finished = true; } +} + +void PositionTransferArm::End(bool interrupted) noexcept +{ + +} diff --git a/src/main/cpp/subsystems/TransferArmSubsystem.cpp b/src/main/cpp/subsystems/TransferArmSubsystem.cpp index bae3e8f..d4edef0 100644 --- a/src/main/cpp/subsystems/TransferArmSubsystem.cpp +++ b/src/main/cpp/subsystems/TransferArmSubsystem.cpp @@ -3,18 +3,70 @@ #include "Constants.h" #include +#include +#include TransferArmSubsystem::TransferArmSubsystem() noexcept { - StopIntake(); + m_motor.RestoreFactoryDefaults(); + + // set PID coefficients + m_pidController.SetP(kP); + m_pidController.SetI(kI); + m_pidController.SetD(kD); + m_pidController.SetIZone(kIz); + m_pidController.SetFF(kFF); + m_pidController.SetOutputRange(kMinOutput, kMaxOutput); + + // display PID coefficients on SmartDashboard + frc::SmartDashboard::PutNumber("P Gain", kP); + frc::SmartDashboard::PutNumber("I Gain", kI); + frc::SmartDashboard::PutNumber("D Gain", kD); + frc::SmartDashboard::PutNumber("I Zone", kIz); + frc::SmartDashboard::PutNumber("Feed Forward", kFF); + frc::SmartDashboard::PutNumber("Max Output", kMaxOutput); + frc::SmartDashboard::PutNumber("Min Output", kMinOutput); + frc::SmartDashboard::PutNumber("Set Rotations", 0); + + StopTransferArm(); } -void TransferArmSubsystem::StopIntake() noexcept +void TransferArmSubsystem::StopTransferArm() noexcept { m_motor.StopMotor(); } -void TransferArmSubsystem::SetArmMotorVoltagePercent(const double percent) noexcept +void TransferArmSubsystem::SetTransferArmPosition(const units::turn_t position) noexcept +{ + m_pidController.SetReference(position.value(), rev::CANSparkMax::ControlType::kPosition); + frc::SmartDashboard::PutNumber("SetPoint", position.value()); + frc::SmartDashboard::PutNumber("ProcessVariable", m_encoder.GetPosition()); +} + +units::turn_t TransferArmSubsystem::GetTransferArmPosition() noexcept { - m_motor.SetVoltage(percent * 12_V); + return units::turn_t(m_encoder.GetPosition()); +} + +void TransferArmSubsystem::UpdatePIDValues() noexcept +{ + double p = frc::SmartDashboard::GetNumber("P Gain", 0); + double i = frc::SmartDashboard::GetNumber("I Gain", 0); + double d = frc::SmartDashboard::GetNumber("D Gain", 0); + double iz = frc::SmartDashboard::GetNumber("I Zone", 0); + double ff = frc::SmartDashboard::GetNumber("Feed Forward", 0); + double max = frc::SmartDashboard::GetNumber("Max Output", 0); + double min = frc::SmartDashboard::GetNumber("Min Output", 0); + double rotations = frc::SmartDashboard::GetNumber("Set Rotations", 0); + + // if PID coefficients on SmartDashboard have changed, write new values to controller + if((p != kP)) { m_pidController.SetP(p); kP = p; } + if((i != kI)) { m_pidController.SetI(i); kI = i; } + if((d != kD)) { m_pidController.SetD(d); kD = d; } + if((iz != kIz)) { m_pidController.SetIZone(iz); kIz = iz; } + if((ff != kFF)) { m_pidController.SetFF(ff); kFF = ff; } + if((max != kMaxOutput) || (min != kMinOutput)) { + m_pidController.SetOutputRange(min, max); + kMinOutput = min; kMaxOutput = max; + } } diff --git a/src/main/include/commands/PositionTransferArmCommand.h b/src/main/include/commands/PositionTransferArmCommand.h new file mode 100644 index 0000000..e522b06 --- /dev/null +++ b/src/main/include/commands/PositionTransferArmCommand.h @@ -0,0 +1,44 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include +#include + +#include "subsystems/TransferArmSubsystem.h" + +#include + +// Expose turning maximum velocity and acceleration. +class PositionTransferArm + : public frc2::CommandHelper +{ +public: + explicit PositionTransferArm(TransferArmSubsystem *transferArmSubsystem, units::degree_t position) noexcept + : transferArmSubsystem{transferArmSubsystem}, position{position} + { + SetName("PositionTransferArm"); + AddRequirements(transferArmSubsystem); + } + + void Initialize() noexcept override; + void Execute() noexcept override; + void End(bool interrupted) noexcept override; + bool IsFinished() noexcept override { return finished; } + + static frc2::CommandPtr DriveCommandFactory(TransferArmSubsystem *transferArmSubsystem, units::degree_t position) noexcept + { + return frc2::CommandPtr{std::make_unique(transferArmSubsystem, position)}; + } + +private: + TransferArmSubsystem *transferArmSubsystem{nullptr}; + units::degree_t position; + frc::Timer timer{}; + bool finished{false}; +}; diff --git a/src/main/include/subsystems/TransferArmSubsystem.h b/src/main/include/subsystems/TransferArmSubsystem.h index 1a3dc5f..f34e6f2 100644 --- a/src/main/include/subsystems/TransferArmSubsystem.h +++ b/src/main/include/subsystems/TransferArmSubsystem.h @@ -17,9 +17,14 @@ class TransferArmSubsystem : public frc2::SubsystemBase TransferArmSubsystem(const TransferArmSubsystem &) = delete; TransferArmSubsystem &operator=(const TransferArmSubsystem &) = delete; - void StopIntake() noexcept; - void SetArmMotorVoltagePercent(const double percent) noexcept; // must be used in a PID loop to set arm position + void StopTransferArm() noexcept; + void SetTransferArmPosition(const units::turn_t position) noexcept; + units::turn_t GetTransferArmPosition() noexcept; + void UpdatePIDValues() noexcept; private: rev::CANSparkMax m_motor{intake::kIntakeArmMotorCanID, rev::CANSparkMax::MotorType::kBrushless}; + rev::SparkPIDController m_pidController = m_motor.GetPIDController(); + rev::SparkRelativeEncoder m_encoder = m_motor.GetEncoder(rev::SparkRelativeEncoder::Type::kQuadrature); + double kP = 0.001, kI = 0, kD = 0, kIz = 0, kFF = 0, kMaxOutput = 1, kMinOutput = -1; }; From 69c383f986e0e8d439c066a379ead30b13e621da Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:10:50 -0800 Subject: [PATCH 086/126] set up test keybinding --- src/main/cpp/RobotContainer.cpp | 24 ++++++++++++------- src/main/include/RobotContainer.h | 2 ++ .../commands/PositionTransferArmCommand.h | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 5f71518..2307c3b 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -16,6 +16,7 @@ #include #include #include +#include "commands/PositionTransferArmCommand.h" #include #include @@ -48,6 +49,8 @@ void RobotContainer::AutonomousInit() noexcept {&m_driveSubsystem})); m_intakeSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, {&m_intakeSubsystem})); + m_transferArmSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, + {&m_transferArmSubsystem})); m_shooterSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, {&m_shooterSubsystem})); m_infrastructureSubsystem.SetDefaultCommand(frc2::RunCommand([&]() -> void {}, @@ -259,15 +262,15 @@ void RobotContainer::ConfigureBindings() noexcept .ToPtr()); // TODO: decide if we want this - m_xbox.X().OnTrue(frc2::InstantCommand([&]() -> void - { m_fieldOriented = false; }, - {}) - .ToPtr()); - m_xbox.Y().OnTrue(frc2::InstantCommand([&]() -> void - { m_driveSubsystem.ZeroHeading(); - m_fieldOriented = true; }, - {&m_driveSubsystem}) - .ToPtr()); + // m_xbox.X().OnTrue(frc2::InstantCommand([&]() -> void + // { m_fieldOriented = false; }, + // {}) + // .ToPtr()); + // m_xbox.Y().OnTrue(frc2::InstantCommand([&]() -> void + // { m_driveSubsystem.ZeroHeading(); + // m_fieldOriented = true; }, + // {&m_driveSubsystem}) + // .ToPtr()); // TODO: decide if we want to bind wheel lock @@ -279,6 +282,9 @@ void RobotContainer::ConfigureBindings() noexcept { m_intakeSubsystem.StopIntake(); }, {&m_intakeSubsystem}) .ToPtr()); + + m_xbox.B().OnTrue(PositionTransferArm::PositionTransferArmCommandFactory(&m_transferArmSubsystem, 90_deg)); + } #pragma endregion diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index deb4a00..d45b6c3 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -12,6 +12,7 @@ #include "commands/TestModeCommands.h" #include "subsystems/DriveSubsystem.h" #include "subsystems/IntakeSubsystem.h" +#include "subsystems/TransferArmSubsystem.h" #include "subsystems/Infrastructure.h" #include "subsystems/ShooterSubsystem.h" #include @@ -85,6 +86,7 @@ class RobotContainer DriveSubsystem m_driveSubsystem; InfrastructureSubsystem m_infrastructureSubsystem; IntakeSubsystem m_intakeSubsystem; + TransferArmSubsystem m_transferArmSubsystem; ShooterSubsystem m_shooterSubsystem; // declared for the infrastructure subsystem diff --git a/src/main/include/commands/PositionTransferArmCommand.h b/src/main/include/commands/PositionTransferArmCommand.h index e522b06..aa3f638 100644 --- a/src/main/include/commands/PositionTransferArmCommand.h +++ b/src/main/include/commands/PositionTransferArmCommand.h @@ -31,7 +31,7 @@ class PositionTransferArm void End(bool interrupted) noexcept override; bool IsFinished() noexcept override { return finished; } - static frc2::CommandPtr DriveCommandFactory(TransferArmSubsystem *transferArmSubsystem, units::degree_t position) noexcept + static frc2::CommandPtr PositionTransferArmCommandFactory(TransferArmSubsystem *transferArmSubsystem, units::degree_t position) noexcept { return frc2::CommandPtr{std::make_unique(transferArmSubsystem, position)}; } From d65ebf3483e29eaee01a1bfbf72bc701b7e57762 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sun, 18 Feb 2024 16:05:07 -0500 Subject: [PATCH 087/126] disable through bore encoder --- src/main/cpp/subsystems/TransferArmSubsystem.cpp | 10 +++++----- src/main/include/subsystems/TransferArmSubsystem.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/cpp/subsystems/TransferArmSubsystem.cpp b/src/main/cpp/subsystems/TransferArmSubsystem.cpp index d4edef0..974f4f5 100644 --- a/src/main/cpp/subsystems/TransferArmSubsystem.cpp +++ b/src/main/cpp/subsystems/TransferArmSubsystem.cpp @@ -40,13 +40,13 @@ void TransferArmSubsystem::SetTransferArmPosition(const units::turn_t position) { m_pidController.SetReference(position.value(), rev::CANSparkMax::ControlType::kPosition); frc::SmartDashboard::PutNumber("SetPoint", position.value()); - frc::SmartDashboard::PutNumber("ProcessVariable", m_encoder.GetPosition()); + // frc::SmartDashboard::PutNumber("ProcessVariable", m_encoder.GetPosition()); } -units::turn_t TransferArmSubsystem::GetTransferArmPosition() noexcept -{ - return units::turn_t(m_encoder.GetPosition()); -} +// units::turn_t TransferArmSubsystem::GetTransferArmPosition() noexcept +// { +// return units::turn_t(m_encoder.GetPosition()); +// } void TransferArmSubsystem::UpdatePIDValues() noexcept { diff --git a/src/main/include/subsystems/TransferArmSubsystem.h b/src/main/include/subsystems/TransferArmSubsystem.h index f34e6f2..9ecfed1 100644 --- a/src/main/include/subsystems/TransferArmSubsystem.h +++ b/src/main/include/subsystems/TransferArmSubsystem.h @@ -19,12 +19,12 @@ class TransferArmSubsystem : public frc2::SubsystemBase void StopTransferArm() noexcept; void SetTransferArmPosition(const units::turn_t position) noexcept; - units::turn_t GetTransferArmPosition() noexcept; + // units::turn_t GetTransferArmPosition() noexcept; void UpdatePIDValues() noexcept; private: rev::CANSparkMax m_motor{intake::kIntakeArmMotorCanID, rev::CANSparkMax::MotorType::kBrushless}; rev::SparkPIDController m_pidController = m_motor.GetPIDController(); - rev::SparkRelativeEncoder m_encoder = m_motor.GetEncoder(rev::SparkRelativeEncoder::Type::kQuadrature); + //rev::CANEncoder m_encoder = m_motor.GetAlternateEncoder(rev::CANEncoder::AlternateEncoderType::kQuadrature, 8192); double kP = 0.001, kI = 0, kD = 0, kIz = 0, kFF = 0, kMaxOutput = 1, kMinOutput = -1; }; From 9f3db47b4b1cb8c5e81fd54e40f0a356d5c302e9 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 18 Feb 2024 21:01:53 -0500 Subject: [PATCH 088/126] complete subsystem refactor --- src/main/cpp/RobotContainer.cpp | 20 ++--- src/main/cpp/commands/AutonomousCommands.cpp | 4 +- src/main/cpp/subsystems/IntakeSubsystem.cpp | 8 +- src/main/cpp/subsystems/ShooterSubsystem.cpp | 88 ++----------------- .../cpp/subsystems/TransferArmSubsystem.cpp | 38 ++------ src/main/include/Constants.h | 27 +++--- src/main/include/subsystems/IntakeSubsystem.h | 9 +- .../include/subsystems/ShooterSubsystem.h | 45 +--------- .../include/subsystems/TransferArmSubsystem.h | 16 ++-- 9 files changed, 57 insertions(+), 198 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 2307c3b..afd8706 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -41,7 +41,6 @@ RobotContainer::RobotContainer() noexcept void RobotContainer::AutonomousInit() noexcept { m_driveSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); @@ -82,7 +81,6 @@ std::optional RobotContainer::GetAutonomousCommand() noexcept void RobotContainer::TeleopInit() noexcept { m_driveSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.ZeroHeading(); @@ -283,8 +281,14 @@ void RobotContainer::ConfigureBindings() noexcept {&m_intakeSubsystem}) .ToPtr()); - m_xbox.B().OnTrue(PositionTransferArm::PositionTransferArmCommandFactory(&m_transferArmSubsystem, 90_deg)); - + m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void + { m_shooterSubsystem.SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); }, + {&m_shooterSubsystem}) + .ToPtr()); + m_xbox.B().OnFalse(frc2::InstantCommand([&]() -> void + { m_shooterSubsystem.StopShooter(); }, + {&m_shooterSubsystem}) + .ToPtr()); } #pragma endregion @@ -294,12 +298,10 @@ void RobotContainer::TestInit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.TestInit(); - m_shooterSubsystem.TestInit(); frc::SendableChooser> *chooser{m_driveSubsystem.TestModeChooser()}; @@ -322,7 +324,6 @@ void RobotContainer::TestInit() noexcept void RobotContainer::TestPeriodic() noexcept { m_driveSubsystem.TestPeriodic(); - m_shooterSubsystem.TestPeriodic(); } void RobotContainer::TestExit() noexcept @@ -330,15 +331,12 @@ void RobotContainer::TestExit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); m_driveSubsystem.TestExit(); - m_shooterSubsystem.TestExit(); m_driveSubsystem.BurnConfig(); - m_shooterSubsystem.BurnConfig(); } frc2::CommandPtr RobotContainer::PointCommandFactory(RobotContainer *container) noexcept @@ -368,7 +366,6 @@ void RobotContainer::DisabledInit() noexcept frc2::CommandScheduler::GetInstance().CancelAll(); m_driveSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); @@ -383,7 +380,6 @@ void RobotContainer::DisabledPeriodic() noexcept {} void RobotContainer::DisabledExit() noexcept { m_driveSubsystem.ClearFaults(); - m_shooterSubsystem.ClearFaults(); m_driveSubsystem.ResetEncoders(); diff --git a/src/main/cpp/commands/AutonomousCommands.cpp b/src/main/cpp/commands/AutonomousCommands.cpp index 8cb19ed..dda7726 100644 --- a/src/main/cpp/commands/AutonomousCommands.cpp +++ b/src/main/cpp/commands/AutonomousCommands.cpp @@ -16,7 +16,7 @@ void TimedAutoBase::Initialize() noexcept m_Intake->StopIntake(); - m_shooter->Stop(); + m_shooter->StopShooter(); finished_ = false; FPGATime_ = frc::RobotController::GetFPGATime(); @@ -29,7 +29,7 @@ void TimedAutoBase::End(bool interrupted) noexcept m_Intake->StopIntake(); - m_shooter->Stop(); + m_shooter->StopShooter(); } void TimedAutoBase::Execute() noexcept diff --git a/src/main/cpp/subsystems/IntakeSubsystem.cpp b/src/main/cpp/subsystems/IntakeSubsystem.cpp index d412655..6a35853 100644 --- a/src/main/cpp/subsystems/IntakeSubsystem.cpp +++ b/src/main/cpp/subsystems/IntakeSubsystem.cpp @@ -1,20 +1,18 @@ #include "subsystems/IntakeSubsystem.h" - -#include "Constants.h" - #include IntakeSubsystem::IntakeSubsystem() noexcept { + m_IntakeMotor.SetInverted(intake::kIntakeSpinMotorIsInverted); StopIntake(); } void IntakeSubsystem::StopIntake() noexcept { - m_motor.StopMotor(); + m_IntakeMotor.StopMotor(); } void IntakeSubsystem::SetSpinMotorVoltagePercent(const double percent) noexcept { - m_motor.SetVoltage(percent * 12_V); + m_IntakeMotor.SetVoltage(percent * 12_V); } diff --git a/src/main/cpp/subsystems/ShooterSubsystem.cpp b/src/main/cpp/subsystems/ShooterSubsystem.cpp index 77d4280..a0db7da 100644 --- a/src/main/cpp/subsystems/ShooterSubsystem.cpp +++ b/src/main/cpp/subsystems/ShooterSubsystem.cpp @@ -1,93 +1,21 @@ #include "subsystems/ShooterSubsystem.h" - -#include "Constants.h" - #include ShooterSubsystem::ShooterSubsystem() noexcept { - // TODO: add comment here saying what motor this is for - const SmartMotorBase::ConfigMap config = { - {"kStatus1", uint{250}}, - {"kStatus2", uint{250}}, - {"kIdleMode", uint{0}}, - {"kRampRate", double{0.1}}, - {"kSmartCurrentStallLimit", uint{25}}, // Amps - {"kSmartCurrentFreeLimit", uint{10}}, - {"kMotorType", bool(0)}, //Set brushed motor for shooters - }; - - m_LeftShooterMotorBase = SparkFactory::CreateSparkMax( - shooter::kLeftShooterMotorName, - shooter::kLeftShooterMotorCanID, - shooter::kLeftShooterMotorIsInverted); - m_RightShooterMotorBase = SparkFactory::CreateSparkMax( - shooter::kRightShooterMotorName, - shooter::kRightShooterMotorCanID, - shooter::kRightShooterMotorIsInverted); - m_LeftShooterMotor = std::make_unique>(*m_LeftShooterMotorBase); - m_RightShooterMotor = std::make_unique>(*m_RightShooterMotorBase); - - m_LeftShooterMotor->SetConfig(config); - m_RightShooterMotor->SetConfig(config); - - Stop(); + m_LeftShooterMotor.SetInverted(shooter::kLeftShooterMotorIsInverted); + m_RightShooterMotor.SetInverted(shooter::kRightShooterMotorIsInverted); + StopShooter(); } -void ShooterSubsystem::Periodic() noexcept +void ShooterSubsystem::StopShooter() noexcept { - m_LeftShooterMotor->Periodic(); - m_RightShooterMotor->Periodic(); + m_LeftShooterMotor.StopMotor(); + m_RightShooterMotor.StopMotor(); } void ShooterSubsystem::SetShooterMotorVoltagePercent(const double percent) noexcept { - m_LeftShooterMotor->SetVoltage(percent * 12.00_V); - m_RightShooterMotor->SetVoltage(percent * 12.00_V); -} - -void ShooterSubsystem::Stop() noexcept -{ - m_LeftShooterMotor->Stop(); - m_RightShooterMotor->Stop(); -} - -#pragma region Utility -bool ShooterSubsystem::GetStatus() const noexcept -{ - return m_LeftShooterMotor->GetStatus() || - m_RightShooterMotor->GetStatus(); -} - -void ShooterSubsystem::BurnConfig() noexcept -{ - m_LeftShooterMotor->ApplyConfig(true); - m_RightShooterMotor->ApplyConfig(true); -} - -void ShooterSubsystem::ClearFaults() noexcept -{ - m_LeftShooterMotor->ClearFaults(); - m_RightShooterMotor->ClearFaults(); -} -#pragma endregion - -#pragma region Test -void ShooterSubsystem::TestInit() noexcept {} - -void ShooterSubsystem::TestExit() noexcept {} - -void ShooterSubsystem::TestPeriodic() noexcept -{ - const std::chrono::time_point now = std::chrono::steady_clock::now(); - if (now >= m_verifyMotorControllersWhen) - { - using namespace std::chrono_literals; - - m_verifyMotorControllersWhen = now + 15s; - - m_LeftShooterMotor->CheckConfig(); - m_RightShooterMotor->CheckConfig(); - } + m_LeftShooterMotor.SetVoltage(percent * 12.00_V); + m_RightShooterMotor.SetVoltage(percent * 12.00_V); } -#pragma endregion diff --git a/src/main/cpp/subsystems/TransferArmSubsystem.cpp b/src/main/cpp/subsystems/TransferArmSubsystem.cpp index 974f4f5..b3137b1 100644 --- a/src/main/cpp/subsystems/TransferArmSubsystem.cpp +++ b/src/main/cpp/subsystems/TransferArmSubsystem.cpp @@ -1,15 +1,11 @@ #include "subsystems/TransferArmSubsystem.h" - -#include "Constants.h" - #include -#include #include TransferArmSubsystem::TransferArmSubsystem() noexcept { - m_motor.RestoreFactoryDefaults(); - + m_TransferArmMotor.SetInverted(arm::kTransferArmMotorIsInverted); + // set PID coefficients m_pidController.SetP(kP); m_pidController.SetI(kI); @@ -19,21 +15,16 @@ TransferArmSubsystem::TransferArmSubsystem() noexcept m_pidController.SetOutputRange(kMinOutput, kMaxOutput); // display PID coefficients on SmartDashboard - frc::SmartDashboard::PutNumber("P Gain", kP); - frc::SmartDashboard::PutNumber("I Gain", kI); - frc::SmartDashboard::PutNumber("D Gain", kD); - frc::SmartDashboard::PutNumber("I Zone", kIz); - frc::SmartDashboard::PutNumber("Feed Forward", kFF); - frc::SmartDashboard::PutNumber("Max Output", kMaxOutput); - frc::SmartDashboard::PutNumber("Min Output", kMinOutput); - frc::SmartDashboard::PutNumber("Set Rotations", 0); + frc::SmartDashboard::PutNumber("Arm P Gain ", kP); + frc::SmartDashboard::PutNumber("Arm D Gain ", kD); + frc::SmartDashboard::PutNumber("Arm F ", kFF); StopTransferArm(); } void TransferArmSubsystem::StopTransferArm() noexcept { - m_motor.StopMotor(); + m_TransferArmMotor.StopMotor(); } void TransferArmSubsystem::SetTransferArmPosition(const units::turn_t position) noexcept @@ -50,23 +41,12 @@ void TransferArmSubsystem::SetTransferArmPosition(const units::turn_t position) void TransferArmSubsystem::UpdatePIDValues() noexcept { - double p = frc::SmartDashboard::GetNumber("P Gain", 0); - double i = frc::SmartDashboard::GetNumber("I Gain", 0); - double d = frc::SmartDashboard::GetNumber("D Gain", 0); - double iz = frc::SmartDashboard::GetNumber("I Zone", 0); - double ff = frc::SmartDashboard::GetNumber("Feed Forward", 0); - double max = frc::SmartDashboard::GetNumber("Max Output", 0); - double min = frc::SmartDashboard::GetNumber("Min Output", 0); - double rotations = frc::SmartDashboard::GetNumber("Set Rotations", 0); + double p = frc::SmartDashboard::GetNumber("Arm P Gain ", 0); + double d = frc::SmartDashboard::GetNumber("Arm D Gain ", 0); + double ff = frc::SmartDashboard::GetNumber("Arm F ", 0); // if PID coefficients on SmartDashboard have changed, write new values to controller if((p != kP)) { m_pidController.SetP(p); kP = p; } - if((i != kI)) { m_pidController.SetI(i); kI = i; } if((d != kD)) { m_pidController.SetD(d); kD = d; } - if((iz != kIz)) { m_pidController.SetIZone(iz); kIz = iz; } if((ff != kFF)) { m_pidController.SetFF(ff); kFF = ff; } - if((max != kMaxOutput) || (min != kMinOutput)) { - m_pidController.SetOutputRange(min, max); - kMinOutput = min; kMaxOutput = max; - } } diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 48a662f..01d8b29 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -129,23 +129,20 @@ namespace pidf namespace shooter { - // Left Motor Constructor Parameters - constexpr std::string_view kLeftShooterMotorName = "LeftShooterMotor"; + // Left Motor Parameters constexpr int kLeftShooterMotorCanID = 14; constexpr bool kLeftShooterMotorIsInverted = true; - // Right Motor Constructor Parameters - constexpr std::string_view kRightShooterMotorName = "RightShooterMotor"; + // Right Motor Parameters constexpr int kRightShooterMotorCanID = 15; constexpr bool kRightShooterMotorIsInverted = false; - constexpr int kShooterMotorSpeed = 0; + constexpr int kShooterMotorVoltagePercent = .45; } namespace intake { - // Intake Spin Motor Constructor Parameters - constexpr std::string_view kIntakeSpinMotorName = "IntakeSpinMotor"; + // Intake Motor Parameters constexpr int kIntakeSpinMotorCanID = 16; constexpr bool kIntakeSpinMotorIsInverted = true; @@ -156,10 +153,16 @@ namespace intake constexpr units::degree_t kIntakeArmLoad = 0.0_deg; constexpr double kIntakeSpinMotorVoltagePercent = .15; - - // Arm Motor Constructor Parameters - constexpr std::string_view kIntakeArmMotorName = "ArmMotor"; - constexpr int kIntakeArmMotorCanID = 17; - constexpr bool kIntakeArmMotorIsInverted = false; } +namespace arm +{ + // Arm Motor Parameters + constexpr int kTransferArmMotorCanID = 17; + constexpr bool kTransferArmMotorIsInverted = false; + + // Arm Controller + constexpr double kArmPositionP = 0.005; + constexpr double kArmPositionD = 0.0005; + constexpr double kArmPositionF = 0.0; +} diff --git a/src/main/include/subsystems/IntakeSubsystem.h b/src/main/include/subsystems/IntakeSubsystem.h index 027118b..e388763 100644 --- a/src/main/include/subsystems/IntakeSubsystem.h +++ b/src/main/include/subsystems/IntakeSubsystem.h @@ -1,13 +1,8 @@ #pragma once +#include "Constants.h" #include "rev/CANSparkMax.h" - -#include #include -#include "Constants.h" - -#include -#include class IntakeSubsystem : public frc2::SubsystemBase { @@ -21,5 +16,5 @@ class IntakeSubsystem : public frc2::SubsystemBase void SetSpinMotorVoltagePercent(const double percent) noexcept; private: - rev::CANSparkMax m_motor{intake::kIntakeSpinMotorCanID, rev::CANSparkMax::MotorType::kBrushed}; + rev::CANSparkMax m_IntakeMotor{intake::kIntakeSpinMotorCanID, rev::CANSparkMax::MotorType::kBrushed}; }; diff --git a/src/main/include/subsystems/ShooterSubsystem.h b/src/main/include/subsystems/ShooterSubsystem.h index 443068f..a43f68f 100644 --- a/src/main/include/subsystems/ShooterSubsystem.h +++ b/src/main/include/subsystems/ShooterSubsystem.h @@ -1,26 +1,8 @@ #pragma once #include "Constants.h" -#include "infrastructure/SparkMax.h" - - -#include -#include -#include -#include -#include +#include "rev/CANSparkMax.h" #include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include class ShooterSubsystem : public frc2::SubsystemBase { @@ -30,29 +12,10 @@ class ShooterSubsystem : public frc2::SubsystemBase ShooterSubsystem(const ShooterSubsystem &) = delete; ShooterSubsystem &operator=(const ShooterSubsystem &) = delete; - void Periodic() noexcept override; + void StopShooter() noexcept; void SetShooterMotorVoltagePercent(const double percent) noexcept; - void Stop() noexcept; private: - std::unique_ptr m_LeftShooterMotorBase; - std::unique_ptr m_RightShooterMotorBase; - std::unique_ptr> m_LeftShooterMotor; - std::unique_ptr> m_RightShooterMotor; - -#pragma region Utility -public: - bool GetStatus() const noexcept; - void BurnConfig() noexcept; - void ClearFaults() noexcept; -#pragma endregion - -#pragma region Test -public: - void TestInit() noexcept; - void TestExit() noexcept; - void TestPeriodic() noexcept; -private: - std::chrono::steady_clock::time_point m_verifyMotorControllersWhen; -#pragma endregion + rev::CANSparkMax m_LeftShooterMotor{shooter::kLeftShooterMotorCanID, rev::CANSparkMax::MotorType::kBrushed}; + rev::CANSparkMax m_RightShooterMotor{shooter::kRightShooterMotorCanID, rev::CANSparkMax::MotorType::kBrushed}; }; diff --git a/src/main/include/subsystems/TransferArmSubsystem.h b/src/main/include/subsystems/TransferArmSubsystem.h index 9ecfed1..61fd090 100644 --- a/src/main/include/subsystems/TransferArmSubsystem.h +++ b/src/main/include/subsystems/TransferArmSubsystem.h @@ -1,13 +1,9 @@ #pragma once +#include "Constants.h" #include "rev/CANSparkMax.h" - -#include #include -#include "Constants.h" - -#include -#include +#include class TransferArmSubsystem : public frc2::SubsystemBase { @@ -23,8 +19,8 @@ class TransferArmSubsystem : public frc2::SubsystemBase void UpdatePIDValues() noexcept; private: - rev::CANSparkMax m_motor{intake::kIntakeArmMotorCanID, rev::CANSparkMax::MotorType::kBrushless}; - rev::SparkPIDController m_pidController = m_motor.GetPIDController(); - //rev::CANEncoder m_encoder = m_motor.GetAlternateEncoder(rev::CANEncoder::AlternateEncoderType::kQuadrature, 8192); - double kP = 0.001, kI = 0, kD = 0, kIz = 0, kFF = 0, kMaxOutput = 1, kMinOutput = -1; + rev::CANSparkMax m_TransferArmMotor{arm::kTransferArmMotorCanID, rev::CANSparkMax::MotorType::kBrushless}; + // rev::CANEncoder m_encoder = m_TransferArmMotor.GetAlternateEncoder(rev::CANEncoder::AlternateEncoderType::kQuadrature, 8192); + rev::SparkPIDController m_pidController = m_TransferArmMotor.GetPIDController(); + double kP = arm::kArmPositionP, kI = 0, kD = arm::kArmPositionD, kIz = 0, kFF = arm::kArmPositionF, kMaxOutput = 1, kMinOutput = -1; }; From b3adf396a06afbe6b27fa828783388f47f8b6f39 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 18 Feb 2024 22:05:10 -0500 Subject: [PATCH 089/126] command refactor --- src/main/cpp/RobotContainer.cpp | 13 - src/main/cpp/commands/AutonomousCommands.cpp | 1 - src/main/cpp/commands/DriveCommands.cpp | 2 - .../commands/PositionTransferArmCommand.cpp | 3 - src/main/cpp/commands/TestModeCommands.cpp | 311 ------------------ src/main/include/RobotContainer.h | 1 - .../include/commands/AutonomousCommands.h | 4 - src/main/include/commands/DriveCommands.h | 5 +- .../commands/PositionTransferArmCommand.h | 4 - src/main/include/commands/TestModeCommands.h | 258 --------------- 10 files changed, 1 insertion(+), 601 deletions(-) delete mode 100644 src/main/cpp/commands/TestModeCommands.cpp delete mode 100644 src/main/include/commands/TestModeCommands.h diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index afd8706..165f1eb 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -305,19 +305,6 @@ void RobotContainer::TestInit() noexcept frc::SendableChooser> *chooser{m_driveSubsystem.TestModeChooser()}; - chooser->SetDefaultOption("Zero", std::bind(ZeroCommand::ZeroCommandFactory, &m_driveSubsystem)); - chooser->AddOption("Turning Max", std::bind(MaxVAndATurningCommand::MaxVAndATurningCommandFactory, &m_driveSubsystem)); - chooser->AddOption("Drive Max", std::bind(MaxVAndADriveCommand::MaxVAndADriveCommandFactory, &m_driveSubsystem)); - chooser->AddOption("Xs and Os", std::bind(XsAndOsCommand::XsAndOsCommandFactory, &m_driveSubsystem)); - chooser->AddOption("RotateModules", std::bind(RotateModulesCommand::RotateModulesCommandFactory, &m_driveSubsystem)); - chooser->AddOption("Point", std::bind(PointCommandFactory, this)); - chooser->AddOption("Square", std::bind(SquareCommand::SquareCommandFactory, &m_driveSubsystem)); - chooser->AddOption("Spirograph", std::bind(SpirographCommand::SpirographCommandFactory, &m_driveSubsystem)); - chooser->AddOption("Orbit", std::bind(OrbitCommand::OrbitCommandFactory, &m_driveSubsystem)); - chooser->AddOption("Pirouette", std::bind(PirouetteCommand::PirouetteCommandFactory, &m_driveSubsystem)); - chooser->AddOption("Drive", std::bind(DriveCommandFactory, this)); - chooser->AddOption("Spin", std::bind(SpinCommand::SpinCommandFactory, &m_driveSubsystem)); - frc2::CommandScheduler::GetInstance().Enable(); } diff --git a/src/main/cpp/commands/AutonomousCommands.cpp b/src/main/cpp/commands/AutonomousCommands.cpp index dda7726..4e2a15f 100644 --- a/src/main/cpp/commands/AutonomousCommands.cpp +++ b/src/main/cpp/commands/AutonomousCommands.cpp @@ -3,7 +3,6 @@ // the WPILib BSD license file in the root directory of this project. #include "commands/AutonomousCommands.h" - #include #include #include diff --git a/src/main/cpp/commands/DriveCommands.cpp b/src/main/cpp/commands/DriveCommands.cpp index b8e2c44..5b8753d 100644 --- a/src/main/cpp/commands/DriveCommands.cpp +++ b/src/main/cpp/commands/DriveCommands.cpp @@ -4,8 +4,6 @@ #include "commands/DriveCommands.h" -#include - void ZeroTurningModules::Execute() noexcept { (void)driveSubsystem->ZeroModules(); } void DriveCommand::Initialize() noexcept diff --git a/src/main/cpp/commands/PositionTransferArmCommand.cpp b/src/main/cpp/commands/PositionTransferArmCommand.cpp index fa368a1..06d4cbd 100644 --- a/src/main/cpp/commands/PositionTransferArmCommand.cpp +++ b/src/main/cpp/commands/PositionTransferArmCommand.cpp @@ -3,9 +3,6 @@ // the WPILib BSD license file in the root directory of this project. #include "commands/PositionTransferArmCommand.h" -#include - -#include void PositionTransferArm::Initialize() noexcept { diff --git a/src/main/cpp/commands/TestModeCommands.cpp b/src/main/cpp/commands/TestModeCommands.cpp deleted file mode 100644 index 69ffd5c..0000000 --- a/src/main/cpp/commands/TestModeCommands.cpp +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#include "commands/TestModeCommands.h" - -#include - -void ZeroCommand::Execute() noexcept { (void)m_subsystem->ZeroModules(); } - -void MaxVAndATurningCommand::Initialize() noexcept -{ - m_iteration = 0; - - m_subsystem->TestModeTurningVoltage(0.0); - m_subsystem->TestModeDriveVoltage(0.0); -} - -void MaxVAndATurningCommand::End(bool interrupted) noexcept -{ - m_subsystem->TestModeTurningVoltage(0.0); - m_subsystem->TestModeDriveVoltage(0.0); -} - -void MaxVAndATurningCommand::Execute() noexcept -{ - // This is expected to run at ~20Hz. So 100 iterations is ~5s. - if (m_iteration < 50) - { - m_subsystem->TestModeTurningVoltage(-12.0); - } - else - { - m_subsystem->TestModeTurningVoltage(+12.0); - } - - if (++m_iteration >= 100) - { - m_iteration = 0; - } -} - -void MaxVAndADriveCommand::Initialize() noexcept -{ - m_iteration = 0; - - m_subsystem->TestModeTurningVoltage(0.0); - m_subsystem->TestModeDriveVoltage(0.0); -} - -void MaxVAndADriveCommand::End(bool interrupted) noexcept -{ - m_subsystem->TestModeTurningVoltage(0.0); - m_subsystem->TestModeDriveVoltage(0.0); -} - -void MaxVAndADriveCommand::Execute() noexcept -{ - // This is expected to run at ~20Hz. So 100 iterations is ~5s. - if (m_iteration < 50) - { - m_subsystem->TestModeDriveVoltage(-12.0); - } - else - { - m_subsystem->TestModeDriveVoltage(+12.0); - } - - if (++m_iteration >= 100) - { - m_iteration = 0; - } -} - -void XsAndOsCommand::Initialize() noexcept -{ - m_iteration = 0; - - m_subsystem->ResetDrive(); -} - -void XsAndOsCommand::End(bool interrupted) noexcept -{ - m_subsystem->ResetDrive(); -} - -void XsAndOsCommand::Execute() noexcept -{ - // This is expected to run at ~20Hz. So 100 iterations is ~5s. - if (m_iteration < 100) - { - (void)m_subsystem->SetLockWheelsX(); - } - else - { - (void)m_subsystem->SetTurnInPlace(); - } - - if (++m_iteration >= 200) - { - m_iteration = 0; - } -} - -void RotateModulesCommand::Initialize() noexcept -{ - m_iteration = 0; - - m_subsystem->ResetDrive(); -} - -void RotateModulesCommand::End(bool interrupted) noexcept -{ - m_subsystem->ResetDrive(); -} - -void RotateModulesCommand::Execute() noexcept -{ - // This is expected to run at ~20Hz. So 100 iterations is ~5s. - if (!m_subsystem->SetTurningPosition(1_deg * m_iteration)) - { - return; - } - - if (++m_iteration >= 360) - { - m_iteration = 0; - } -} - -void SquareCommand::Initialize() noexcept -{ - m_side = 0; - - m_subsystem->ResetDrive(); -} - -void SquareCommand::End(bool interrupted) noexcept -{ - m_subsystem->ResetDrive(); -} - -void SquareCommand::Execute() noexcept -{ - // First, command turning to the appropriate angle and go no further, until - // the wheels are oriented appropriately. - if (!m_subsystem->SetTurningPosition(90_deg * m_side)) - { - return; - } - - // Now, command driving a fixed distance and go no further, until it is - // reached. - if (!m_subsystem->SetDriveDistance(0.5_m)) - { - return; - } - - // Finished driving a side; now, set things up to start the next side, on - // the next iteration. After the fourth side, also reset the side counter. - m_subsystem->ResetDrive(); - - if (++m_side >= 4) - { - m_side = 0; - } -} - -void SpirographCommand::Initialize() noexcept -{ - m_angle = 0; - - m_subsystem->ResetDrive(); -} - -void SpirographCommand::End(bool interrupted) noexcept -{ - m_subsystem->ResetDrive(); -} - -void SpirographCommand::Execute() noexcept -{ - // First, command turning to the appropriate angle and go no further, until - // the wheels are oriented appropriately. - if (!m_subsystem->SetTurningPosition(1_deg * m_angle)) - { - return; - } - - // Now, command driving a fixed distance and go no further, until it is - // reached. - if (!m_subsystem->SetDriveDistance(0.5_m)) - { - return; - } - - // Finished driving a side; now, set things up to start the next side, on - // the next iteration. After the fourth side, also reset the side counter. - m_subsystem->ResetDrive(); - - m_angle += 75; - if (m_angle >= 360) - { - m_angle -= 360; - } -} - -void OrbitCommand::Execute() noexcept -{ - m_subsystem->Drive(0_mps, 0_mps, 10_deg_per_s, false, 1_m, 0_m); -} - -void PirouetteCommand::Initialize() noexcept -{ - // Arrange the origin and orientation of the coordinate system, so that the - // robot is on the unit circle, at (1,0) and facing positive X. (The robot - // does not move here, the coordinate system itself moves.) - m_subsystem->ResetOdometry(frc::Pose2d(1_m, 0_m, frc::Rotation2d(0_deg))); -} - -void PirouetteCommand::End(bool interrupted) noexcept -{ -} - -void PirouetteCommand::Execute() noexcept -{ - // Determine the direction the robot is facing, and where it is positioned. - // As it happens, the direction the robot is facing is not used here. - const frc::Pose2d pose = m_subsystem->GetPose(); - - // Now, work out the angle of where the robot is positioned on the unit - // circle, which is independent of where it is facing. It may drift either - // inside or outside of the unit circle. - const double X = pose.X().to(); - const double Y = pose.Y().to(); - frc::Rotation2d major_angle(X, Y); - - // The robot should translate tangentially to the unit circle, in a - // counter-clockwise fashion. So, add 90 degrees. - major_angle = major_angle + frc::Rotation2d(90_deg); - - // Work out the radius of the circle where robot is actually sitting, so it - // may be used to make an adjustment to try to get back onto the unit - // circle. Positive error means robot is outside target circle, negative - // means inside. Outside correction is to increase the computed angle; - // inside is to decrease. - double major_error = std::hypot(X, Y) - 1.0; - - // Limit the maximum correction to apply and then apply it. - if (major_error > 0.5) - { - major_error = 0.5; - } - else if (major_error < -0.5) - { - major_error = -0.5; - } - major_angle = major_angle + frc::Rotation2d(major_error * 20_deg); - - // Update X and Y drive command inputs based on above computations, while - // always commanding rotation. The commanded drive needs to be adjusted to - // account for the actual orientation of the robot, thus field oriented - // applies. - m_subsystem->Drive(major_angle.Sin() * 1_mps, major_angle.Cos() * 1_mps, 10_deg_per_s, true); -} - -void SpinCommand::Initialize() noexcept -{ - m_angle = 0; - m_delay = 0; - - m_subsystem->ResetDrive(); -} - -void SpinCommand::Execute() noexcept -{ - if (!m_subsystem->SetTurnInPlace()) - { - return; - } - - if (!m_subsystem->SetTurnToAngle(1_deg * m_angle)) - { - return; - } - - // Reached desired angle; delay for 2.5s. - if (++m_delay < 25) - { - return; - } - m_delay = 0; - - if (m_angle == 0) - { - m_angle = 90; - } - else if (m_angle == 90) - { - m_angle = 270; - } - else if (m_angle == 270) - { - m_angle = 0; - } -} - -void SpinCommand::End(bool interrupted) noexcept -{ - m_subsystem->ResetDrive(); -} diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index d45b6c3..cf59b18 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -9,7 +9,6 @@ #include #include "commands/AutonomousCommands.h" -#include "commands/TestModeCommands.h" #include "subsystems/DriveSubsystem.h" #include "subsystems/IntakeSubsystem.h" #include "subsystems/TransferArmSubsystem.h" diff --git a/src/main/include/commands/AutonomousCommands.h b/src/main/include/commands/AutonomousCommands.h index 3939f31..05987d2 100644 --- a/src/main/include/commands/AutonomousCommands.h +++ b/src/main/include/commands/AutonomousCommands.h @@ -9,15 +9,11 @@ #include #include #include - #include "subsystems/DriveSubsystem.h" #include "subsystems/IntakeSubsystem.h" #include "subsystems/Infrastructure.h" #include "subsystems/ShooterSubsystem.h" -#include -#include - class TimedAutoBase : public frc2::CommandHelper { protected: diff --git a/src/main/include/commands/DriveCommands.h b/src/main/include/commands/DriveCommands.h index e00c61e..fdcb3ff 100644 --- a/src/main/include/commands/DriveCommands.h +++ b/src/main/include/commands/DriveCommands.h @@ -7,11 +7,8 @@ #include #include #include - #include "subsystems/DriveSubsystem.h" -#include - // Home all swerve modules to zero heading. class ZeroTurningModules : public frc2::CommandHelper @@ -35,7 +32,7 @@ class ZeroTurningModules bool finished{false}; }; -// Expose turning maximum velocity and acceleration. +// TODO: decide what inputs this will accept class DriveCommand : public frc2::CommandHelper { diff --git a/src/main/include/commands/PositionTransferArmCommand.h b/src/main/include/commands/PositionTransferArmCommand.h index aa3f638..411d2f0 100644 --- a/src/main/include/commands/PositionTransferArmCommand.h +++ b/src/main/include/commands/PositionTransferArmCommand.h @@ -9,12 +9,8 @@ #include #include #include - #include "subsystems/TransferArmSubsystem.h" -#include - -// Expose turning maximum velocity and acceleration. class PositionTransferArm : public frc2::CommandHelper { diff --git a/src/main/include/commands/TestModeCommands.h b/src/main/include/commands/TestModeCommands.h deleted file mode 100644 index 4029f7c..0000000 --- a/src/main/include/commands/TestModeCommands.h +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#pragma once - -#include -#include -#include - -#include "subsystems/DriveSubsystem.h" - -#include - -// Home all swerve modules to zero heading. -class ZeroCommand - : public frc2::CommandHelper -{ -public: - explicit ZeroCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("Zero"); } - - void Initialize() noexcept override {} - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override {} - - static frc2::CommandPtr ZeroCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; -}; - -// Expose turning maximum velocity and acceleration. -class MaxVAndATurningCommand - : public frc2::CommandHelper -{ -public: - explicit MaxVAndATurningCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("MaxVAndATurning"); } - - void Initialize() noexcept override; - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override; - - static frc2::CommandPtr MaxVAndATurningCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; - - unsigned m_iteration{0}; -}; - -// Expose drive maximum velocity and acceleration. -class MaxVAndADriveCommand - : public frc2::CommandHelper -{ -public: - explicit MaxVAndADriveCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("MaxVAndADrive"); } - - void Initialize() noexcept override; - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override; - - static frc2::CommandPtr MaxVAndADriveCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; - - unsigned m_iteration{0}; -}; - -// Alternately command swerve modules to form X and O patterns. -class XsAndOsCommand - : public frc2::CommandHelper -{ -public: - explicit XsAndOsCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("Xs and Os"); } - - void Initialize() noexcept override; - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override; - - static frc2::CommandPtr XsAndOsCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; - - unsigned m_iteration{0}; -}; - -// Command swerve modules to slowly rotate together (spin). -class RotateModulesCommand - : public frc2::CommandHelper -{ -public: - explicit RotateModulesCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("Rotate Modules"); } - - void Initialize() noexcept override; - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override; - - static frc2::CommandPtr RotateModulesCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; - - unsigned m_iteration{0}; -}; - -// Drive in a square. -class SquareCommand - : public frc2::CommandHelper -{ -public: - explicit SquareCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("Square"); } - - void Initialize() noexcept override; - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override; - - static frc2::CommandPtr SquareCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; - - unsigned m_side{0}; -}; - -// Drive in a spirograph pattern. -class SpirographCommand - : public frc2::CommandHelper -{ -public: - explicit SpirographCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("Spirograph"); } - - void Initialize() noexcept override; - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override; - - static frc2::CommandPtr SpirographCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; - - unsigned m_angle{0}; -}; - -// Drive in an orbit. -class OrbitCommand - : public frc2::CommandHelper -{ -public: - explicit OrbitCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("Orbit"); } - - void Initialize() noexcept override {} - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override {} - - static frc2::CommandPtr OrbitCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; -}; - -// Drive in a fancy pirouette. -class PirouetteCommand - : public frc2::CommandHelper -{ -public: - explicit PirouetteCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("Pirouette"); } - - void Initialize() noexcept override; - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override; - - static frc2::CommandPtr PirouetteCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; -}; - -// Excersize theta (spin) controller. -class SpinCommand - : public frc2::CommandHelper -{ -public: - explicit SpinCommand(DriveSubsystem *subsystem) noexcept - : m_subsystem{subsystem} { SetName("Spin"); } - - void Initialize() noexcept override; - - void Execute() noexcept override; - - void End(bool interrupted) noexcept override; - - static frc2::CommandPtr SpinCommandFactory(DriveSubsystem *subsystem) noexcept - { - return frc2::CommandPtr{std::make_unique(subsystem)}; - } - -private: - DriveSubsystem *m_subsystem{nullptr}; - - unsigned m_angle{0}; - unsigned m_delay{0}; -}; From 59dfbcc86636d068d380654580322362f2d86eac Mon Sep 17 00:00:00 2001 From: Heatround <105463535+Heatround@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:38:32 -0500 Subject: [PATCH 090/126] Updates shooter command need to figure out button bindings --- .vscode/settings.json | 3 +- src/main/cpp/RobotContainer.cpp | 7 ++++ src/main/cpp/commands/ShootCommands.cpp | 30 +++++++++++++++++ src/main/include/RobotContainer.h | 4 +++ src/main/include/commands/ShootCommands.h | 40 +++++++++++++++++++++++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/main/cpp/commands/ShootCommands.cpp create mode 100644 src/main/include/commands/ShootCommands.h diff --git a/.vscode/settings.json b/.vscode/settings.json index e9d8a36..ad8bce4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -58,5 +58,6 @@ "C_Cpp_Runner.msvcSecureNoWarnings": false, "files.associations": { "*.inc": "cpp" - } + }, + "C_Cpp.errorSquiggles": "disabled" } \ No newline at end of file diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 165f1eb..b165910 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -16,6 +16,7 @@ #include #include #include +#include "commands/ShootCommands.h" #include "commands/PositionTransferArmCommand.h" #include @@ -289,6 +290,12 @@ void RobotContainer::ConfigureBindings() noexcept { m_shooterSubsystem.StopShooter(); }, {&m_shooterSubsystem}) .ToPtr()); + m_xbox.X().OnTrue(frc2::InstantCommand.&m_shooterSubsystem} + {ShootCommands[&]} + .ToPtr ()); + + + } #pragma endregion diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp new file mode 100644 index 0000000..ff66c70 --- /dev/null +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -0,0 +1,30 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/ShootCommands.h" + +// Called when the command is initially scheduled. +void ShootCommands::Initialize() { + + //Start the shooter motors and timer + shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); + timer.Reset(); +} + +// Called repeatedly when this Command is scheduled to run +void ShootCommands::Execute() { + //When the timer has run for 5 seconds set finished to true to stop the motors + if (timer.HasElapsed(5_s)) {finished = true;} +} + +// Called once the command ends or is interrupted. +void ShootCommands::End(bool interrupted) { + //Stop the shooter motors + shooterSubsystem->StopShooter(); +} + +// Returns true when the command should end. +bool ShootCommands::IsFinished() { + return false; +} diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index cf59b18..e08101e 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -59,6 +59,8 @@ class RobotContainer bool m_lock{false}; bool triggerSpeedEnabled{false}; + + frc2::CommandXboxController m_xbox{0}; // frc2::CommandXboxController m_xbox{1}; TODO: update for second controller #pragma endregion @@ -87,6 +89,8 @@ class RobotContainer IntakeSubsystem m_intakeSubsystem; TransferArmSubsystem m_transferArmSubsystem; ShooterSubsystem m_shooterSubsystem; + + frc2::CommandPtr ShootCommands(); // declared for the infrastructure subsystem uint m_LEDPattern{29}; diff --git a/src/main/include/commands/ShootCommands.h b/src/main/include/commands/ShootCommands.h new file mode 100644 index 0000000..8c262c1 --- /dev/null +++ b/src/main/include/commands/ShootCommands.h @@ -0,0 +1,40 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include "subsystems/ShooterSubsystem.h" +#include "subsystems/TransferArmSubsystem.h" + +/** + * An example command. + * + *

Note that this extends CommandHelper, rather extending Command + * directly; this is crucially important, or else the decorator functions in + * Command will *not* work! + */ +class ShootCommands + : public frc2::CommandHelper +{ + public: + explicit ShootCommands(ShooterSubsystem *shooterSubsystem) + { + AddRequirements(shooterSubsystem); + } + + ShootCommands(); + + void Initialize() override; + void Execute() override; + void End(bool interrupted) override; + bool IsFinished() override; + + private: + ShooterSubsystem *shooterSubsystem{nullptr}; + frc::Timer timer{}; + bool finished{false}; +}; From 266bdb49f0a86d4258847c06d3ebd9248210b956 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Mon, 19 Feb 2024 12:05:07 -0500 Subject: [PATCH 091/126] added new binding --- src/main/cpp/RobotContainer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index b165910..7043ce7 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -290,10 +290,10 @@ void RobotContainer::ConfigureBindings() noexcept { m_shooterSubsystem.StopShooter(); }, {&m_shooterSubsystem}) .ToPtr()); - m_xbox.X().OnTrue(frc2::InstantCommand.&m_shooterSubsystem} + /* m_xbox.X().OnTrue(PositionTransferArm() {ShootCommands[&]} - .ToPtr ()); - + .ToPtr ());*/ + m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); } From b472ba7ed3a8c2633c19301622676267eeb6cd95 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:11:54 -0500 Subject: [PATCH 092/126] Update ShootCommands.h --- src/main/include/commands/ShootCommands.h | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/include/commands/ShootCommands.h b/src/main/include/commands/ShootCommands.h index 8c262c1..81f25b7 100644 --- a/src/main/include/commands/ShootCommands.h +++ b/src/main/include/commands/ShootCommands.h @@ -18,23 +18,22 @@ * Command will *not* work! */ class ShootCommands - : public frc2::CommandHelper + : public frc2::CommandHelper { - public: - explicit ShootCommands(ShooterSubsystem *shooterSubsystem) +public: + explicit ShootCommands(ShooterSubsystem *shooterSubsystem) + : shooterSubsystem{shooterSubsystem}, { AddRequirements(shooterSubsystem); } - ShootCommands(); - void Initialize() override; void Execute() override; void End(bool interrupted) override; bool IsFinished() override; - private: - ShooterSubsystem *shooterSubsystem{nullptr}; - frc::Timer timer{}; - bool finished{false}; +private: + ShooterSubsystem *shooterSubsystem{nullptr}; + frc::Timer timer{}; + bool finished{false}; }; From a10363d1b1f2ddae5e97d170e99a1ea3b7e1c127 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:27:01 -0500 Subject: [PATCH 093/126] shooter command fixes --- .vscode/settings.json | 3 ++- src/main/cpp/RobotContainer.cpp | 6 +----- src/main/include/RobotContainer.h | 2 -- src/main/include/commands/ShootCommands.h | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ad8bce4..f0ca392 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -57,7 +57,8 @@ "C_Cpp_Runner.useLinkTimeOptimization": false, "C_Cpp_Runner.msvcSecureNoWarnings": false, "files.associations": { - "*.inc": "cpp" + "*.inc": "cpp", + "memory": "cpp" }, "C_Cpp.errorSquiggles": "disabled" } \ No newline at end of file diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 7043ce7..8f05144 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -290,12 +290,8 @@ void RobotContainer::ConfigureBindings() noexcept { m_shooterSubsystem.StopShooter(); }, {&m_shooterSubsystem}) .ToPtr()); - /* m_xbox.X().OnTrue(PositionTransferArm() - {ShootCommands[&]} - .ToPtr ());*/ - m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); - + m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); } #pragma endregion diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index e08101e..a74a97f 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -90,8 +90,6 @@ class RobotContainer TransferArmSubsystem m_transferArmSubsystem; ShooterSubsystem m_shooterSubsystem; - frc2::CommandPtr ShootCommands(); - // declared for the infrastructure subsystem uint m_LEDPattern{29}; uint m_LEDPatternCount{0}; diff --git a/src/main/include/commands/ShootCommands.h b/src/main/include/commands/ShootCommands.h index 81f25b7..1f67ce9 100644 --- a/src/main/include/commands/ShootCommands.h +++ b/src/main/include/commands/ShootCommands.h @@ -22,7 +22,7 @@ class ShootCommands { public: explicit ShootCommands(ShooterSubsystem *shooterSubsystem) - : shooterSubsystem{shooterSubsystem}, + : shooterSubsystem{shooterSubsystem} { AddRequirements(shooterSubsystem); } From 61b3d8f292ca36f82e1fd4db4affac5c07183692 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:27:13 -0500 Subject: [PATCH 094/126] add includes --- src/main/include/subsystems/IntakeSubsystem.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/include/subsystems/IntakeSubsystem.h b/src/main/include/subsystems/IntakeSubsystem.h index e388763..550779b 100644 --- a/src/main/include/subsystems/IntakeSubsystem.h +++ b/src/main/include/subsystems/IntakeSubsystem.h @@ -3,6 +3,8 @@ #include "Constants.h" #include "rev/CANSparkMax.h" #include +#include +#include class IntakeSubsystem : public frc2::SubsystemBase { From 8759bc8e3ebc3d28c50da6d38315d5f5cf74287c Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 19 Feb 2024 13:04:53 -0500 Subject: [PATCH 095/126] Revert "add includes" This reverts commit 61b3d8f292ca36f82e1fd4db4affac5c07183692. --- src/main/include/subsystems/IntakeSubsystem.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/include/subsystems/IntakeSubsystem.h b/src/main/include/subsystems/IntakeSubsystem.h index 550779b..e388763 100644 --- a/src/main/include/subsystems/IntakeSubsystem.h +++ b/src/main/include/subsystems/IntakeSubsystem.h @@ -3,8 +3,6 @@ #include "Constants.h" #include "rev/CANSparkMax.h" #include -#include -#include class IntakeSubsystem : public frc2::SubsystemBase { From 921db28ab09d52f9a127d4221714b92165af5245 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Mon, 19 Feb 2024 13:29:10 -0500 Subject: [PATCH 096/126] Shooter Command Working Need to adjust motor speed when mechanical binding issue fixed. Add Arm movement to shooting command --- src/main/cpp/commands/ShootCommands.cpp | 8 ++++++-- src/main/include/Constants.h | 4 ++-- src/main/include/subsystems/ShooterSubsystem.h | 2 ++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index ff66c70..2360b0e 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -10,12 +10,16 @@ void ShootCommands::Initialize() { //Start the shooter motors and timer shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); timer.Reset(); + timer.Start(); } // Called repeatedly when this Command is scheduled to run void ShootCommands::Execute() { //When the timer has run for 5 seconds set finished to true to stop the motors - if (timer.HasElapsed(5_s)) {finished = true;} + if (timer.HasElapsed(5_s)) { + finished = true; + shooterSubsystem->StopShooter(); + } } // Called once the command ends or is interrupted. @@ -26,5 +30,5 @@ void ShootCommands::End(bool interrupted) { // Returns true when the command should end. bool ShootCommands::IsFinished() { - return false; + return finished; } diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 01d8b29..4f44ce9 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -135,9 +135,9 @@ namespace shooter // Right Motor Parameters constexpr int kRightShooterMotorCanID = 15; - constexpr bool kRightShooterMotorIsInverted = false; + constexpr bool kRightShooterMotorIsInverted = true; - constexpr int kShooterMotorVoltagePercent = .45; + constexpr int kShooterMotorVoltagePercent = 1; } namespace intake diff --git a/src/main/include/subsystems/ShooterSubsystem.h b/src/main/include/subsystems/ShooterSubsystem.h index a43f68f..3b45e8a 100644 --- a/src/main/include/subsystems/ShooterSubsystem.h +++ b/src/main/include/subsystems/ShooterSubsystem.h @@ -3,6 +3,8 @@ #include "Constants.h" #include "rev/CANSparkMax.h" #include +//#include +//#include class ShooterSubsystem : public frc2::SubsystemBase { From 4e25c206085f812cf25c2f4cbab46097d2e25cec Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 19 Feb 2024 13:36:45 -0500 Subject: [PATCH 097/126] Update ShooterSubsystem.h --- src/main/include/subsystems/ShooterSubsystem.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/include/subsystems/ShooterSubsystem.h b/src/main/include/subsystems/ShooterSubsystem.h index 3b45e8a..a43f68f 100644 --- a/src/main/include/subsystems/ShooterSubsystem.h +++ b/src/main/include/subsystems/ShooterSubsystem.h @@ -3,8 +3,6 @@ #include "Constants.h" #include "rev/CANSparkMax.h" #include -//#include -//#include class ShooterSubsystem : public frc2::SubsystemBase { From aebbc3ac69dc4d3b46f8bc21f68ff1c1be1d446b Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 19 Feb 2024 13:38:41 -0500 Subject: [PATCH 098/126] Update RobotContainer.h --- src/main/include/RobotContainer.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index a74a97f..cf59b18 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -59,8 +59,6 @@ class RobotContainer bool m_lock{false}; bool triggerSpeedEnabled{false}; - - frc2::CommandXboxController m_xbox{0}; // frc2::CommandXboxController m_xbox{1}; TODO: update for second controller #pragma endregion @@ -89,7 +87,7 @@ class RobotContainer IntakeSubsystem m_intakeSubsystem; TransferArmSubsystem m_transferArmSubsystem; ShooterSubsystem m_shooterSubsystem; - + // declared for the infrastructure subsystem uint m_LEDPattern{29}; uint m_LEDPatternCount{0}; From be65f78f7bb5feb769df293c312bde7c9b4a2c94 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 19 Feb 2024 13:41:35 -0500 Subject: [PATCH 099/126] update keybindings --- src/main/cpp/RobotContainer.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 8f05144..46e6832 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -260,19 +260,6 @@ void RobotContainer::ConfigureBindings() noexcept {}) .ToPtr()); - // TODO: decide if we want this - // m_xbox.X().OnTrue(frc2::InstantCommand([&]() -> void - // { m_fieldOriented = false; }, - // {}) - // .ToPtr()); - // m_xbox.Y().OnTrue(frc2::InstantCommand([&]() -> void - // { m_driveSubsystem.ZeroHeading(); - // m_fieldOriented = true; }, - // {&m_driveSubsystem}) - // .ToPtr()); - - // TODO: decide if we want to bind wheel lock - m_xbox.A().OnTrue(frc2::InstantCommand([&]() -> void { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorVoltagePercent); }, {&m_intakeSubsystem}) @@ -292,6 +279,8 @@ void RobotContainer::ConfigureBindings() noexcept .ToPtr()); m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); + + m_xbox.X().OnTrue(PositionTransferArm(&m_transferArmSubsystem, 90_deg).ToPtr()); // Example Only } #pragma endregion From cf952f5868a361e5bbc26a17bd7baa88ac2055f7 Mon Sep 17 00:00:00 2001 From: Heatround <105463535+Heatround@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:36:56 -0500 Subject: [PATCH 100/126] Update Constants.h Added namespaces for climber and amp --- src/main/include/Constants.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 4f44ce9..1c94c5c 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -166,3 +166,27 @@ namespace arm constexpr double kArmPositionD = 0.0005; constexpr double kArmPositionF = 0.0; } + +namespace climber +{ + //Climber parameters + constexpr int kClimberMotorCanID = 18; + constexpr bool kClimberMotorIsInverted = false; + constexpr double kClimberMotorVoltagePercent = .15; + + constexpr int kClimberSolenoidCanID = 19; + +} + +namespace amp +{ + //Bosch motor for extending out and back + constexpr int kAmpExtendMotorCanID = 20; + constexpr bool kAmpExtendMotorIsInverted = false; + constexpr double kAmpExtendMotorVoltagePercent = .15; + + //Johnson Electric for raising and lowering + constexpr int kAmpRaiseMotorCanID = 21; + constexpr bool kAmpRaiseMotorIsInverted = false; + constexpr double kAmpRaiseMotorVoltagePercent = .15; +} From 742ee096ea82641cfa79ec82e24a88a218396b4c Mon Sep 17 00:00:00 2001 From: Heatround <105463535+Heatround@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:17:03 -0500 Subject: [PATCH 101/126] Climber Code initial build Add the initial build for the climber --- src/main/cpp/RobotContainer.cpp | 13 +++++++ src/main/cpp/commands/ClimberLowerCommand.cpp | 30 ++++++++++++++++ src/main/cpp/commands/ClimberRaiseCommand.cpp | 25 +++++++++++++ src/main/cpp/commands/ClimberStopCommand.cpp | 23 ++++++++++++ src/main/cpp/commands/ShootCommands.cpp | 1 + src/main/cpp/subsystems/ClimberSubsystem.cpp | 20 +++++++++++ src/main/include/Constants.h | 3 +- src/main/include/RobotContainer.h | 2 ++ .../include/commands/ClimberLowerCommand.h | 36 +++++++++++++++++++ .../include/commands/ClimberRaiseCommand.h | 36 +++++++++++++++++++ .../include/commands/ClimberStopCommand.h | 36 +++++++++++++++++++ .../include/subsystems/ClimberSubsystem.h | 28 +++++++++++++++ 12 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 src/main/cpp/commands/ClimberLowerCommand.cpp create mode 100644 src/main/cpp/commands/ClimberRaiseCommand.cpp create mode 100644 src/main/cpp/commands/ClimberStopCommand.cpp create mode 100644 src/main/cpp/subsystems/ClimberSubsystem.cpp create mode 100644 src/main/include/commands/ClimberLowerCommand.h create mode 100644 src/main/include/commands/ClimberRaiseCommand.h create mode 100644 src/main/include/commands/ClimberStopCommand.h create mode 100644 src/main/include/subsystems/ClimberSubsystem.h diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 46e6832..fcbbbce 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -16,6 +16,9 @@ #include #include #include +#include "commands/ClimberLowerCommand.h" +#include "commands/ClimberRaiseCommand.h" +#include "commands/ClimberStopCommand.h" #include "commands/ShootCommands.h" #include "commands/PositionTransferArmCommand.h" @@ -281,6 +284,16 @@ void RobotContainer::ConfigureBindings() noexcept m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); m_xbox.X().OnTrue(PositionTransferArm(&m_transferArmSubsystem, 90_deg).ToPtr()); // Example Only + + m_xbox.LeftBumper().WhileTrue(ClimberRaiseCommand(&m_climberSubsystem).ToPtr()); // Raise the climber while button is pressed. + m_xbox.LeftBumper().OnFalse(ClimberStopCommand(&m_climberSubsystem).ToPtr()); // on false stop the climber motor + + m_xbox.LeftTrigger().WhileTrue(ClimberLowerCommand(&m_climberSubsystem).ToPtr()); //Lower the climber while button is pressed + m_xbox.LeftTrigger().OnFalse(ClimberStopCommand(&m_climberSubsystem).ToPtr()); // on false stop the climber motor + + + + } #pragma endregion diff --git a/src/main/cpp/commands/ClimberLowerCommand.cpp b/src/main/cpp/commands/ClimberLowerCommand.cpp new file mode 100644 index 0000000..e4dd2a4 --- /dev/null +++ b/src/main/cpp/commands/ClimberLowerCommand.cpp @@ -0,0 +1,30 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/ClimberLowerCommand.h" + +/* +ClimberLowerCommand::ClimberLowerCommand() { + // Use addRequirements() here to declare subsystem dependencies. +} +*/ + +// Called when the command is initially scheduled. +void ClimberLowerCommand::Initialize() {} + +// Called repeatedly when this Command is scheduled to run +void ClimberLowerCommand::Execute() { + //Start the climber motor to move climbing hook up. + climberSubsystem->SetClimberMotorVoltagePercent(climber::kClimberMotorLowerVoltagePercent); +} + +// Called once the command ends or is interrupted. +void ClimberLowerCommand::End(bool interrupted) { + climberSubsystem->StopClimber(); +} + +// Returns true when the command should end. +bool ClimberLowerCommand::IsFinished() { + return false; +} diff --git a/src/main/cpp/commands/ClimberRaiseCommand.cpp b/src/main/cpp/commands/ClimberRaiseCommand.cpp new file mode 100644 index 0000000..371ecc3 --- /dev/null +++ b/src/main/cpp/commands/ClimberRaiseCommand.cpp @@ -0,0 +1,25 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/ClimberRaiseCommand.h" + + +// Called when the command is initially scheduled. +void ClimberRaiseCommand::Initialize() {} + +// Called repeatedly when this Command is scheduled to run +void ClimberRaiseCommand::Execute() { + //Start the climber motor to move climbing hook up. + climberSubsystem->SetClimberMotorVoltagePercent(climber::kClimberMotorRaiseVoltagePercent); +} + +// Called once the command ends or is interrupted. +void ClimberRaiseCommand::End(bool interrupted) { + climberSubsystem->StopClimber(); +} + +// Returns true when the command should end. +bool ClimberRaiseCommand::IsFinished() { + return false; +} diff --git a/src/main/cpp/commands/ClimberStopCommand.cpp b/src/main/cpp/commands/ClimberStopCommand.cpp new file mode 100644 index 0000000..8a4452a --- /dev/null +++ b/src/main/cpp/commands/ClimberStopCommand.cpp @@ -0,0 +1,23 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/ClimberStopCommand.h" + + +// Called when the command is initially scheduled. +void ClimberStopCommand::Initialize() {} + +// Called repeatedly when this Command is scheduled to run +void ClimberStopCommand::Execute() { + //Stop the motors + climberSubsystem->StopClimber(); +} + +// Called once the command ends or is interrupted. +void ClimberStopCommand::End(bool interrupted) {} + +// Returns true when the command should end. +bool ClimberStopCommand::IsFinished() { + return false; +} diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index 2360b0e..e9c316f 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -9,6 +9,7 @@ void ShootCommands::Initialize() { //Start the shooter motors and timer shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); + finished = false; timer.Reset(); timer.Start(); } diff --git a/src/main/cpp/subsystems/ClimberSubsystem.cpp b/src/main/cpp/subsystems/ClimberSubsystem.cpp new file mode 100644 index 0000000..ccabfc9 --- /dev/null +++ b/src/main/cpp/subsystems/ClimberSubsystem.cpp @@ -0,0 +1,20 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "subsystems/ClimberSubsystem.h" + +ClimberSubsystem::ClimberSubsystem() noexcept +{ + m_ClimberMotor.SetInverted(climber::kClimberMotorCanID); +} + +void ClimberSubsystem::StopClimber() noexcept +{ + m_ClimberMotor.StopMotor(); +} + +void ClimberSubsystem::SetClimberMotorVoltagePercent(const double percent) noexcept +{ + m_ClimberMotor.SetVoltage(percent * 12.00_V); +} diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 1c94c5c..1cf333f 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -172,7 +172,8 @@ namespace climber //Climber parameters constexpr int kClimberMotorCanID = 18; constexpr bool kClimberMotorIsInverted = false; - constexpr double kClimberMotorVoltagePercent = .15; + constexpr double kClimberMotorRaiseVoltagePercent = .15; + constexpr double kClimberMotorLowerVoltagePercent = -.15; constexpr int kClimberSolenoidCanID = 19; diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index cf59b18..c337dde 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -13,6 +13,7 @@ #include "subsystems/IntakeSubsystem.h" #include "subsystems/TransferArmSubsystem.h" #include "subsystems/Infrastructure.h" +#include "subsystems/ClimberSubsystem.h" #include "subsystems/ShooterSubsystem.h" #include @@ -87,6 +88,7 @@ class RobotContainer IntakeSubsystem m_intakeSubsystem; TransferArmSubsystem m_transferArmSubsystem; ShooterSubsystem m_shooterSubsystem; + ClimberSubsystem m_climberSubsystem; // declared for the infrastructure subsystem uint m_LEDPattern{29}; diff --git a/src/main/include/commands/ClimberLowerCommand.h b/src/main/include/commands/ClimberLowerCommand.h new file mode 100644 index 0000000..71de357 --- /dev/null +++ b/src/main/include/commands/ClimberLowerCommand.h @@ -0,0 +1,36 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include "subsystems/ClimberSubsystem.h" +#include "Constants.h" + +/** + * An example command. + * + *

Note that this extends CommandHelper, rather extending Command + * directly; this is crucially important, or else the decorator functions in + * Command will *not* work! + */ +class ClimberLowerCommand + : public frc2::CommandHelper { + public: + explicit ClimberLowerCommand(ClimberSubsystem *climberSubsystem) + : climberSubsystem{climberSubsystem} + { + AddRequirements(climberSubsystem); + } + + void Initialize() override; + void Execute() override; + void End(bool interrupted) override; + bool IsFinished() override; + + private: + ClimberSubsystem *climberSubsystem{nullptr}; + bool finished{false}; +}; diff --git a/src/main/include/commands/ClimberRaiseCommand.h b/src/main/include/commands/ClimberRaiseCommand.h new file mode 100644 index 0000000..3df7682 --- /dev/null +++ b/src/main/include/commands/ClimberRaiseCommand.h @@ -0,0 +1,36 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include "subsystems/ClimberSubsystem.h" +#include "Constants.h" + +/** + * An example command. + * + *

Note that this extends CommandHelper, rather extending Command + * directly; this is crucially important, or else the decorator functions in + * Command will *not* work! + */ +class ClimberRaiseCommand + : public frc2::CommandHelper { + public: + explicit ClimberRaiseCommand(ClimberSubsystem *climberSubsystem) + : climberSubsystem{climberSubsystem} + { + AddRequirements(climberSubsystem); + } + + void Initialize() override; + void Execute() override; + void End(bool interrupted) override; + bool IsFinished() override; + + private: + ClimberSubsystem *climberSubsystem{nullptr}; + bool finished{false}; +}; diff --git a/src/main/include/commands/ClimberStopCommand.h b/src/main/include/commands/ClimberStopCommand.h new file mode 100644 index 0000000..b94753a --- /dev/null +++ b/src/main/include/commands/ClimberStopCommand.h @@ -0,0 +1,36 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include "subsystems/ClimberSubsystem.h" +#include "Constants.h" + +/** + * An example command. + * + *

Note that this extends CommandHelper, rather extending Command + * directly; this is crucially important, or else the decorator functions in + * Command will *not* work! + */ +class ClimberStopCommand + : public frc2::CommandHelper { + public: + explicit ClimberStopCommand(ClimberSubsystem *climberSubsystem) + : climberSubsystem{climberSubsystem} + { + AddRequirements(climberSubsystem); + } + + void Initialize() override; + void Execute() override; + void End(bool interrupted) override; + bool IsFinished() override; + + private: + ClimberSubsystem *climberSubsystem{nullptr}; + bool finished{false}; +}; diff --git a/src/main/include/subsystems/ClimberSubsystem.h b/src/main/include/subsystems/ClimberSubsystem.h new file mode 100644 index 0000000..5c32037 --- /dev/null +++ b/src/main/include/subsystems/ClimberSubsystem.h @@ -0,0 +1,28 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include "Constants.h" +#include "rev/CANSparkMax.h" + + +class ClimberSubsystem : public frc2::SubsystemBase +{ + public: + ClimberSubsystem() noexcept; + + ClimberSubsystem(const ClimberSubsystem &) = delete; + ClimberSubsystem &operator=(const ClimberSubsystem &) = delete; + + void StopClimber() noexcept; + void SetClimberMotorVoltagePercent(const double percent) noexcept; + + private: + // Components (e.g. motor controllers and sensors) should generally be + // declared private and exposed only through public methods. + + rev::CANSparkMax m_ClimberMotor{climber::kClimberMotorCanID, rev::CANSparkMax::MotorType::kBrushed}; +}; From ce0f0876baff0f65cacddd806506d6f92e4f7116 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Tue, 20 Feb 2024 18:05:24 -0500 Subject: [PATCH 102/126] Corrected ShootCommand error Reset the finished to false at initialization so shooter can shoot multiple times. --- src/main/cpp/commands/ShootCommands.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index 2360b0e..73cfbed 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -9,6 +9,7 @@ void ShootCommands::Initialize() { //Start the shooter motors and timer shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); + IsFinished = false; timer.Reset(); timer.Start(); } From 20e6049768dd5ea5fe5d9664b5714d164d2bf2c6 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Tue, 20 Feb 2024 20:05:17 -0500 Subject: [PATCH 103/126] Created Intake Command Create intake command to start motors to grab a note. Need to add code to move the arm to the correct position. Reset controller to have A intake and B eject for testing. Added code for limit switches to stop the intake. --- src/main/cpp/RobotContainer.cpp | 18 +++++++++- src/main/cpp/commands/IntakeCommand.cpp | 34 +++++++++++++++++++ src/main/cpp/commands/ShootCommands.cpp | 2 +- src/main/include/Constants.h | 1 + src/main/include/commands/IntakeCommand.h | 41 +++++++++++++++++++++++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/main/cpp/commands/IntakeCommand.cpp create mode 100644 src/main/include/commands/IntakeCommand.h diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 46e6832..75f138a 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -16,6 +16,7 @@ #include #include #include +#include "commands/IntakeCommand.h" #include "commands/ShootCommands.h" #include "commands/PositionTransferArmCommand.h" @@ -260,6 +261,7 @@ void RobotContainer::ConfigureBindings() noexcept {}) .ToPtr()); + /* Commented out, changing A to run the IntakeCommand for picking up a note m_xbox.A().OnTrue(frc2::InstantCommand([&]() -> void { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorVoltagePercent); }, {&m_intakeSubsystem}) @@ -267,8 +269,11 @@ void RobotContainer::ConfigureBindings() noexcept m_xbox.A().OnFalse(frc2::InstantCommand([&]() -> void { m_intakeSubsystem.StopIntake(); }, {&m_intakeSubsystem}) - .ToPtr()); + .ToPtr()); */ + m_xbox.A().OnTrue(IntakeCommand(&m_intakeSubsystem).ToPtr()); + + /* Commented out, shooter functions are in Y now. Reusing B for the intake eject command m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void { m_shooterSubsystem.SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); }, {&m_shooterSubsystem}) @@ -277,7 +282,18 @@ void RobotContainer::ConfigureBindings() noexcept { m_shooterSubsystem.StopShooter(); }, {&m_shooterSubsystem}) .ToPtr()); + */ + + m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void + { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorEjectVoltagePercent); }, + {&m_intakeSubsystem}) + .ToPtr()); + m_xbox.B().OnFalse(frc2::InstantCommand([&]() -> void + { m_intakeSubsystem.StopIntake(); }, + {&m_intakeSubsystem}) + .ToPtr()); + // Runs shoot command to move arm into postion, start up the shooting motors and eject the note m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); m_xbox.X().OnTrue(PositionTransferArm(&m_transferArmSubsystem, 90_deg).ToPtr()); // Example Only diff --git a/src/main/cpp/commands/IntakeCommand.cpp b/src/main/cpp/commands/IntakeCommand.cpp new file mode 100644 index 0000000..0bde30f --- /dev/null +++ b/src/main/cpp/commands/IntakeCommand.cpp @@ -0,0 +1,34 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/IntakeCommand.h" + + +// Called when the command is initially scheduled. +void IntakeCommand::Initialize() { + /* Start the intake motors. + Set finished to false to allow multiple calls of this command. + */ + intakeSubsystem->SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorVoltagePercent); + finished = false; +} + +// Called repeatedly when this Command is scheduled to run +void IntakeCommand::Execute() { + /* Checks if the limitswitches have been activated. If so sets finished to true and intake to stop. + Need to add code for moving arm pack to home position. */ + if (limit1.Get() or limit2.Get()){ + finished = true; + intakeSubsystem->StopIntake(); + // + } +} + +// Called once the command ends or is interrupted. +void IntakeCommand::End(bool interrupted) {} + +// Returns true when the command should end. +bool IntakeCommand::IsFinished() { + return finished; +} diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index 73cfbed..e9c316f 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -9,7 +9,7 @@ void ShootCommands::Initialize() { //Start the shooter motors and timer shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); - IsFinished = false; + finished = false; timer.Reset(); timer.Start(); } diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 1c94c5c..7393e24 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -153,6 +153,7 @@ namespace intake constexpr units::degree_t kIntakeArmLoad = 0.0_deg; constexpr double kIntakeSpinMotorVoltagePercent = .15; + constexpr double kIntakeSpinMotorEjectVoltagePercent = -.15; } namespace arm diff --git a/src/main/include/commands/IntakeCommand.h b/src/main/include/commands/IntakeCommand.h new file mode 100644 index 0000000..a1a6585 --- /dev/null +++ b/src/main/include/commands/IntakeCommand.h @@ -0,0 +1,41 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include "frc/DigitalInput.h" +#include "subsystems/IntakeSubsystem.h" +#include "Constants.h" +#include "subsystems/TransferArmSubsystem.h" + +/** + * An example command. + * + *

Note that this extends CommandHelper, rather extending Command + * directly; this is crucially important, or else the decorator functions in + * Command will *not* work! + */ +class IntakeCommand + : public frc2::CommandHelper { + public: + explicit IntakeCommand(IntakeSubsystem *intakeSubsystem) + : intakeSubsystem{intakeSubsystem} + { + AddRequirements(intakeSubsystem); + } + + void Initialize() override; + void Execute() override; + void End(bool interrupted) override; + bool IsFinished() override; + + private: + IntakeSubsystem *intakeSubsystem{nullptr}; + bool finished{false}; + frc::DigitalInput limit1{1}; + frc::DigitalInput limit2{2}; + +}; From c5597e2ff3c2208395f506d3d621102f98dd0262 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:24:34 -0800 Subject: [PATCH 104/126] Update PositionTransferArmCommand.cpp --- src/main/cpp/commands/PositionTransferArmCommand.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/cpp/commands/PositionTransferArmCommand.cpp b/src/main/cpp/commands/PositionTransferArmCommand.cpp index 06d4cbd..558acae 100644 --- a/src/main/cpp/commands/PositionTransferArmCommand.cpp +++ b/src/main/cpp/commands/PositionTransferArmCommand.cpp @@ -8,6 +8,7 @@ void PositionTransferArm::Initialize() noexcept { transferArmSubsystem->SetTransferArmPosition(position); timer.Reset(); + timer.Start(); } void PositionTransferArm::Execute() noexcept From 9510f04d0f3d5bbc98db0beb7a33f8b4870fc6a6 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:24:55 -0800 Subject: [PATCH 105/126] add climber timing constants --- src/main/include/Constants.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 1cf333f..115e10b 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -173,7 +173,9 @@ namespace climber constexpr int kClimberMotorCanID = 18; constexpr bool kClimberMotorIsInverted = false; constexpr double kClimberMotorRaiseVoltagePercent = .15; + constexpr units::second_t kClimberRaiseTimer = 2_s; constexpr double kClimberMotorLowerVoltagePercent = -.15; + constexpr units::second_t kClimberLowerTimer = 2_s; constexpr int kClimberSolenoidCanID = 19; From 13ba670e7a56da5cf561c366e2ee73fd1831725a Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:25:14 -0800 Subject: [PATCH 106/126] set climber motor inversion --- src/main/cpp/subsystems/ClimberSubsystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/cpp/subsystems/ClimberSubsystem.cpp b/src/main/cpp/subsystems/ClimberSubsystem.cpp index ccabfc9..d46a4e4 100644 --- a/src/main/cpp/subsystems/ClimberSubsystem.cpp +++ b/src/main/cpp/subsystems/ClimberSubsystem.cpp @@ -6,7 +6,7 @@ ClimberSubsystem::ClimberSubsystem() noexcept { - m_ClimberMotor.SetInverted(climber::kClimberMotorCanID); + m_ClimberMotor.SetInverted(climber::kClimberMotorIsInverted); } void ClimberSubsystem::StopClimber() noexcept From 85acfc9a1bfa6c8fcb0fe0702dfc3f04b7ab2bc6 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:25:35 -0800 Subject: [PATCH 107/126] added logic for commands to finish --- src/main/cpp/commands/ClimberLowerCommand.cpp | 10 +++++++--- src/main/cpp/commands/ClimberRaiseCommand.cpp | 13 +++++++++---- src/main/cpp/commands/ClimberStopCommand.cpp | 12 +++++++----- src/main/include/commands/ClimberLowerCommand.h | 2 ++ src/main/include/commands/ClimberRaiseCommand.h | 2 ++ 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/cpp/commands/ClimberLowerCommand.cpp b/src/main/cpp/commands/ClimberLowerCommand.cpp index e4dd2a4..cea3333 100644 --- a/src/main/cpp/commands/ClimberLowerCommand.cpp +++ b/src/main/cpp/commands/ClimberLowerCommand.cpp @@ -11,13 +11,17 @@ ClimberLowerCommand::ClimberLowerCommand() { */ // Called when the command is initially scheduled. -void ClimberLowerCommand::Initialize() {} +void ClimberLowerCommand::Initialize() { + timer.Reset(); + timer.Start(); +} // Called repeatedly when this Command is scheduled to run void ClimberLowerCommand::Execute() { //Start the climber motor to move climbing hook up. climberSubsystem->SetClimberMotorVoltagePercent(climber::kClimberMotorLowerVoltagePercent); -} + // End command after time defined in constants file. + if (timer.HasElapsed(climber::kClimberLowerTimer)) { finished = true; }} // Called once the command ends or is interrupted. void ClimberLowerCommand::End(bool interrupted) { @@ -26,5 +30,5 @@ void ClimberLowerCommand::End(bool interrupted) { // Returns true when the command should end. bool ClimberLowerCommand::IsFinished() { - return false; + return finished; } diff --git a/src/main/cpp/commands/ClimberRaiseCommand.cpp b/src/main/cpp/commands/ClimberRaiseCommand.cpp index 371ecc3..f05ab1b 100644 --- a/src/main/cpp/commands/ClimberRaiseCommand.cpp +++ b/src/main/cpp/commands/ClimberRaiseCommand.cpp @@ -6,12 +6,17 @@ // Called when the command is initially scheduled. -void ClimberRaiseCommand::Initialize() {} +void ClimberRaiseCommand::Initialize() { + timer.Reset(); + timer.Start(); +} // Called repeatedly when this Command is scheduled to run void ClimberRaiseCommand::Execute() { - //Start the climber motor to move climbing hook up. - climberSubsystem->SetClimberMotorVoltagePercent(climber::kClimberMotorRaiseVoltagePercent); + //Start the climber motor to move climbing hook up. + climberSubsystem->SetClimberMotorVoltagePercent(climber::kClimberMotorRaiseVoltagePercent); + // End command after time defined in constants file. + if (timer.HasElapsed(climber::kClimberRaiseTimer)) { finished = true; } } // Called once the command ends or is interrupted. @@ -21,5 +26,5 @@ void ClimberRaiseCommand::End(bool interrupted) { // Returns true when the command should end. bool ClimberRaiseCommand::IsFinished() { - return false; + return finished; } diff --git a/src/main/cpp/commands/ClimberStopCommand.cpp b/src/main/cpp/commands/ClimberStopCommand.cpp index 8a4452a..c21cf83 100644 --- a/src/main/cpp/commands/ClimberStopCommand.cpp +++ b/src/main/cpp/commands/ClimberStopCommand.cpp @@ -6,18 +6,20 @@ // Called when the command is initially scheduled. -void ClimberStopCommand::Initialize() {} - -// Called repeatedly when this Command is scheduled to run -void ClimberStopCommand::Execute() { +void ClimberStopCommand::Initialize() +{ //Stop the motors climberSubsystem->StopClimber(); + finished = true; } +// Called repeatedly when this Command is scheduled to run +void ClimberStopCommand::Execute() {} + // Called once the command ends or is interrupted. void ClimberStopCommand::End(bool interrupted) {} // Returns true when the command should end. bool ClimberStopCommand::IsFinished() { - return false; + return finished; } diff --git a/src/main/include/commands/ClimberLowerCommand.h b/src/main/include/commands/ClimberLowerCommand.h index 71de357..e1af39f 100644 --- a/src/main/include/commands/ClimberLowerCommand.h +++ b/src/main/include/commands/ClimberLowerCommand.h @@ -8,6 +8,7 @@ #include #include "subsystems/ClimberSubsystem.h" #include "Constants.h" +#include /** * An example command. @@ -32,5 +33,6 @@ class ClimberLowerCommand private: ClimberSubsystem *climberSubsystem{nullptr}; + frc::Timer timer{}; bool finished{false}; }; diff --git a/src/main/include/commands/ClimberRaiseCommand.h b/src/main/include/commands/ClimberRaiseCommand.h index 3df7682..a45c531 100644 --- a/src/main/include/commands/ClimberRaiseCommand.h +++ b/src/main/include/commands/ClimberRaiseCommand.h @@ -8,6 +8,7 @@ #include #include "subsystems/ClimberSubsystem.h" #include "Constants.h" +#include /** * An example command. @@ -32,5 +33,6 @@ class ClimberRaiseCommand private: ClimberSubsystem *climberSubsystem{nullptr}; + frc::Timer timer{}; bool finished{false}; }; From f66409bb4cf234005907ebf5de2bb545969f311d Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:29:00 -0800 Subject: [PATCH 108/126] Revert "Corrected ShootCommand error" This reverts commit ce0f0876baff0f65cacddd806506d6f92e4f7116. --- src/main/cpp/commands/ShootCommands.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index 73cfbed..2360b0e 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -9,7 +9,6 @@ void ShootCommands::Initialize() { //Start the shooter motors and timer shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); - IsFinished = false; timer.Reset(); timer.Start(); } From 16c2e8cc827c98d43f6ab166ee74845923a00fe2 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:39:18 -0800 Subject: [PATCH 109/126] Revert "Corrected ShootCommand error" This reverts commit ce0f0876baff0f65cacddd806506d6f92e4f7116. --- src/main/cpp/commands/ShootCommands.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index e9c316f..2360b0e 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -9,7 +9,6 @@ void ShootCommands::Initialize() { //Start the shooter motors and timer shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); - finished = false; timer.Reset(); timer.Start(); } From 7f1a877749d18b910e6bf77e52b5d16e1b911f17 Mon Sep 17 00:00:00 2001 From: Heatround <105463535+Heatround@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:54:23 -0500 Subject: [PATCH 110/126] Added an intake eject command IntakeEjectCommand for ejecting the note from the intake. --- src/main/cpp/RobotContainer.cpp | 6 +++ src/main/cpp/commands/IntakeEjectCommand.cpp | 37 ++++++++++++++++++ src/main/include/commands/IntakeCommand.h | 6 +-- .../include/commands/IntakeEjectCommand.h | 38 +++++++++++++++++++ 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 src/main/cpp/commands/IntakeEjectCommand.cpp create mode 100644 src/main/include/commands/IntakeEjectCommand.h diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 75f138a..3eee52c 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -19,6 +19,7 @@ #include "commands/IntakeCommand.h" #include "commands/ShootCommands.h" #include "commands/PositionTransferArmCommand.h" +#include "commands/IntakeEjectCommand.h" #include #include @@ -284,6 +285,8 @@ void RobotContainer::ConfigureBindings() noexcept .ToPtr()); */ + m_xbox.B().OnTrue(IntakeEjectCommand(&m_intakeSubsystem).ToPtr()); + /* m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorEjectVoltagePercent); }, {&m_intakeSubsystem}) @@ -292,11 +295,14 @@ void RobotContainer::ConfigureBindings() noexcept { m_intakeSubsystem.StopIntake(); }, {&m_intakeSubsystem}) .ToPtr()); +*/ // Runs shoot command to move arm into postion, start up the shooting motors and eject the note m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); m_xbox.X().OnTrue(PositionTransferArm(&m_transferArmSubsystem, 90_deg).ToPtr()); // Example Only + + } #pragma endregion diff --git a/src/main/cpp/commands/IntakeEjectCommand.cpp b/src/main/cpp/commands/IntakeEjectCommand.cpp new file mode 100644 index 0000000..fd52729 --- /dev/null +++ b/src/main/cpp/commands/IntakeEjectCommand.cpp @@ -0,0 +1,37 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/IntakeEjectCommand.h" + + +// Called when the command is initially scheduled. +void IntakeEjectCommand::Initialize() { + /* Start the intake motors in the reverse direction + Set finished to false to allow multiple calls of this command + */ + intakeSubsystem->SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorEjectVoltagePercent); + finished = false; + timer.Reset(); + timer.Start(); +} + +// Called repeatedly when this Command is scheduled to run +void IntakeEjectCommand::Execute() { + //Run the intake motors in reverse for 2 seconds then stop the intake + if (timer.HasElapsed(2_s)){ + finished = true; + intakeSubsystem->StopIntake(); + } +} + +// Called once the command ends or is interrupted. +void IntakeEjectCommand::End(bool interrupted) { + //Stop the intake motors + intakeSubsystem->StopIntake(); +} + +// Returns true when the command should end. +bool IntakeEjectCommand::IsFinished() { + return finished; +} diff --git a/src/main/include/commands/IntakeCommand.h b/src/main/include/commands/IntakeCommand.h index a1a6585..6375146 100644 --- a/src/main/include/commands/IntakeCommand.h +++ b/src/main/include/commands/IntakeCommand.h @@ -12,11 +12,7 @@ #include "subsystems/TransferArmSubsystem.h" /** - * An example command. - * - *

Note that this extends CommandHelper, rather extending Command - * directly; this is crucially important, or else the decorator functions in - * Command will *not* work! + * This command is for picking up a note and moving to the home position with the arm */ class IntakeCommand : public frc2::CommandHelper { diff --git a/src/main/include/commands/IntakeEjectCommand.h b/src/main/include/commands/IntakeEjectCommand.h new file mode 100644 index 0000000..79d5c43 --- /dev/null +++ b/src/main/include/commands/IntakeEjectCommand.h @@ -0,0 +1,38 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include "subsystems/IntakeSubsystem.h" +#include "Constants.h" +#include + + + +/** + * This command is for ejecting the note from the intake. + * This can be called by the shootcommand to feed into the shooter + * Or used to eject a note onto the field + */ +class IntakeEjectCommand + : public frc2::CommandHelper { + public: + explicit IntakeEjectCommand(IntakeSubsystem *intakeSubsystem) + : intakeSubsystem{intakeSubsystem} + { + AddRequirements(intakeSubsystem); + } + + void Initialize() override; + void Execute() override; + void End(bool interrupted) override; + bool IsFinished() override; + + private: + IntakeSubsystem *intakeSubsystem{nullptr}; + bool finished{false}; + frc::Timer timer{}; +}; From 6f384500d7f4d3735068e70136868e97cda48fc1 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Thu, 22 Feb 2024 18:30:16 -0500 Subject: [PATCH 111/126] Add Operator Controller, Split Controls Based on Function --- src/main/cpp/RobotContainer.cpp | 32 +++++++++++++++---------------- src/main/include/RobotContainer.h | 5 +++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index fcbbbce..e242e61 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -144,13 +144,13 @@ std::tuple RobotContainer::GetDriveTeleopControls( Finally, the other controller joystick is used for commanding rotation and things work out so that this is also an inverted X axis. */ - double LeftStickX = -m_xbox.GetLeftY(); - double LeftStickY = -m_xbox.GetLeftX(); - double rightStickRot = -m_xbox.GetRightX(); + double LeftStickX = -m_xboxDrive.GetLeftY(); + double LeftStickY = -m_xboxDrive.GetLeftX(); + double rightStickRot = -m_xboxDrive.GetRightX(); if (triggerSpeedEnabled) // scale speed by analog trigger { - double RightTrigAnalogVal = m_xbox.GetRightTriggerAxis(); + double RightTrigAnalogVal = m_xboxDrive.GetRightTriggerAxis(); RightTrigAnalogVal = ConditionRawTriggerInput(RightTrigAnalogVal); if (LeftStickX != 0 || LeftStickY != 0) @@ -190,7 +190,7 @@ std::tuple RobotContainer::GetDriveTeleopControls( double RobotContainer::ConditionRawTriggerInput(double RawTrigVal) noexcept { // Set a raw trigger value using the right trigger - RawTrigVal = m_xbox.GetRightTriggerAxis(); + RawTrigVal = m_xboxDrive.GetRightTriggerAxis(); double deadZoneVal = 0.05; double deadZoneCorrection = 1.0 / (1.0 - deadZoneVal); @@ -257,39 +257,39 @@ double RobotContainer::ConditionRawJoystickInput(double RawJoystickVal, double m void RobotContainer::ConfigureBindings() noexcept { // TODO: define Keybindings here - m_xbox.Start().OnTrue( + m_xboxDrive.Start().OnTrue( frc2::InstantCommand([&]() -> void { triggerSpeedEnabled = !triggerSpeedEnabled; }, {}) .ToPtr()); - m_xbox.A().OnTrue(frc2::InstantCommand([&]() -> void + m_xboxOperate.A().OnTrue(frc2::InstantCommand([&]() -> void { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorVoltagePercent); }, {&m_intakeSubsystem}) .ToPtr()); - m_xbox.A().OnFalse(frc2::InstantCommand([&]() -> void + m_xboxOperate.A().OnFalse(frc2::InstantCommand([&]() -> void { m_intakeSubsystem.StopIntake(); }, {&m_intakeSubsystem}) .ToPtr()); - m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void + m_xboxOperate.B().OnTrue(frc2::InstantCommand([&]() -> void { m_shooterSubsystem.SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); }, {&m_shooterSubsystem}) .ToPtr()); - m_xbox.B().OnFalse(frc2::InstantCommand([&]() -> void + m_xboxOperate.B().OnFalse(frc2::InstantCommand([&]() -> void { m_shooterSubsystem.StopShooter(); }, {&m_shooterSubsystem}) .ToPtr()); - m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); + m_xboxOperate.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); - m_xbox.X().OnTrue(PositionTransferArm(&m_transferArmSubsystem, 90_deg).ToPtr()); // Example Only + m_xboxOperate.X().OnTrue(PositionTransferArm(&m_transferArmSubsystem, 90_deg).ToPtr()); // Example Only - m_xbox.LeftBumper().WhileTrue(ClimberRaiseCommand(&m_climberSubsystem).ToPtr()); // Raise the climber while button is pressed. - m_xbox.LeftBumper().OnFalse(ClimberStopCommand(&m_climberSubsystem).ToPtr()); // on false stop the climber motor + m_xboxOperate.LeftBumper().WhileTrue(ClimberRaiseCommand(&m_climberSubsystem).ToPtr()); // Raise the climber while button is pressed. + m_xboxOperate.LeftBumper().OnFalse(ClimberStopCommand(&m_climberSubsystem).ToPtr()); // on false stop the climber motor - m_xbox.LeftTrigger().WhileTrue(ClimberLowerCommand(&m_climberSubsystem).ToPtr()); //Lower the climber while button is pressed - m_xbox.LeftTrigger().OnFalse(ClimberStopCommand(&m_climberSubsystem).ToPtr()); // on false stop the climber motor + m_xboxOperate.LeftTrigger().WhileTrue(ClimberLowerCommand(&m_climberSubsystem).ToPtr()); //Lower the climber while button is pressed + m_xboxOperate.LeftTrigger().OnFalse(ClimberStopCommand(&m_climberSubsystem).ToPtr()); // on false stop the climber motor diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index c337dde..49d40fb 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -59,8 +59,9 @@ class RobotContainer bool m_fieldOriented{true}; bool m_lock{false}; bool triggerSpeedEnabled{false}; - - frc2::CommandXboxController m_xbox{0}; + + frc2::CommandXboxController m_xboxDrive{0}; + frc2::CommandXboxController m_xboxOperate{1}; // frc2::CommandXboxController m_xbox{1}; TODO: update for second controller #pragma endregion From cd6090a6c1764d4af480453453c9e937f2b71570 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:05:55 -0500 Subject: [PATCH 112/126] Post-test of intake Tested the code and fine tuned the intake motor settings. --- src/main/cpp/commands/IntakeCommand.cpp | 19 ++++++++++++++++--- src/main/include/Constants.h | 4 ++-- src/main/include/commands/IntakeCommand.h | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/cpp/commands/IntakeCommand.cpp b/src/main/cpp/commands/IntakeCommand.cpp index 0bde30f..9f62212 100644 --- a/src/main/cpp/commands/IntakeCommand.cpp +++ b/src/main/cpp/commands/IntakeCommand.cpp @@ -3,7 +3,14 @@ // the WPILib BSD license file in the root directory of this project. #include "commands/IntakeCommand.h" - +#include +#include +#include +#include +#include +#include +#include +#include // Called when the command is initially scheduled. void IntakeCommand::Initialize() { @@ -18,15 +25,21 @@ void IntakeCommand::Initialize() { void IntakeCommand::Execute() { /* Checks if the limitswitches have been activated. If so sets finished to true and intake to stop. Need to add code for moving arm pack to home position. */ + frc::SmartDashboard::PutBoolean(" Limit1 Boolean Value: ", limit1.Get()); + frc::SmartDashboard::PutBoolean("Limit2 Boolean Value: ", limit2.Get()); if (limit1.Get() or limit2.Get()){ + finished = true; intakeSubsystem->StopIntake(); - // + //Add code to raise the arm to the home position } + //elseif (!limit2.Get()){} } // Called once the command ends or is interrupted. -void IntakeCommand::End(bool interrupted) {} +void IntakeCommand::End(bool interrupted) { + +} // Returns true when the command should end. bool IntakeCommand::IsFinished() { diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 7393e24..eb328a9 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -152,8 +152,8 @@ namespace intake constexpr units::degree_t kIntakeArmPickup = 180.0_deg; constexpr units::degree_t kIntakeArmLoad = 0.0_deg; - constexpr double kIntakeSpinMotorVoltagePercent = .15; - constexpr double kIntakeSpinMotorEjectVoltagePercent = -.15; + constexpr double kIntakeSpinMotorVoltagePercent = .50; + constexpr double kIntakeSpinMotorEjectVoltagePercent = -.40; } namespace arm diff --git a/src/main/include/commands/IntakeCommand.h b/src/main/include/commands/IntakeCommand.h index 6375146..6e2af3f 100644 --- a/src/main/include/commands/IntakeCommand.h +++ b/src/main/include/commands/IntakeCommand.h @@ -31,7 +31,7 @@ class IntakeCommand private: IntakeSubsystem *intakeSubsystem{nullptr}; bool finished{false}; - frc::DigitalInput limit1{1}; - frc::DigitalInput limit2{2}; + frc::DigitalInput limit1{8}; + frc::DigitalInput limit2{9}; }; From 3c2e5c48e17fbdaba260b361f225c560043f01b9 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sat, 24 Feb 2024 13:36:12 -0500 Subject: [PATCH 113/126] testing commit --- src/main/cpp/commands/IntakeCommand.cpp | 8 ++++++-- .../commands/PositionTransferArmCommand.cpp | 10 ++++++---- .../cpp/subsystems/TransferArmSubsystem.cpp | 20 ++++++++++++++----- src/main/include/Constants.h | 4 ++-- .../include/subsystems/TransferArmSubsystem.h | 5 +++-- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/main/cpp/commands/IntakeCommand.cpp b/src/main/cpp/commands/IntakeCommand.cpp index 9f62212..0041cf6 100644 --- a/src/main/cpp/commands/IntakeCommand.cpp +++ b/src/main/cpp/commands/IntakeCommand.cpp @@ -19,21 +19,25 @@ void IntakeCommand::Initialize() { */ intakeSubsystem->SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorVoltagePercent); finished = false; + + frc::SmartDashboard::PutBoolean("Limit1 Boolean Value: ", limit1.Get()); + frc::SmartDashboard::PutBoolean("Limit2 Boolean Value: ", limit2.Get()); } // Called repeatedly when this Command is scheduled to run void IntakeCommand::Execute() { /* Checks if the limitswitches have been activated. If so sets finished to true and intake to stop. Need to add code for moving arm pack to home position. */ - frc::SmartDashboard::PutBoolean(" Limit1 Boolean Value: ", limit1.Get()); + frc::SmartDashboard::PutBoolean("Limit1 Boolean Value: ", limit1.Get()); frc::SmartDashboard::PutBoolean("Limit2 Boolean Value: ", limit2.Get()); + if (limit1.Get() or limit2.Get()){ finished = true; intakeSubsystem->StopIntake(); //Add code to raise the arm to the home position } - //elseif (!limit2.Get()){} + } // Called once the command ends or is interrupted. diff --git a/src/main/cpp/commands/PositionTransferArmCommand.cpp b/src/main/cpp/commands/PositionTransferArmCommand.cpp index 06d4cbd..b4ed33d 100644 --- a/src/main/cpp/commands/PositionTransferArmCommand.cpp +++ b/src/main/cpp/commands/PositionTransferArmCommand.cpp @@ -6,17 +6,19 @@ void PositionTransferArm::Initialize() noexcept { - transferArmSubsystem->SetTransferArmPosition(position); + // transferArmSubsystem->SetTransferArmPosition(position); timer.Reset(); + timer.Start(); } void PositionTransferArm::Execute() noexcept { - transferArmSubsystem->UpdatePIDValues(); - if (timer.HasElapsed(5_s)) { finished = true; } + // transferArmSubsystem->UpdatePIDValues();s + transferArmSubsystem->SetArmMotorVoltagePercent(-.05); + if (timer.HasElapsed(2_s)) { finished = true; } } void PositionTransferArm::End(bool interrupted) noexcept { - + transferArmSubsystem->StopTransferArm(); } diff --git a/src/main/cpp/subsystems/TransferArmSubsystem.cpp b/src/main/cpp/subsystems/TransferArmSubsystem.cpp index b3137b1..5c3cc7b 100644 --- a/src/main/cpp/subsystems/TransferArmSubsystem.cpp +++ b/src/main/cpp/subsystems/TransferArmSubsystem.cpp @@ -6,6 +6,8 @@ TransferArmSubsystem::TransferArmSubsystem() noexcept { m_TransferArmMotor.SetInverted(arm::kTransferArmMotorIsInverted); + m_encoder.SetMeasurementPeriod(10); + // set PID coefficients m_pidController.SetP(kP); m_pidController.SetI(kI); @@ -19,6 +21,8 @@ TransferArmSubsystem::TransferArmSubsystem() noexcept frc::SmartDashboard::PutNumber("Arm D Gain ", kD); frc::SmartDashboard::PutNumber("Arm F ", kFF); + frc::SmartDashboard::PutNumber("Arm Init Position ", m_encoder.GetPosition()); + StopTransferArm(); } @@ -27,17 +31,23 @@ void TransferArmSubsystem::StopTransferArm() noexcept m_TransferArmMotor.StopMotor(); } +void TransferArmSubsystem::SetArmMotorVoltagePercent(const double percent) noexcept +{ + frc::SmartDashboard::PutNumber("Arm Position ", m_encoder.GetPosition()); + m_TransferArmMotor.SetVoltage(percent * 12_V); +} + void TransferArmSubsystem::SetTransferArmPosition(const units::turn_t position) noexcept { m_pidController.SetReference(position.value(), rev::CANSparkMax::ControlType::kPosition); frc::SmartDashboard::PutNumber("SetPoint", position.value()); - // frc::SmartDashboard::PutNumber("ProcessVariable", m_encoder.GetPosition()); + frc::SmartDashboard::PutNumber("ProcessVariable", m_encoder.GetPosition()); } -// units::turn_t TransferArmSubsystem::GetTransferArmPosition() noexcept -// { -// return units::turn_t(m_encoder.GetPosition()); -// } +units::turn_t TransferArmSubsystem::GetTransferArmPosition() noexcept +{ + return units::turn_t(m_encoder.GetPosition()); +} void TransferArmSubsystem::UpdatePIDValues() noexcept { diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index eb328a9..a53985d 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -152,7 +152,7 @@ namespace intake constexpr units::degree_t kIntakeArmPickup = 180.0_deg; constexpr units::degree_t kIntakeArmLoad = 0.0_deg; - constexpr double kIntakeSpinMotorVoltagePercent = .50; + constexpr double kIntakeSpinMotorVoltagePercent = .1; constexpr double kIntakeSpinMotorEjectVoltagePercent = -.40; } @@ -163,7 +163,7 @@ namespace arm constexpr bool kTransferArmMotorIsInverted = false; // Arm Controller - constexpr double kArmPositionP = 0.005; + constexpr double kArmPositionP = 0.1; constexpr double kArmPositionD = 0.0005; constexpr double kArmPositionF = 0.0; } diff --git a/src/main/include/subsystems/TransferArmSubsystem.h b/src/main/include/subsystems/TransferArmSubsystem.h index 61fd090..7a47b9d 100644 --- a/src/main/include/subsystems/TransferArmSubsystem.h +++ b/src/main/include/subsystems/TransferArmSubsystem.h @@ -14,13 +14,14 @@ class TransferArmSubsystem : public frc2::SubsystemBase TransferArmSubsystem &operator=(const TransferArmSubsystem &) = delete; void StopTransferArm() noexcept; + void SetArmMotorVoltagePercent(const double percent) noexcept; void SetTransferArmPosition(const units::turn_t position) noexcept; - // units::turn_t GetTransferArmPosition() noexcept; + units::turn_t GetTransferArmPosition() noexcept; void UpdatePIDValues() noexcept; private: rev::CANSparkMax m_TransferArmMotor{arm::kTransferArmMotorCanID, rev::CANSparkMax::MotorType::kBrushless}; - // rev::CANEncoder m_encoder = m_TransferArmMotor.GetAlternateEncoder(rev::CANEncoder::AlternateEncoderType::kQuadrature, 8192); + rev::SparkMaxAlternateEncoder m_encoder = m_TransferArmMotor.GetAlternateEncoder(8192); rev::SparkPIDController m_pidController = m_TransferArmMotor.GetPIDController(); double kP = arm::kArmPositionP, kI = 0, kD = arm::kArmPositionD, kIz = 0, kFF = arm::kArmPositionF, kMaxOutput = 1, kMinOutput = -1; }; From d1303b1ae579154bc61770824e8549507b9e8518 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sat, 24 Feb 2024 16:40:39 -0500 Subject: [PATCH 114/126] add prints --- src/main/cpp/commands/PositionTransferArmCommand.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/cpp/commands/PositionTransferArmCommand.cpp b/src/main/cpp/commands/PositionTransferArmCommand.cpp index b4ed33d..c6d13fd 100644 --- a/src/main/cpp/commands/PositionTransferArmCommand.cpp +++ b/src/main/cpp/commands/PositionTransferArmCommand.cpp @@ -3,9 +3,12 @@ // the WPILib BSD license file in the root directory of this project. #include "commands/PositionTransferArmCommand.h" +#include void PositionTransferArm::Initialize() noexcept { + finished = false; + // transferArmSubsystem->SetTransferArmPosition(position); timer.Reset(); timer.Start(); @@ -13,8 +16,9 @@ void PositionTransferArm::Initialize() noexcept void PositionTransferArm::Execute() noexcept { + frc::SmartDashboard::PutNumber("Arm Timer ", timer.Get().value()); // transferArmSubsystem->UpdatePIDValues();s - transferArmSubsystem->SetArmMotorVoltagePercent(-.05); + transferArmSubsystem->SetArmMotorVoltagePercent(.05); if (timer.HasElapsed(2_s)) { finished = true; } } From b7962245b76fb9021f9d7e9d3d0f8bc42d926bd8 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:21:37 -0500 Subject: [PATCH 115/126] comment out PID --- .../cpp/subsystems/TransferArmSubsystem.cpp | 54 +++++++++---------- .../include/subsystems/TransferArmSubsystem.h | 8 +-- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/main/cpp/subsystems/TransferArmSubsystem.cpp b/src/main/cpp/subsystems/TransferArmSubsystem.cpp index 5c3cc7b..5cb3cd8 100644 --- a/src/main/cpp/subsystems/TransferArmSubsystem.cpp +++ b/src/main/cpp/subsystems/TransferArmSubsystem.cpp @@ -9,17 +9,17 @@ TransferArmSubsystem::TransferArmSubsystem() noexcept m_encoder.SetMeasurementPeriod(10); // set PID coefficients - m_pidController.SetP(kP); - m_pidController.SetI(kI); - m_pidController.SetD(kD); - m_pidController.SetIZone(kIz); - m_pidController.SetFF(kFF); - m_pidController.SetOutputRange(kMinOutput, kMaxOutput); + // m_pidController.SetP(kP); + // m_pidController.SetI(kI); + // m_pidController.SetD(kD); + // m_pidController.SetIZone(kIz); + // m_pidController.SetFF(kFF); + // m_pidController.SetOutputRange(kMinOutput, kMaxOutput); // display PID coefficients on SmartDashboard - frc::SmartDashboard::PutNumber("Arm P Gain ", kP); - frc::SmartDashboard::PutNumber("Arm D Gain ", kD); - frc::SmartDashboard::PutNumber("Arm F ", kFF); + // frc::SmartDashboard::PutNumber("Arm P Gain ", kP); + // frc::SmartDashboard::PutNumber("Arm D Gain ", kD); + // frc::SmartDashboard::PutNumber("Arm F ", kFF); frc::SmartDashboard::PutNumber("Arm Init Position ", m_encoder.GetPosition()); @@ -33,30 +33,30 @@ void TransferArmSubsystem::StopTransferArm() noexcept void TransferArmSubsystem::SetArmMotorVoltagePercent(const double percent) noexcept { - frc::SmartDashboard::PutNumber("Arm Position ", m_encoder.GetPosition()); m_TransferArmMotor.SetVoltage(percent * 12_V); } -void TransferArmSubsystem::SetTransferArmPosition(const units::turn_t position) noexcept -{ - m_pidController.SetReference(position.value(), rev::CANSparkMax::ControlType::kPosition); - frc::SmartDashboard::PutNumber("SetPoint", position.value()); - frc::SmartDashboard::PutNumber("ProcessVariable", m_encoder.GetPosition()); -} +// void TransferArmSubsystem::SetTransferArmPosition(const units::turn_t position) noexcept +// { +// m_pidController.SetReference(position.value(), rev::CANSparkMax::ControlType::kPosition); +// frc::SmartDashboard::PutNumber("SetPoint", position.value()); +// frc::SmartDashboard::PutNumber("ProcessVariable", m_encoder.GetPosition()); +// } units::turn_t TransferArmSubsystem::GetTransferArmPosition() noexcept { + frc::SmartDashboard::PutNumber("Arm Position ", m_encoder.GetPosition()); return units::turn_t(m_encoder.GetPosition()); } -void TransferArmSubsystem::UpdatePIDValues() noexcept -{ - double p = frc::SmartDashboard::GetNumber("Arm P Gain ", 0); - double d = frc::SmartDashboard::GetNumber("Arm D Gain ", 0); - double ff = frc::SmartDashboard::GetNumber("Arm F ", 0); - - // if PID coefficients on SmartDashboard have changed, write new values to controller - if((p != kP)) { m_pidController.SetP(p); kP = p; } - if((d != kD)) { m_pidController.SetD(d); kD = d; } - if((ff != kFF)) { m_pidController.SetFF(ff); kFF = ff; } -} +// void TransferArmSubsystem::UpdatePIDValues() noexcept +// { +// double p = frc::SmartDashboard::GetNumber("Arm P Gain ", 0); +// double d = frc::SmartDashboard::GetNumber("Arm D Gain ", 0); +// double ff = frc::SmartDashboard::GetNumber("Arm F ", 0); + +// // if PID coefficients on SmartDashboard have changed, write new values to controller +// if((p != kP)) { m_pidController.SetP(p); kP = p; } +// if((d != kD)) { m_pidController.SetD(d); kD = d; } +// if((ff != kFF)) { m_pidController.SetFF(ff); kFF = ff; } +// } diff --git a/src/main/include/subsystems/TransferArmSubsystem.h b/src/main/include/subsystems/TransferArmSubsystem.h index 7a47b9d..b74f814 100644 --- a/src/main/include/subsystems/TransferArmSubsystem.h +++ b/src/main/include/subsystems/TransferArmSubsystem.h @@ -15,13 +15,13 @@ class TransferArmSubsystem : public frc2::SubsystemBase void StopTransferArm() noexcept; void SetArmMotorVoltagePercent(const double percent) noexcept; - void SetTransferArmPosition(const units::turn_t position) noexcept; + // void SetTransferArmPosition(const units::turn_t position) noexcept; units::turn_t GetTransferArmPosition() noexcept; - void UpdatePIDValues() noexcept; + // void UpdatePIDValues() noexcept; private: rev::CANSparkMax m_TransferArmMotor{arm::kTransferArmMotorCanID, rev::CANSparkMax::MotorType::kBrushless}; rev::SparkMaxAlternateEncoder m_encoder = m_TransferArmMotor.GetAlternateEncoder(8192); - rev::SparkPIDController m_pidController = m_TransferArmMotor.GetPIDController(); - double kP = arm::kArmPositionP, kI = 0, kD = arm::kArmPositionD, kIz = 0, kFF = arm::kArmPositionF, kMaxOutput = 1, kMinOutput = -1; + // rev::SparkPIDController m_pidController = m_TransferArmMotor.GetPIDController(); + // double kP = arm::kArmPositionP, kI = 0, kD = arm::kArmPositionD, kIz = 0, kFF = arm::kArmPositionF, kMaxOutput = 1, kMinOutput = -1; }; From 53209a956fa811df09b58811f9a2eee3fa4aad5f Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:21:45 -0500 Subject: [PATCH 116/126] add PID command --- .../cpp/commands/PIDTransferArmCommand.cpp | 38 +++++++++++++++++++ .../include/commands/PIDTransferArmCommand.h | 30 +++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/main/cpp/commands/PIDTransferArmCommand.cpp create mode 100644 src/main/include/commands/PIDTransferArmCommand.h diff --git a/src/main/cpp/commands/PIDTransferArmCommand.cpp b/src/main/cpp/commands/PIDTransferArmCommand.cpp new file mode 100644 index 0000000..d91aaba --- /dev/null +++ b/src/main/cpp/commands/PIDTransferArmCommand.cpp @@ -0,0 +1,38 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/PIDTransferArmCommand.h" +#include + +PIDPositionTransferArm::PIDPositionTransferArm(units::turn_t targetPosition, TransferArmSubsystem* transferArmSubsystem) + : CommandHelper{frc::PIDController{arm::kArmPositionP, 0, arm::kArmPositionD}, + // Close loop on heading + [transferArmSubsystem] + { + frc::SmartDashboard::PutNumber("Arm Position ", transferArmSubsystem->GetTransferArmPosition().value()); + return transferArmSubsystem->GetTransferArmPosition().value(); + }, + // Set reference to targetPosition + targetPosition.value(), + // Pipe output to turn transfer arm + [transferArmSubsystem](double output) + { + frc::SmartDashboard::PutNumber("Arm PID Output ", output); + transferArmSubsystem->SetArmMotorVoltagePercent(output); + }, + // Require the transfer arm + {transferArmSubsystem}} { + // Set the controller to be continuous (because it is an angle controller) + m_controller.EnableContinuousInput(-1, 1); + // Set the controller tolerance - the delta tolerance ensures the robot is + // stationary at the setpoint before it is considered as having reached the + // reference + m_controller.SetTolerance(.01, .01); + + AddRequirements(transferArmSubsystem); +} + +bool PIDPositionTransferArm::IsFinished() { + return m_controller.AtSetpoint(); +} diff --git a/src/main/include/commands/PIDTransferArmCommand.h b/src/main/include/commands/PIDTransferArmCommand.h new file mode 100644 index 0000000..753def0 --- /dev/null +++ b/src/main/include/commands/PIDTransferArmCommand.h @@ -0,0 +1,30 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "subsystems/TransferArmSubsystem.h" +#include "Constants.h" + +/** + * A command that will turn the transfer arm to the specified angle. + */ +class PIDPositionTransferArm : public frc2::CommandHelper { + public: + /** + * Turns the transfer arm to robot to the specified angle. + * + * @param targetAngleDegrees The angle to turn to + * @param transferArmSubsystem The transferArmSubsystem subsystem to use + */ + PIDPositionTransferArm(units::turn_t targetPosition, TransferArmSubsystem* transferArmSubsystem); + + bool IsFinished() override; +}; From cf8dec0397a29cc1d4048ed68754f41fd763a7f6 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:23:44 -0500 Subject: [PATCH 117/126] update controls --- src/main/cpp/RobotContainer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 3eee52c..f01570a 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -19,6 +19,7 @@ #include "commands/IntakeCommand.h" #include "commands/ShootCommands.h" #include "commands/PositionTransferArmCommand.h" +#include "commands/PIDTransferArmCommand.h" #include "commands/IntakeEjectCommand.h" #include @@ -300,7 +301,7 @@ void RobotContainer::ConfigureBindings() noexcept // Runs shoot command to move arm into postion, start up the shooting motors and eject the note m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); - m_xbox.X().OnTrue(PositionTransferArm(&m_transferArmSubsystem, 90_deg).ToPtr()); // Example Only + m_xbox.X().OnTrue(PIDPositionTransferArm(90_deg, &m_transferArmSubsystem).ToPtr()); // Example Only } From c0f2798d9c67867d67afbf1932e4b9c928eaf425 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 25 Feb 2024 12:53:42 -0500 Subject: [PATCH 118/126] add reset prints --- src/main/cpp/commands/PIDTransferArmCommand.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/cpp/commands/PIDTransferArmCommand.cpp b/src/main/cpp/commands/PIDTransferArmCommand.cpp index d91aaba..9bd367e 100644 --- a/src/main/cpp/commands/PIDTransferArmCommand.cpp +++ b/src/main/cpp/commands/PIDTransferArmCommand.cpp @@ -31,8 +31,14 @@ PIDPositionTransferArm::PIDPositionTransferArm(units::turn_t targetPosition, Tra m_controller.SetTolerance(.01, .01); AddRequirements(transferArmSubsystem); + + frc::SmartDashboard::PutBoolean("PID Command Reset ", true); } bool PIDPositionTransferArm::IsFinished() { - return m_controller.AtSetpoint(); + if(m_controller.AtSetpoint()) + { + frc::SmartDashboard::PutBoolean("PID Command Reset ", false); + } + return m_controller.AtSetpoint(); } From 9b6d70ae98d02ddbfdee307d9fd89a9ccb3907a3 Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Sun, 25 Feb 2024 13:09:19 -0500 Subject: [PATCH 119/126] bumper arm keybindings --- src/main/cpp/RobotContainer.cpp | 7 +++---- src/main/include/Constants.h | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index f01570a..042558b 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -300,10 +300,9 @@ void RobotContainer::ConfigureBindings() noexcept // Runs shoot command to move arm into postion, start up the shooting motors and eject the note m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); - - m_xbox.X().OnTrue(PIDPositionTransferArm(90_deg, &m_transferArmSubsystem).ToPtr()); // Example Only - - + + m_xbox.LeftBumper().OnTrue(PIDPositionTransferArm(0_deg, &m_transferArmSubsystem).ToPtr()); // Intake + m_xbox.RightBumper().OnTrue(PIDPositionTransferArm(arm::kIntakeToShooterAngle, &m_transferArmSubsystem).ToPtr()); // Shooter } #pragma endregion diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index a53985d..3438e93 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -166,6 +166,8 @@ namespace arm constexpr double kArmPositionP = 0.1; constexpr double kArmPositionD = 0.0005; constexpr double kArmPositionF = 0.0; + + constexpr units::turn_t kIntakeToShooterAngle = 90_deg; } namespace climber From 26ec29d99fda31d897f9b348811d467f42ae3aa8 Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:38:43 -0500 Subject: [PATCH 120/126] Update Constants.h intake constants updated --- src/main/include/Constants.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 3438e93..077b9c3 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -152,22 +152,22 @@ namespace intake constexpr units::degree_t kIntakeArmPickup = 180.0_deg; constexpr units::degree_t kIntakeArmLoad = 0.0_deg; - constexpr double kIntakeSpinMotorVoltagePercent = .1; - constexpr double kIntakeSpinMotorEjectVoltagePercent = -.40; + constexpr double kIntakeSpinMotorVoltagePercent = .75; + constexpr double kIntakeSpinMotorEjectVoltagePercent = -.90; } namespace arm { // Arm Motor Parameters constexpr int kTransferArmMotorCanID = 17; - constexpr bool kTransferArmMotorIsInverted = false; + constexpr bool kTransferArmMotorIsInverted = true; // Arm Controller - constexpr double kArmPositionP = 0.1; - constexpr double kArmPositionD = 0.0005; + constexpr double kArmPositionP = 10.0; + constexpr double kArmPositionD = .10; constexpr double kArmPositionF = 0.0; - constexpr units::turn_t kIntakeToShooterAngle = 90_deg; + constexpr units::turn_t kIntakeToShooterAngle = 200_deg; } namespace climber From 553240e02eab727f9e0ed97e826b29a72c9bbf1f Mon Sep 17 00:00:00 2001 From: Scott <114890628+skoot1331@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:31:54 -0500 Subject: [PATCH 121/126] update controls --- src/main/cpp/RobotContainer.cpp | 43 ++++----------------------------- src/main/include/Constants.h | 3 ++- 2 files changed, 7 insertions(+), 39 deletions(-) diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index 38d0dba..9098b12 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -266,53 +266,20 @@ void RobotContainer::ConfigureBindings() noexcept {}) .ToPtr()); - /* Commented out, changing A to run the IntakeCommand for picking up a note - m_xbox.A().OnTrue(frc2::InstantCommand([&]() -> void - { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorVoltagePercent); }, - {&m_intakeSubsystem}) - .ToPtr()); - m_xbox.A().OnFalse(frc2::InstantCommand([&]() -> void - { m_intakeSubsystem.StopIntake(); }, - {&m_intakeSubsystem}) - .ToPtr()); */ m_xbox.A().OnTrue(IntakeCommand(&m_intakeSubsystem).ToPtr()); - - - /* Commented out, shooter functions are in Y now. Reusing B for the intake eject command - m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void - { m_shooterSubsystem.SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); }, - {&m_shooterSubsystem}) - .ToPtr()); - m_xbox.B().OnFalse(frc2::InstantCommand([&]() -> void - { m_shooterSubsystem.StopShooter(); }, - {&m_shooterSubsystem}) - .ToPtr()); - */ - m_xbox.B().OnTrue(IntakeEjectCommand(&m_intakeSubsystem).ToPtr()); - /* - m_xbox.B().OnTrue(frc2::InstantCommand([&]() -> void - { m_intakeSubsystem.SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorEjectVoltagePercent); }, - {&m_intakeSubsystem}) - .ToPtr()); - m_xbox.B().OnFalse(frc2::InstantCommand([&]() -> void - { m_intakeSubsystem.StopIntake(); }, - {&m_intakeSubsystem}) - .ToPtr()); -*/ // Runs shoot command to move arm into postion, start up the shooting motors and eject the note m_xbox.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); - m_xbox.X().OnTrue(PositionTransferArm(&m_transferArmSubsystem, 90_deg).ToPtr()); // Example Only - - m_xbox.LeftBumper().OnTrue(PIDPositionTransferArm(0_deg, &m_transferArmSubsystem).ToPtr()); // Intake - m_xbox.RightBumper().OnTrue(PIDPositionTransferArm(arm::kIntakeToShooterAngle, &m_transferArmSubsystem).ToPtr()); // Shooter + m_xbox.X().OnTrue(PIDPositionTransferArm(arm::kShooterToAmpAngle, &m_transferArmSubsystem).ToPtr()); // Example Only + m_xbox.LeftBumper().OnTrue(PIDPositionTransferArm(arm::kShooterToIntakeAngle, &m_transferArmSubsystem).ToPtr()); // Intake + m_xbox.RightBumper().OnTrue(PIDPositionTransferArm(0_deg, &m_transferArmSubsystem).ToPtr()); // Shooter - m_xbox.RightTrigger().WhileTrue(ClimberRaiseCommand(&m_climberSubsystem).ToPtr()); // Raise the climber while button is pressed. + m_xbox.RightTrigger().OnTrue(ClimberRaiseCommand(&m_climberSubsystem).ToPtr()); // Raise the climber while button is pressed. m_xbox.RightTrigger().OnFalse(ClimberStopCommand(&m_climberSubsystem).ToPtr()); // on false stop the climber motor - m_xbox.LeftTrigger().WhileTrue(ClimberLowerCommand(&m_climberSubsystem).ToPtr()); //Lower the climber while button is pressed + m_xbox.LeftTrigger().OnTrue(ClimberLowerCommand(&m_climberSubsystem).ToPtr()); //Lower the climber while button is pressed m_xbox.LeftTrigger().OnFalse(ClimberStopCommand(&m_climberSubsystem).ToPtr()); // on false stop the climber motor } #pragma endregion diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 33eb008..8dcf4b0 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -167,7 +167,8 @@ namespace arm constexpr double kArmPositionD = .10; constexpr double kArmPositionF = 0.0; - constexpr units::turn_t kIntakeToShooterAngle = 200_deg; + constexpr units::turn_t kShooterToAmpAngle = -100_deg; + constexpr units::turn_t kShooterToIntakeAngle = -200_deg; } namespace climber From 247f6b054ef73e781c1157bfc70c49e79babf67b Mon Sep 17 00:00:00 2001 From: James2028s <156536948+James2028s@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:03:47 -0500 Subject: [PATCH 122/126] WorkingIntakew/shooter Branch to fix the working intake with the shooter --- src/main/cpp/commands/IntakeEjectCommand.cpp | 1 + src/main/cpp/commands/ShootCommands.cpp | 4 ++++ src/main/include/Constants.h | 2 +- src/main/include/commands/ShootCommands.h | 2 ++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/cpp/commands/IntakeEjectCommand.cpp b/src/main/cpp/commands/IntakeEjectCommand.cpp index fd52729..14ece14 100644 --- a/src/main/cpp/commands/IntakeEjectCommand.cpp +++ b/src/main/cpp/commands/IntakeEjectCommand.cpp @@ -10,6 +10,7 @@ void IntakeEjectCommand::Initialize() { /* Start the intake motors in the reverse direction Set finished to false to allow multiple calls of this command */ + intakeSubsystem->SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorEjectVoltagePercent); finished = false; timer.Reset(); diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index e9c316f..c3cbc87 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -3,11 +3,15 @@ // the WPILib BSD license file in the root directory of this project. #include "commands/ShootCommands.h" +#include "commands/IntakeEjectCommand.h" +#include + // Called when the command is initially scheduled. void ShootCommands::Initialize() { //Start the shooter motors and timer + frc2::RunCommand(IntakeEjectCommand(&intakeSubsystem)); shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); finished = false; timer.Reset(); diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index 8dcf4b0..e5cd407 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -167,7 +167,7 @@ namespace arm constexpr double kArmPositionD = .10; constexpr double kArmPositionF = 0.0; - constexpr units::turn_t kShooterToAmpAngle = -100_deg; + constexpr units::turn_t kShooterToAmpAngle = -50_deg; constexpr units::turn_t kShooterToIntakeAngle = -200_deg; } diff --git a/src/main/include/commands/ShootCommands.h b/src/main/include/commands/ShootCommands.h index 1f67ce9..334dee1 100644 --- a/src/main/include/commands/ShootCommands.h +++ b/src/main/include/commands/ShootCommands.h @@ -9,6 +9,7 @@ #include #include "subsystems/ShooterSubsystem.h" #include "subsystems/TransferArmSubsystem.h" +#include "subsystems/IntakeSubsystem.h" /** * An example command. @@ -34,6 +35,7 @@ class ShootCommands private: ShooterSubsystem *shooterSubsystem{nullptr}; + IntakeSubsystem *intakeSubsystem{nullptr}; frc::Timer timer{}; bool finished{false}; }; From 5b93c954eb44e7a0507d5d1eca1b97bf2cdb31b5 Mon Sep 17 00:00:00 2001 From: Heatround <105463535+Heatround@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:32:19 -0500 Subject: [PATCH 123/126] Fixes for the shooter Fixes the intake to start when the shooter button is pressed --- src/main/cpp/commands/ShootCommands.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index c3cbc87..b238b31 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -4,9 +4,13 @@ #include "commands/ShootCommands.h" #include "commands/IntakeEjectCommand.h" +<<<<<<< Updated upstream #include +======= +#include +>>>>>>> Stashed changes // Called when the command is initially scheduled. void ShootCommands::Initialize() { @@ -16,6 +20,8 @@ void ShootCommands::Initialize() { finished = false; timer.Reset(); timer.Start(); + frc2::Command(IntakeEjectCommand()); + } // Called repeatedly when this Command is scheduled to run From cb5b74f5c857f220546b7aa9237156f7ef3cfc20 Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Tue, 27 Feb 2024 18:21:11 -0500 Subject: [PATCH 124/126] Does not build - attempt to combine commands This is an initial attempt to use sequential and parallel commands. It errors during build. Putting on hold and going to plan B --- src/main/cpp/RobotContainer.cpp | 5 +- src/main/cpp/commands/ClimberLowerCommand.cpp | 1 + src/main/cpp/commands/IntakeEjectCommand.cpp | 10 +++- src/main/cpp/commands/ShootCommands.cpp | 12 +---- .../cpp/commands/ShootProcedureCommand.cpp | 31 +++++++++++++ src/main/include/RobotContainer.h | 1 + src/main/include/commands/ShootCommands.h | 5 +- .../include/commands/ShootProcedureCommand.h | 46 +++++++++++++++++++ 8 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 src/main/cpp/commands/ShootProcedureCommand.cpp create mode 100644 src/main/include/commands/ShootProcedureCommand.h diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index c4c938b..a44e840 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -24,6 +24,7 @@ #include "commands/PositionTransferArmCommand.h" #include "commands/PIDTransferArmCommand.h" #include "commands/IntakeEjectCommand.h" +#include "commands/ShootProcedureCommand.h" #include #include @@ -270,8 +271,10 @@ void RobotContainer::ConfigureBindings() noexcept m_xboxOperate.B().OnTrue(IntakeEjectCommand(&m_intakeSubsystem).ToPtr()); // Runs shoot command to move arm into postion, start up the shooting motors and eject the note - m_xboxOperate.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); + //m_xboxOperate.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); + m_xboxOperate.Y().OnTrue(ShootProcedureCommand().ToPtr()); + m_xboxOperate.X().OnTrue(PIDPositionTransferArm(arm::kShooterToAmpAngle, &m_transferArmSubsystem).ToPtr()); // Example Only m_xboxOperate.LeftBumper().OnTrue(PIDPositionTransferArm(arm::kShooterToIntakeAngle, &m_transferArmSubsystem).ToPtr()); // Intake m_xboxOperate.RightBumper().OnTrue(PIDPositionTransferArm(0_deg, &m_transferArmSubsystem).ToPtr()); // Shooter diff --git a/src/main/cpp/commands/ClimberLowerCommand.cpp b/src/main/cpp/commands/ClimberLowerCommand.cpp index cea3333..6d905b8 100644 --- a/src/main/cpp/commands/ClimberLowerCommand.cpp +++ b/src/main/cpp/commands/ClimberLowerCommand.cpp @@ -14,6 +14,7 @@ ClimberLowerCommand::ClimberLowerCommand() { void ClimberLowerCommand::Initialize() { timer.Reset(); timer.Start(); + finished = false; } // Called repeatedly when this Command is scheduled to run diff --git a/src/main/cpp/commands/IntakeEjectCommand.cpp b/src/main/cpp/commands/IntakeEjectCommand.cpp index 14ece14..231886d 100644 --- a/src/main/cpp/commands/IntakeEjectCommand.cpp +++ b/src/main/cpp/commands/IntakeEjectCommand.cpp @@ -11,16 +11,22 @@ void IntakeEjectCommand::Initialize() { Set finished to false to allow multiple calls of this command */ - intakeSubsystem->SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorEjectVoltagePercent); + finished = false; timer.Reset(); timer.Start(); + } // Called repeatedly when this Command is scheduled to run void IntakeEjectCommand::Execute() { + + if (timer.HasElapsed(1_s)){ + intakeSubsystem->SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorEjectVoltagePercent); + } + //Run the intake motors in reverse for 2 seconds then stop the intake - if (timer.HasElapsed(2_s)){ + if (timer.HasElapsed(3_s)){ finished = true; intakeSubsystem->StopIntake(); } diff --git a/src/main/cpp/commands/ShootCommands.cpp b/src/main/cpp/commands/ShootCommands.cpp index b238b31..b4fb3fe 100644 --- a/src/main/cpp/commands/ShootCommands.cpp +++ b/src/main/cpp/commands/ShootCommands.cpp @@ -3,30 +3,22 @@ // the WPILib BSD license file in the root directory of this project. #include "commands/ShootCommands.h" -#include "commands/IntakeEjectCommand.h" -<<<<<<< Updated upstream -#include - -======= -#include ->>>>>>> Stashed changes // Called when the command is initially scheduled. void ShootCommands::Initialize() { //Start the shooter motors and timer - frc2::RunCommand(IntakeEjectCommand(&intakeSubsystem)); shooterSubsystem->SetShooterMotorVoltagePercent(shooter::kShooterMotorVoltagePercent); finished = false; timer.Reset(); timer.Start(); - frc2::Command(IntakeEjectCommand()); + } // Called repeatedly when this Command is scheduled to run void ShootCommands::Execute() { - //When the timer has run for 5 seconds set finished to true to stop the motors + //When the timer has run for 5 seconds set finished to true to stop the motors if (timer.HasElapsed(5_s)) { finished = true; shooterSubsystem->StopShooter(); diff --git a/src/main/cpp/commands/ShootProcedureCommand.cpp b/src/main/cpp/commands/ShootProcedureCommand.cpp new file mode 100644 index 0000000..694f955 --- /dev/null +++ b/src/main/cpp/commands/ShootProcedureCommand.cpp @@ -0,0 +1,31 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "commands/ShootProcedureCommand.h" + +/* +ShootProcedureCommand::ShootProcedureCommand() { + // Use addRequirements() here to declare subsystem dependencies. +}*/ + +// Called when the command is initially scheduled. +void ShootProcedureCommand::Initialize() {} + +// Called repeatedly when this Command is scheduled to run +void ShootProcedureCommand::Execute() { + + frc2::SequentialCommandGroup{ + PIDPositionTransferArm(0_deg, &m_transferArmSubsystem), + frc2::ParallelCommandGroup{ShootCommands(&m_shooterSubsystem),IntakeEjectCommand(&m_intakeSubsystem)}, + }; + +} + +// Called once the command ends or is interrupted. +void ShootProcedureCommand::End(bool interrupted) {} + +// Returns true when the command should end. +bool ShootProcedureCommand::IsFinished() { + return false; +} diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index 49d40fb..6dac19c 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -9,6 +9,7 @@ #include #include "commands/AutonomousCommands.h" +#include "commands/ShootProcedureCommand.h" #include "subsystems/DriveSubsystem.h" #include "subsystems/IntakeSubsystem.h" #include "subsystems/TransferArmSubsystem.h" diff --git a/src/main/include/commands/ShootCommands.h b/src/main/include/commands/ShootCommands.h index 334dee1..56e3c21 100644 --- a/src/main/include/commands/ShootCommands.h +++ b/src/main/include/commands/ShootCommands.h @@ -8,10 +8,8 @@ #include #include #include "subsystems/ShooterSubsystem.h" -#include "subsystems/TransferArmSubsystem.h" -#include "subsystems/IntakeSubsystem.h" -/** +/* * An example command. * *

Note that this extends CommandHelper, rather extending Command @@ -35,7 +33,6 @@ class ShootCommands private: ShooterSubsystem *shooterSubsystem{nullptr}; - IntakeSubsystem *intakeSubsystem{nullptr}; frc::Timer timer{}; bool finished{false}; }; diff --git a/src/main/include/commands/ShootProcedureCommand.h b/src/main/include/commands/ShootProcedureCommand.h new file mode 100644 index 0000000..c87aabb --- /dev/null +++ b/src/main/include/commands/ShootProcedureCommand.h @@ -0,0 +1,46 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include +#include "commands/PositionTransferArmCommand.h" +#include "commands/ShootCommands.h" +#include "commands/IntakeEjectCommand.h" +#include "commands/PIDTransferArmCommand.h" +#include "subsystems/IntakeSubsystem.h" +#include "subsystems/ShooterSubsystem.h" +#include "subsystems/TransferArmSubsystem.h" + +/** + * An example command. + * + *

Note that this extends CommandHelper, rather extending Command + * directly; this is crucially important, or else the decorator functions in + * Command will *not* work! + */ +class ShootProcedureCommand + : public frc2::CommandHelper { + public: + explicit ShootProcedureCommand(IntakeSubsystem *m_intakeSubsystem, TransferArmSubsystem *m_transferArmSubsystem, ShooterSubsystem *m_shooterSubsystem ) + : m_intakeSubsystem{m_intakeSubsystem}, m_transferArmSubsystem{m_transferArmSubsystem}, m_shooterSubsystem{m_shooterSubsystem} + { + AddRequirements(m_intakeSubsystem); + AddRequirements(m_transferArmSubsystem); + AddRequirements(m_shooterSubsystem); + } + + void Initialize() override; + void Execute() override; + void End(bool interrupted) override; + bool IsFinished() override; + + private: + IntakeSubsystem *m_intakeSubsystem{nullptr}; + TransferArmSubsystem *m_transferArmSubsystem{nullptr}; + ShooterSubsystem *m_shooterSubsystem{nullptr}; +}; From 4983465590a066572f3614ee0a4f94569d60abbb Mon Sep 17 00:00:00 2001 From: Heatround <105463535+Heatround@users.noreply.github.com> Date: Tue, 27 Feb 2024 23:19:10 -0500 Subject: [PATCH 125/126] Shoot command calls multiple execution Configured the Y button to move arm to shoot position, then run in parallel the shootcommand and intakeejectcommand --- src/main/cpp/RobotContainer.cpp | 4 +- .../cpp/commands/ShootProcedureCommand.cpp | 31 ------------- src/main/include/RobotContainer.h | 1 - .../include/commands/ShootProcedureCommand.h | 46 ------------------- 4 files changed, 2 insertions(+), 80 deletions(-) delete mode 100644 src/main/cpp/commands/ShootProcedureCommand.cpp delete mode 100644 src/main/include/commands/ShootProcedureCommand.h diff --git a/src/main/cpp/RobotContainer.cpp b/src/main/cpp/RobotContainer.cpp index a44e840..be41d3b 100644 --- a/src/main/cpp/RobotContainer.cpp +++ b/src/main/cpp/RobotContainer.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "commands/IntakeCommand.h" #include "commands/ClimberLowerCommand.h" #include "commands/ClimberRaiseCommand.h" @@ -24,7 +25,6 @@ #include "commands/PositionTransferArmCommand.h" #include "commands/PIDTransferArmCommand.h" #include "commands/IntakeEjectCommand.h" -#include "commands/ShootProcedureCommand.h" #include #include @@ -273,7 +273,7 @@ void RobotContainer::ConfigureBindings() noexcept // Runs shoot command to move arm into postion, start up the shooting motors and eject the note //m_xboxOperate.Y().OnTrue(ShootCommands(&m_shooterSubsystem).ToPtr()); - m_xboxOperate.Y().OnTrue(ShootProcedureCommand().ToPtr()); + m_xboxOperate.Y().OnTrue(PIDPositionTransferArm(0_deg, &m_transferArmSubsystem).ToPtr().AndThen(ShootCommands(&m_shooterSubsystem).ToPtr()).AlongWith(IntakeEjectCommand(&m_intakeSubsystem).ToPtr())); m_xboxOperate.X().OnTrue(PIDPositionTransferArm(arm::kShooterToAmpAngle, &m_transferArmSubsystem).ToPtr()); // Example Only m_xboxOperate.LeftBumper().OnTrue(PIDPositionTransferArm(arm::kShooterToIntakeAngle, &m_transferArmSubsystem).ToPtr()); // Intake diff --git a/src/main/cpp/commands/ShootProcedureCommand.cpp b/src/main/cpp/commands/ShootProcedureCommand.cpp deleted file mode 100644 index 694f955..0000000 --- a/src/main/cpp/commands/ShootProcedureCommand.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#include "commands/ShootProcedureCommand.h" - -/* -ShootProcedureCommand::ShootProcedureCommand() { - // Use addRequirements() here to declare subsystem dependencies. -}*/ - -// Called when the command is initially scheduled. -void ShootProcedureCommand::Initialize() {} - -// Called repeatedly when this Command is scheduled to run -void ShootProcedureCommand::Execute() { - - frc2::SequentialCommandGroup{ - PIDPositionTransferArm(0_deg, &m_transferArmSubsystem), - frc2::ParallelCommandGroup{ShootCommands(&m_shooterSubsystem),IntakeEjectCommand(&m_intakeSubsystem)}, - }; - -} - -// Called once the command ends or is interrupted. -void ShootProcedureCommand::End(bool interrupted) {} - -// Returns true when the command should end. -bool ShootProcedureCommand::IsFinished() { - return false; -} diff --git a/src/main/include/RobotContainer.h b/src/main/include/RobotContainer.h index 6dac19c..49d40fb 100644 --- a/src/main/include/RobotContainer.h +++ b/src/main/include/RobotContainer.h @@ -9,7 +9,6 @@ #include #include "commands/AutonomousCommands.h" -#include "commands/ShootProcedureCommand.h" #include "subsystems/DriveSubsystem.h" #include "subsystems/IntakeSubsystem.h" #include "subsystems/TransferArmSubsystem.h" diff --git a/src/main/include/commands/ShootProcedureCommand.h b/src/main/include/commands/ShootProcedureCommand.h deleted file mode 100644 index c87aabb..0000000 --- a/src/main/include/commands/ShootProcedureCommand.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#pragma once - -#include -#include -#include -#include -#include "commands/PositionTransferArmCommand.h" -#include "commands/ShootCommands.h" -#include "commands/IntakeEjectCommand.h" -#include "commands/PIDTransferArmCommand.h" -#include "subsystems/IntakeSubsystem.h" -#include "subsystems/ShooterSubsystem.h" -#include "subsystems/TransferArmSubsystem.h" - -/** - * An example command. - * - *

Note that this extends CommandHelper, rather extending Command - * directly; this is crucially important, or else the decorator functions in - * Command will *not* work! - */ -class ShootProcedureCommand - : public frc2::CommandHelper { - public: - explicit ShootProcedureCommand(IntakeSubsystem *m_intakeSubsystem, TransferArmSubsystem *m_transferArmSubsystem, ShooterSubsystem *m_shooterSubsystem ) - : m_intakeSubsystem{m_intakeSubsystem}, m_transferArmSubsystem{m_transferArmSubsystem}, m_shooterSubsystem{m_shooterSubsystem} - { - AddRequirements(m_intakeSubsystem); - AddRequirements(m_transferArmSubsystem); - AddRequirements(m_shooterSubsystem); - } - - void Initialize() override; - void Execute() override; - void End(bool interrupted) override; - bool IsFinished() override; - - private: - IntakeSubsystem *m_intakeSubsystem{nullptr}; - TransferArmSubsystem *m_transferArmSubsystem{nullptr}; - ShooterSubsystem *m_shooterSubsystem{nullptr}; -}; From 9fa69d15411540bda2601785001717fc97125fdd Mon Sep 17 00:00:00 2001 From: Normalizy Zero Date: Wed, 28 Feb 2024 16:49:40 -0500 Subject: [PATCH 126/126] Fine tuning for shooter command. --- src/main/cpp/commands/IntakeEjectCommand.cpp | 4 ++-- src/main/include/Constants.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/cpp/commands/IntakeEjectCommand.cpp b/src/main/cpp/commands/IntakeEjectCommand.cpp index 231886d..1a57feb 100644 --- a/src/main/cpp/commands/IntakeEjectCommand.cpp +++ b/src/main/cpp/commands/IntakeEjectCommand.cpp @@ -21,12 +21,12 @@ void IntakeEjectCommand::Initialize() { // Called repeatedly when this Command is scheduled to run void IntakeEjectCommand::Execute() { - if (timer.HasElapsed(1_s)){ + if (timer.HasElapsed(2_s)){ intakeSubsystem->SetSpinMotorVoltagePercent(intake::kIntakeSpinMotorEjectVoltagePercent); } //Run the intake motors in reverse for 2 seconds then stop the intake - if (timer.HasElapsed(3_s)){ + if (timer.HasElapsed(4_s)){ finished = true; intakeSubsystem->StopIntake(); } diff --git a/src/main/include/Constants.h b/src/main/include/Constants.h index e5cd407..0b19191 100644 --- a/src/main/include/Constants.h +++ b/src/main/include/Constants.h @@ -137,7 +137,7 @@ namespace shooter constexpr int kRightShooterMotorCanID = 15; constexpr bool kRightShooterMotorIsInverted = true; - constexpr int kShooterMotorVoltagePercent = 1; + constexpr int kShooterMotorVoltagePercent = .80; } namespace intake @@ -152,7 +152,7 @@ namespace intake constexpr units::degree_t kIntakeArmPickup = 180.0_deg; constexpr units::degree_t kIntakeArmLoad = 0.0_deg; - constexpr double kIntakeSpinMotorVoltagePercent = .75; + constexpr double kIntakeSpinMotorVoltagePercent = .80; constexpr double kIntakeSpinMotorEjectVoltagePercent = -.90; }