From d2cee9786b8b3fbcdb291ce6eef9945faedde3f6 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Tue, 14 May 2024 14:05:35 +0000 Subject: [PATCH 01/24] Switch from Prow's values to those for Perspective --- constants.h | 10 ++-- guiwindow.cpp | 116 ++++++++++++++++++++++++--------------- guiwindow.ui | 148 +++++++++++++++++++++++++++++++------------------- 3 files changed, 169 insertions(+), 105 deletions(-) diff --git a/constants.h b/constants.h index bead385..c3200d9 100644 --- a/constants.h +++ b/constants.h @@ -112,10 +112,12 @@ typedef struct tinyUSBtable_t { } tinyUSBtable_s; typedef struct profilesTable_t { - uint16_t xScale; - uint16_t yScale; - uint16_t xCenter; - uint16_t yCenter; + uint16_t topOffset; + uint16_t bottomOffset; + uint16_t leftOffset; + uint16_t rightOffset; + uint16_t TLled; + uint16_t TRled; uint8_t irSensitivity; uint8_t runMode; bool layoutType; diff --git a/guiwindow.cpp b/guiwindow.cpp index e91a92d..9245d4c 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -78,10 +78,12 @@ QLabel *pinLabel[30]; QWidget *padding[30]; QRadioButton *selectedProfile[PROFILES_COUNT]; -QLabel *xScale[PROFILES_COUNT]; -QLabel *yScale[PROFILES_COUNT]; -QLabel *xCenter[PROFILES_COUNT]; -QLabel *yCenter[PROFILES_COUNT]; +QLabel *topOffset[PROFILES_COUNT]; +QLabel *bottomOffset[PROFILES_COUNT]; +QLabel *leftOffset[PROFILES_COUNT]; +QLabel *rightOffset[PROFILES_COUNT]; +QLabel *TLled[PROFILES_COUNT]; +QLabel *TRled[PROFILES_COUNT]; QComboBox *irSens[PROFILES_COUNT]; QComboBox *runMode[PROFILES_COUNT]; QCheckBox *layoutMode[PROFILES_COUNT]; @@ -181,18 +183,22 @@ guiWindow::guiWindow(QWidget *parent) connect(renameBtn[i], SIGNAL(clicked()), this, SLOT(renameBoxes_clicked())); selectedProfile[i] = new QRadioButton(QString("%1.").arg(i+1)); connect(selectedProfile[i], SIGNAL(toggled(bool)), this, SLOT(selectedProfile_isChecked(bool))); - xScale[i] = new QLabel("0"); - yScale[i] = new QLabel("0"); - xCenter[i] = new QLabel("0"); - yCenter[i] = new QLabel("0"); + topOffset[i] = new QLabel("0"); + bottomOffset[i] = new QLabel("0"); + leftOffset[i] = new QLabel("0"); + rightOffset[i] = new QLabel("0"); + TLled[i] = new QLabel("0"); + TRled[i] = new QLabel("0"); irSens[i] = new QComboBox(); runMode[i] = new QComboBox(); layoutMode[i] = new QCheckBox(); color[i] = new QPushButton(); - xScale[i]->setAlignment(Qt::AlignHCenter); - yScale[i]->setAlignment(Qt::AlignHCenter); - xCenter[i]->setAlignment(Qt::AlignHCenter); - yCenter[i]->setAlignment(Qt::AlignHCenter); + topOffset[i]->setAlignment(Qt::AlignHCenter); + bottomOffset[i]->setAlignment(Qt::AlignHCenter); + leftOffset[i]->setAlignment(Qt::AlignHCenter); + rightOffset[i]->setAlignment(Qt::AlignHCenter); + TLled[i]->setAlignment(Qt::AlignHCenter); + TRled[i]->setAlignment(Qt::AlignHCenter); irSens[i]->addItem("Default"); irSens[i]->addItem("Higher"); irSens[i]->addItem("Highest"); @@ -207,14 +213,16 @@ guiWindow::guiWindow(QWidget *parent) connect(color[i], SIGNAL(clicked()), this, SLOT(colorBoxes_clicked())); ui->profilesArea->addWidget(renameBtn[i], i+1, 0, 1, 1); ui->profilesArea->addWidget(selectedProfile[i], i+1, 1, 1, 1); - ui->profilesArea->addWidget(xScale[i], i+1, 2, 1, 1); - ui->profilesArea->addWidget(yScale[i], i+1, 4, 1, 1); - ui->profilesArea->addWidget(xCenter[i], i+1, 6, 1, 1); - ui->profilesArea->addWidget(yCenter[i], i+1, 8, 1, 1); - ui->profilesArea->addWidget(irSens[i], i+1, 10, 1, 1); - ui->profilesArea->addWidget(runMode[i], i+1, 12, 1, 1); - ui->profilesArea->addWidget(layoutMode[i], i+1, 14, 1, 1); - ui->profilesArea->addWidget(color[i], i+1, 16, 1, 1); + ui->profilesArea->addWidget(topOffset[i], i+1, 2, 1, 1); + ui->profilesArea->addWidget(bottomOffset[i], i+1, 4, 1, 1); + ui->profilesArea->addWidget(leftOffset[i], i+1, 6, 1, 1); + ui->profilesArea->addWidget(rightOffset[i], i+1, 8, 1, 1); + ui->profilesArea->addWidget(TLled[i], i+1, 10, 1, 1); + ui->profilesArea->addWidget(TRled[i], i+1, 12, 1, 1); + ui->profilesArea->addWidget(irSens[i], i+1, 14, 1, 1); + ui->profilesArea->addWidget(runMode[i], i+1, 16, 1, 1); + ui->profilesArea->addWidget(layoutMode[i], i+1, 18, 1, 1); + ui->profilesArea->addWidget(color[i], i+1, 20, 1, 1); } // Setup Test Mode screen colors @@ -353,21 +361,29 @@ void guiWindow::SerialLoad() serialPort.waitForBytesWritten(2000); serialPort.waitForReadyRead(2000); buffer = serialPort.readLine().trimmed(); - xScale[i]->setText(buffer); - profilesTable[i].xScale = buffer.toInt(); - profilesTable_orig[i].xScale = profilesTable[i].xScale; + topOffset[i]->setText(buffer); + profilesTable[i].topOffset = buffer.toInt(); + profilesTable_orig[i].topOffset = profilesTable[i].topOffset; buffer = serialPort.readLine().trimmed(); - yScale[i]->setText(buffer); - profilesTable[i].yScale = buffer.toInt(); - profilesTable_orig[i].yScale = profilesTable[i].yScale; + bottomOffset[i]->setText(buffer); + profilesTable[i].bottomOffset = buffer.toInt(); + profilesTable_orig[i].bottomOffset = profilesTable[i].bottomOffset; buffer = serialPort.readLine().trimmed(); - xCenter[i]->setText(buffer); - profilesTable[i].xCenter = buffer.toInt(); - profilesTable_orig[i].xCenter = profilesTable[i].xCenter; + leftOffset[i]->setText(buffer); + profilesTable[i].leftOffset = buffer.toInt(); + profilesTable_orig[i].leftOffset = profilesTable[i].leftOffset; buffer = serialPort.readLine().trimmed(); - yCenter[i]->setText(buffer); - profilesTable[i].yCenter = buffer.toInt(); - profilesTable_orig[i].yCenter = profilesTable[i].yCenter; + rightOffset[i]->setText(buffer); + profilesTable[i].rightOffset = buffer.toInt(); + profilesTable_orig[i].rightOffset = profilesTable[i].rightOffset; + buffer = serialPort.readLine().trimmed(); + TLled[i]->setText(buffer); + profilesTable[i].TLled = buffer.toFloat(); + profilesTable_orig[i].TLled = profilesTable[i].TLled; + buffer = serialPort.readLine().trimmed(); + TRled[i]->setText(buffer); + profilesTable[i].TRled = buffer.toFloat(); + profilesTable_orig[i].TRled = profilesTable[i].TRled; buffer = serialPort.readLine().trimmed(); profilesTable[i].irSensitivity = buffer.toInt(); profilesTable_orig[i].irSensitivity = profilesTable[i].irSensitivity; @@ -587,16 +603,22 @@ void guiWindow::DiffUpdate() if(profilesTable_orig[i].profName != profilesTable[i].profName) { settingsDiff++; } - if(profilesTable_orig[i].xScale != profilesTable[i].xScale) { + if(profilesTable_orig[i].topOffset != profilesTable[i].topOffset) { + settingsDiff++; + } + if(profilesTable_orig[i].bottomOffset != profilesTable[i].bottomOffset) { settingsDiff++; } - if(profilesTable_orig[i].yScale != profilesTable[i].yScale) { + if(profilesTable_orig[i].leftOffset != profilesTable[i].leftOffset) { settingsDiff++; } - if(profilesTable_orig[i].xCenter != profilesTable[i].xCenter) { + if(profilesTable_orig[i].rightOffset != profilesTable[i].rightOffset) { settingsDiff++; } - if(profilesTable_orig[i].yCenter != profilesTable[i].yCenter) { + if(profilesTable_orig[i].TLled != profilesTable[i].TLled) { + settingsDiff++; + } + if(profilesTable_orig[i].TRled != profilesTable[i].TRled) { settingsDiff++; } if(profilesTable_orig[i].irSensitivity != profilesTable[i].irSensitivity) { @@ -1725,17 +1747,23 @@ void guiWindow::serialPort_readyRead() } board.selectedProfile = selection; idleBuffer = serialPort.readLine(); - xScale[selection]->setText(idleBuffer.trimmed()); - profilesTable[selection].xScale = xScale[selection]->text().toInt(); + topOffset[selection]->setText(idleBuffer.trimmed()); + profilesTable[selection].topOffset = topOffset[selection]->text().toInt(); + idleBuffer = serialPort.readLine(); + bottomOffset[selection]->setText(idleBuffer.trimmed()); + profilesTable[selection].bottomOffset = bottomOffset[selection]->text().toInt(); + idleBuffer = serialPort.readLine(); + leftOffset[selection]->setText(idleBuffer.trimmed()); + profilesTable[selection].leftOffset = leftOffset[selection]->text().toInt(); idleBuffer = serialPort.readLine(); - yScale[selection]->setText(idleBuffer.trimmed()); - profilesTable[selection].yScale = yScale[selection]->text().toInt(); + rightOffset[selection]->setText(idleBuffer.trimmed()); + profilesTable[selection].rightOffset = rightOffset[selection]->text().toInt(); idleBuffer = serialPort.readLine(); - xCenter[selection]->setText(idleBuffer.trimmed()); - profilesTable[selection].xCenter = xCenter[selection]->text().toInt(); + TLled[selection]->setText(idleBuffer.trimmed()); + profilesTable[selection].TLled = TLled[selection]->text().toInt(); idleBuffer = serialPort.readLine(); - yCenter[selection]->setText(idleBuffer.trimmed()); - profilesTable[selection].yCenter = yCenter[selection]->text().toInt(); + TRled[selection]->setText(idleBuffer.trimmed()); + profilesTable[selection].TRled = TRled[selection]->text().toInt(); DiffUpdate(); } } diff --git a/guiwindow.ui b/guiwindow.ui index b8110ee..467147d 100644 --- a/guiwindow.ui +++ b/guiwindow.ui @@ -799,113 +799,131 @@ - - + + - yScale + - Qt::AlignBottom|Qt::AlignHCenter + Qt::AlignCenter - - + + + + Qt::Vertical + + + + + - xCenter + Right Qt::AlignBottom|Qt::AlignHCenter - - + + Qt::Vertical - - + + - Run Mode + Sensitivity Qt::AlignBottom|Qt::AlignHCenter - - + + - Layout + - - Qt::AlignCenter + + + + + + Qt::Vertical - + Qt::Vertical - - + + - yCenter + Bottom Qt::AlignBottom|Qt::AlignHCenter - - - - QFrame::Shape::NoFrame - - - QFrame::Shadow::Plain - - - 1 - + + - xScale + Run Mode Qt::AlignBottom|Qt::AlignHCenter - - + + Qt::Vertical - - + + - + TRled Qt::AlignCenter - - + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + 1 + - Color + Top - Qt::AlignCenter + Qt::AlignBottom|Qt::AlignHCenter + + + + + + + Qt::Vertical @@ -923,34 +941,50 @@ - - + + - Sensitivity + Color - Qt::AlignBottom|Qt::AlignHCenter + Qt::AlignCenter - - - - Qt::Vertical + + + + Left + + + Qt::AlignBottom|Qt::AlignHCenter - - - - Qt::Vertical + + + + TLled + + + Qt::AlignCenter - - + + - + Layout + + + Qt::AlignCenter + + + + + + + Qt::Vertical From 50c80b61a79d99d215e67fc0f35a5d68380e34e3 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Tue, 14 May 2024 21:35:11 +0000 Subject: [PATCH 02/24] SDA/SCL default fix for Pico oops --- constants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/constants.h b/constants.h index c3200d9..23a3bfd 100644 --- a/constants.h +++ b/constants.h @@ -141,7 +141,7 @@ const boardLayout_t rpipicoLayout[] = { {btnPedal, pinDigital}, {btnTrigger, pinDigital}, {solenoidPin, pinDigital}, {rumblePin, pinDigital}, {btnUnmapped, pinDigital}, {btnUnmapped, pinDigital}, - {camSCL, pinDigital}, {camSDA, pinDigital}, + {camSDA, pinDigital}, {camSCL, pinDigital}, {btnUnmapped, pinDigital}, {btnReserved, pinNothing}, // 23, 24, 25 {btnReserved, pinNothing}, {btnReserved, pinNothing}, // are unused/unexposed {btnUnmapped, pinAnalog}, {btnUnmapped, pinAnalog}, // ADC pins From 8317a2866ae51cc2a52185aa4dfc812d2ee94922 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Wed, 15 May 2024 15:48:49 +0000 Subject: [PATCH 03/24] SCL/SDA swapped in ItsyBitsy pins oops --- guiwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 9245d4c..600c792 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -979,8 +979,8 @@ void guiWindow::on_comPortSelector_currentIndexChanged(int index) PinsRight->addWidget(pinBoxes[7], 7, 1), PinsRight->addWidget(pinLabel[7], 7, 0); PinsRight->addWidget(pinBoxes[6], 8, 1), PinsRight->addWidget(pinLabel[6], 8, 0); PinsRight->addWidget(padding[11], 9, 1); // 5! - PinsRight->addWidget(pinBoxes[2], 10, 1), PinsRight->addWidget(pinLabel[2], 10, 0); - PinsRight->addWidget(pinBoxes[3], 11, 1), PinsRight->addWidget(pinLabel[3], 11, 0); + PinsRight->addWidget(pinBoxes[3], 10, 1), PinsRight->addWidget(pinLabel[3], 10, 0); + PinsRight->addWidget(pinBoxes[2], 11, 1), PinsRight->addWidget(pinLabel[2], 11, 0); PinsRight->addWidget(pinBoxes[0], 12, 1), PinsRight->addWidget(pinLabel[0], 12, 0); PinsRight->addWidget(pinBoxes[1], 13, 1), PinsRight->addWidget(pinLabel[1], 13, 0); PinsRight->addWidget(padding[12], 14, 1); // bottom padding From 63820f87100845e2a3b0d717ac193e3827ed9e49 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Thu, 16 May 2024 04:34:54 +0000 Subject: [PATCH 04/24] Updated iconography --- ico/openfire.ico | Bin 27179 -> 115399 bytes ico/openfire.png | Bin 11902 -> 13031 bytes ico/openfire.svg | 97 ++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/ico/openfire.ico b/ico/openfire.ico index eb8cc3df74e9acb01e5fc2abb6e2245662674091..9ed7f018f8480075963a04b63dbea023c40b1b14 100644 GIT binary patch literal 115399 zcmeDk2Rzl^_o1|mXd&5q??kC24G}7Bl-*X5q!J;@NVKI$3sGpE7u_V$>cpWjdK z|B3fWR#tW;y8ld_PFq_W+q!itcJJOj?EU-qn6IxdHe<#NTKTjqS6*Ho+qZ8Y*3{I5 zy?y%@D=8_#YHDh*XV0Et-@kvyl9H0JapT6($_JB^lfy1wzKm5@S7Ynets_4N2M1$! z?%W~AU$kfuR$5w0&J*CHhI!YnU07XR9X4~O(Ldi5Fi3y@{(VeSQxh9Ib}Xi*rbdP( zJw3f$dL<<#?BmCe*tTukFm=JRn1c91Ohe!*Iekk@3proF1F3W7#?)1$$oD{h>a_3- z>0iBig>Bigg&c4B^5tZjsj8}CUJj!$H!EK9drFuo?{ji;umcAUw8c?WR3y{c)6)~v zP?f@N9~gvL&K7L@uBs%3Ihpg3pA8HQ$n|GxYTEW4^7iub!ajfgOs-!YO$jX8lLO^5 z5K9XhLjKvz8G|V+D`Rym_o#AtMXrK3D4zumlf z6N4~y>eSBi$G>0tr!4&Wr|az5v#}#bj$pTM-zN8E$iKL_7>kdO$5yXijUoJhN<#rt zBgK8Yc+;j$!!j~5AiE9&^k+v$$98Gy<%z$ywY4Sp8ORv=w1&xd^R23%V7$VX6@jI^e|`B`oSEF2jD-vy}ilx*wD~Ger;xE zM$TVZQ3_M}XN9mJhAYZeA||KY<24EPPoeDQnW5v!`I$S}CO zyOZO{%gJC{t$47!umMVO-{F|Dx6b3wqyqt8~I~YrW|AY_`5z+P@PebbpV<@x%2*5`JKMMK47y>-g522MGt#^>erAwFE#&^hrRsbFb_*7bX{PV8Ec^&34;O$||#QWgg zyLYh@Cr)61Cq53)$-hxrTKX!TZ$H6AZ~X5d{bf{VW6*ctAv(kZ@_;!9<}`YIAVoQl z*A?J>$b3;R{sA8*gnpCI57vR?fEIw%d>Fh(b-f7HX90q(nF7|I>XG^z@CNY1#|7%| zbe^G%d{o{7ylgu0IxQdi8-xyc+79zTFw94|Kj=#Nz;obZKJ;HAJP?g?^3gj&bq?zf z2L}f-e*tSa>Hx67x(~*LDO09&h&Kfsgvy3>1I%kg1Mp!nF)k>e?et--WLzrqy7S0J9+5UmNGqcuWMyYgW?fVPVN5)u;H zl>u!B@PPolEWPqUc7%uN(}BnSu`ZXDQKos1*FTZ*_x$n2 zzHIxhEb2n8bC6#tNX|v~?=dx@AF?h+%7?Wu9*m4|nVt-JjIy$_!r%wxLIC~=hf7&( z3z`1PV$P`idFcKF25TJnj+cMo!Ub}AJRe$Dkd5JC|Ni~t_`sKfY?3hwgPK~6`jeCYFd*&SVbqI}>T@O}&O5eR8%X>H{%Hxa;+1BPIxQ`@g8 z0IoT6=Cq~7^PqR7q(8{8As)3n3FHhAUcY|bR{m@~5%Ssq)*T(yMQ7ifoSevc!ymnI z5ah3TfHfJOXYQeaSot{?Oj})|?H&G&=oQ*11Q?I$tp_Gdn9w%@UO zOM_5c{2g3jEQJ7TGiWoE->C0l?tro(fP5EZ#CRO<6{E1~3rtuIT3@H{9ZX(}daoEs zzFzQQN9$51?8T{m*n*k=uDwC7faeF}8=hB3S6pTQFn|sN)^>RO852dZ$ZbQhm#10U z)~&F9%}49l-K$5m$?73Ktg-NQFVKC`q)Bc0;Q7(I!uWjS#tkT*9Do;rwm}&eWZ@IW zN?=nbh_x#dkF#vqGP3RkG5|i!0%t>bcn7zgsKceYXDe}FI~En%zJ~}0(eTw0Q@n?r9qBLL#IKn4w}2V{sLL) z*RNm6AW-U#sBoiKF8TdGYu+V{q@j$9&woVtAIfz2J*>N74UC=_|5FcyXo07PJ_fu7 ztikbj1g_*d$IEsH@xU(%P&UXZKv!gmer<9JG1_tp6orInriX9@z0pSkxH3{NJsB5Z z10504&i@|kKcKr2Asq2$YZ3U;s?02>eL0Q?o$v{2K(v+-x<%$a1~6kmf-<_mm1E!(HA z;sSgK(3dhm{ee7z9#7aA{5UK?E070L2avVV)BE9RiCh6buz8>iAgi*mvFWgn6NMjO zKw~&|@ZiCp+7#GR&|8xefgR#dk1@5iwe4)m=;gx*{CFN18DNb? zbluby_@PYdeh+0rU%_!CdIdc%$crcg*cL!~IcXiTjRM$O;Q4`FhrHCk^C~s`Fz(Qj zA5z=5L4F|1r3}}vUvDc1FR!y}KtKRx78v9K?NnY$AIY6xlVzW3!iUKDDoR*kT6`64 z@=(Z|8vb^fQ9MBVq=p4#f7IheXZ?*b574>6xQGWz`3}g4$1RkJYgWiKO4!wJ7*$5-F!}FwuA7mZ)H=e@mod-QxWk&?*hs8U+}Ic;DXYhmWfHF|`FGUH9wzfW2Aeo$>il}GXJ2iw?Pr(m!7s%)E0J`GN;D_;@G5`%J z(hHf9~YudlDiK_OppTe#=XpSQt9kC%l6o!K{0 zFJp9GJ)TxY)@dDkAK6j1;<#)I+mo}>_2_OUh~x`4czA&E zhpSqkxQ-8C0i6W3UYk}rcn9+~ zwOtqWx(oKZ6dwQ44?yp^wUfO()MeSv8-wk48b+6PAcvt09oeT)=MB#w6QK+szocGE z>mwcI^QeApbcpljAnPq@v>fqs9?0_i)@J}E|qfQTY_0S*iI>qg->0PVhnXqH7T;#cgIxkpvQI6lh zmvzV95Vw!O@qqOJz4gpQWcLpC2w;~%3--LD1F(I_UIf}5HQeyLV#SI!{WM-4$W&;> zCHfBE%ZBF%SCDCd9KWMHV1M0AWH$l(1>g$L^x}hD0QOE$%My2l`RV zQUDK-kAZC*tu#d6L3+rCI)JSa^>{%vPg?O{UmoyI)B)yqumhyG$A(s3@DB0=n;BYi zcxbbf_NesoVDvrc)~V%*I6lzlKyD1S7%=}qy@Cu3>Ja4au$F~>1NH|Xo2BfdlzBjU zXitp7Ml?R~R4_NdxX_aUY}70*Eqe`aYW$#2z_?7^=NQEa_E&*ivNOJzy8NDchPDB+ zO==l?M|c3vU~A**>iUyxhEcdsTk8@1DU=Jk2$+9BA4hGM1Gb|e3xGD#6MBh%@cfA2 zzs4xeMB-Dgf4d@oBT&v21phPw@fhw`eE|P=usQk12EwhUaR8f7T&4t9eBWpfl>@d9 zuK!w~koWpb`^Ofdu7}ECm}UgZ1=y~D%?S>B2Hdb9KNoQQWfn_N`2R<62Q*PVXTTNJ zJJ@MWLO*x(YqQfrFKF#2Ls7^9(NGNi_MzWX^rO3{9ewMKHq@JW0xp14FyebeaeOJS zsO_#p-@o-0Zs1b@WT2G4J}UbwDrXHP?r5xc?4z>VXpHZ5qU0wK9BtqG0G`!Q2Z+BC zL1jUm^kvy?d%b=k{(!R>YWE?1jXRY08_*x<9gKd(eT6^Bj$khd`0b#SALDHR_CCUQ zh}TCj!I;>q<2>x!0bMrOW`T@`NPu_{AMC^-eJ|EUuiy@AK=30(EvqGhJFPg759HS! zd08*kVH>&! zuGRzCo`63vA_3&gAWwibH^@4v1IP|2?SrVlQ$K@$Iy!!}+S+7S@gkBA_SaNaR#Jo2 z?it2;*hdEbb|`H@X|)xc{{a~t$d^IZNlT7MSpe8$fn1LWygeBQ_}7m5IoK$H{D_`z zt0Vj=;e;^{d?a*aM-Fl)*w;mwt~c+&e+WH0YM>X)Tb;?0h~kgq0c{8NMbax5bXg$d z!sGYW6?8|m`YyCdkhOyEDI#Svia(ww*fCJrfl>Aas2gaPc-)@8f-ZpC?gB3#>@MiF zby{$A6@SPJY{}@2xxhPhwr7?WJe|D*y#*~j3E&U%Zbs*#?%)rwg6#ygJuOZPu+^bw zzup<1^u9x%rWIgpXB2lRqr3P+9#AgK5wrs3esFp?5q%FjB3k+k@Wt6xeO6E45AcKT z>+$2qKUD#Yhv4IjD2^S)qorE|_;DR4_~cU*Uxxg~f-P)&-(6srtt7q@*`&7jtwRfc z*q;o1B)uO!SwFx*Ykma!bc7d?Z%~J{!oT`&SxiGPq0P=yUGQI9B1Lf<^!@|+o7U*E zfBYE!kh9RjpH?}P?>e$m!u=*v#)o^*-4nr+UL5cZNgcq?0M4s{uN&NM9lydp0?1E8 zFsa=)+z$+V>uLNUPg-*V_%o%4TW9ZK4<0RkgHoR=D?JwNF?iR8r`G5<$X51;{}VN# zh_?4oH_D=JZLstP{Y-{H^r6@1abJ~$a^`F7uE;ZFpHo{9tZZa9oY@n>`l#q+0i z{S)!W*E_WEVf5Xfh(FkoF^VU>_`ehW3l}bIXNLeb3Dmw>q2JM4f396P8r!^#m;5u- z6(LkdwCo=!Z6UgIPrXMPUCG$b zhoKJO&y$fd!S<9o(32_ouNl#1SN|lUKUy2pU!O1nLs#R%-Ztt0`cp>o1D`w8f!;o^ zMQ9%f<-P*Svu_wFv!~+HS_3kwBLcY|weAVl(t3J7_Qq1e3iO6O6nGtg-$}~veR&VE zacWs5tlL0N$S54RA9~7mKx?lLt<)hg zF|jRwyqvCFLGMe;UJu4Xdh$Rh2hOZ3L*pQh1Fh@pXcn>`Vp`wucjr7gY5MGiGU(|5 z0bWM;u60y5l)mOE>lt(*9nG!aUmARIzl&w=upV$+DX%}y5ftc|d}{Ti ztOVeJJ*!{`Mr#~~vcaDbz4@p!e7f=-{KUf^O?m;=93XS1JSVm*Pr6INdva()cYx^VS{HlI~Vj1um=J10o2Cahj8U*A1G^HiFGx>MC+n=hu8n%_N-q!K?LZzw?E|el^z2SrunVMT zmq!nWuD%C34A|aw2j1RD3-c1JHR;WfUBwqq2lWg3ZCdN^-oU4;c|twFJP7s!wAM~I zK18mt{!UFzCHp7o4W0(!*y(wJU9AU52R1a|-wa**H}hbq9|E5YdwQt-@j+YYO}>o|j!%)zNhqCf4B^FS9<+GKp0Iym5RQFPQc@Bq zO`q+1#o_x6@c)MPK8nL0Pz2LP9In2){%ZVTzcb*D2ZZ+q^y{;B0Qdy8p3sryg%VpuTKCqz#~WYZ2XB|EshI8zO4{l-4t9|I~H(r$2)aGWU+! z0K(bjze;x;FIx8BwB%L)LHd*1FY4b;wAuiwpBV_}7QCMRtJnW``XjowB03vVwgL3a zhTgybuhN_nufOyspMO>%CML##w*f?dPs%#_FW>*Ae;ZHh4)jMlogs+c|2J#Azw~dT z|0j^`Ab!B)zue!car;aEHu{5H-U!uCICUNUm!JRA|6lqeI)|bDpZi~>H`wrk?HHVG z12&c5(}$MbKdtp2*m!||1+dG2Jbr-=yD#JoxIDiv%IeiJz=i~DICt#Wfx+H@SFc{t zB5Swafn7M{4f|(|jEs847PpW4;wx0%AFIm&nuDKy*wabG2B%xZr;!-!M4>&v9u2UK z>SNmVM0tNC?ZFlr&N8PyvzP{n`s@|J0(P53{UrW@ZKWsLTbJ{Iz7G8i_CPRVliOzv zfTAEc^Yc%z=lm1eAlSshxkE(xb_z25^4qs>-(uif;CHv{{axFEm6a7;-=x2sxC{UQ z`y{|e1zK-+dHnlbY1A8V0dGxtjxYmN{aZ%`-z$usOWGUt`@7}|!GPO^do**3TMFF3Y zur7oBTj2YWsGI=&t$<%h*vkiUpWX;C7QlG-|H>D@9?Q76xE`xo*pmk53&DP3*vm{y zCPQ=`K}{3z5eR$VKskZzycPgxfDH-J6O-3h?6z@B@` zeE%!=phpFN9$gLKH=?UP|M5EPP5R?yfKG~@FBc-X!#PZh`ZdSP{#~!27Xd#uT?rsZ zg>%+=0=K@PKfnp|2j~F0iZiT-81?Ph6ZO*@dB9nIUFDPDOhNGd*;8=!75#B|Crp?? zJ{Peo?HPz28ILtkH1-lvGZ5a(PPk`M{Pr?Fu^)>x*_(2cU6&VNcgn#O~0C;0s zzIHon3m~KEEjjtGqCd0);C;IyO9EM8XMPCrcJ@16>G^Q%i2nKc`526uy@kiGq(9&U zHi_T|h|zHE+OaSH?v+yQnworl;}-evg(P zoQ~RjW@aW`-_tz>7v%Fh&>!G{b55a8GZH}N^(XLyjLJZuFZ7ka!{3Ge&;~$10X|h3 z31CaoS9R5ueh24uGZMg7uD8d0N<4li`U6}r7cj~XboJav%6jOHd*E#tk?n%-TOvA} zo`m&xqd(vVYh;jd5e*;%{~hg&K`u`;z*-p2wdhIO63Od#q(8t7dLKsQ1K^vU2)=z4 zXXebAo%(d9Y#Ux)UcUtX-9`U8xx--T!Z-O8^V1E4R1T!Kh|wJR-~JVlAc zbm@q<19?eSyXjR8N)68{AHLmObke!$k&wq4yY+JCzfA8cfa1_uWR zns6$MIdw=&ps$j+UAwfZ!uvW+BPTh%T^hjiPoY1YgF!Ta+`Kz9>}pz&+tk+95&{$K zu<4bfC}G_xEfpl&pM#?!>d{G>O4R=~e=74&p+D4BT3Q;Rx&_{=tM+j8XhP&Y*tZZ3 zHa0dt(W0#kX=O}{{{>xoj^lrT0guoYAJvV#lpc!zfo>XDKTyWU@BbwFgT9z(09`AN zV_#ii+#wR+TpwENJxVwg#bFG1|1&*Dzr_?JXv>LIM7N{(RX?Q}$NvnIll=KSV<_)W zqCdb3XYCLPU{^p{pS^vrp`k%oUIlw;h`=K+VT#u935c&RA&&Gd`MdoYLuumPfXo;*D|$x3Ns zisCjHtO-FMKrbHUdsqvAd;sLZv}8V%asNd6Lw_S`H`*8bMu_UEV2+@y`;PAwFg4)= znD*$3Hhvytc&Z|MP#&~(9H1Lg7d+Q)4FG%~$dl-m^(WFFa3QK&2YDPl+i0}ZY+Z-61bQlpFKb7C72$wR(|~ONbszXs=?{3kdGm%y6Tra!ni}t( zdWO7-1USE)Xqif4j-ApHnnueAP@K2`y_3PJ(R6S;4VbQLxDC@1e_m-BH zgkXSkv5A%?Cp8t(@@F~jzw^NRpVk73;>$avzZQQHD*tD`@lU4z@#Dt{)xDdW8{xXg zX$1br^!4>S{eizCB7sp^6xeJ0OYiD-_o^bnH1Lr{HeYG&;!&no65Bu{&7aHv0i8gO zP9%W8tBz<%i$7pQ&P)V`EnBwGtbe?{QD0?clrVMS|I!q#1;MtDTK9*i1^Ix6 zK;pmg5uX6|V4xSm<4|9T_J1Nc{;6?b&jEG4^fdju>aT$;%Mn|P(>(@KiTI#BYpL}7YFwE1nW<~jJ<<@%XY*FZaozj>?w$ZJ$t}r=I8ygJ@Ibdym>@G zAh$n8%8T4K6st`j{q*~Ik%VceNcI{&jKKJ-<3>kE6RP{(v;hN~b|L}%nJ`jc?zTKX z(H?IP%ZvpW%8QY5|D?D?&&29&*$a{V>EQd1k^K*5(}n5M|L`X6|9kp_JsQz^@9ndt z;2bj|fsr+!yqpYHevXA^8z_qCkIBjQ&V2T#^nao@*TDPrHck-{5k$z3fxQAFu*7+D z(4;@i1D3M|8On>1a{q+5;5UOvfOG468=nmuHV^@Ye7+AOu&kWVPnZ4|eAs&(FGgVP z>$pVyE79_=($laKwMB(}>EPRrX!(lr(pcG9+H(M`1B|AK5YCHenSVk&u%jjFSFyMJ zrc>7wQCn0-R_bJHCIlKlQZ#{f9DkVt_2Rzz*Csp-k+Gtspu_)udc@5x%?SYmDl z53Sy>q&5W!yrqvMmwRFMvOL0A*+v>(;yc;oovSD1@He-*Qn1GNF^9)kyDIYa{Z zhv_T6AIBNL64glp9*m9wP=<%ys1C;fmR(lKytxr0YiSOXS}}g>)RM+ zY^xPdpNs{dj|y}Bnf5p=-Dv~%_V$G9GbSd6;rhhs)>GF>lO|!%w~2^lp@py zp#Jm224D_m{Jo|#9IqjPfh-cIHNC5|1s}t-0p0DZSFZ?FCG>q-zVv&Fu8icv$XSmd z%Z4_?Nd6EP)&xZA9j6D_lX*Fe!a%0TXj!!4k5iMxyjP6GO3%>NQQ+mmHDtr^F3Bih z?=H~*e&J}r`77Q5A3#(d1o+*#bEiu@E3C2hI}Pi!?@`hM)(N-v4?;8Kg!W-~0FP?@~QMxsHoRW3SKAml5Lhpmzm2m7MNJwl~c?I-t$&6l_r5#da-U zkWJ5@A=D+D;M@^#x_ZxK4yGq}K*| z>OG7v!0&0I`2nvBMy}u^5zaj(8o&-5&JSgzjJ}Es`e!(wj%fJy?HdNZmb(fctiRVT z;z#|rKSOguPtpGVkpbA^SzYV%IL<_MsZgiTXS#B(5MEYaTtVN%NT{!`$H1<KiJO$+-=ii?XGK?|#?sUe?% z*wr%ebig03Kx@A%d)Tn54(tdzqAk5|pp%T=$%%nIdQa#df&XD7K%I7H?+#u^eQ`A~ zFu*|X$4CI4wmWkHHD0h@+l1EOK(l8jyEZO73};31-J#^Z4r>DHGP?R48XC$-)sX8F z>a?rn{fcy;lOnpF!7Bs!f!^9X3j8A25xXJ1VetCl*l{;5>@>19fxTxSV+Px^Ajje4 zHnRxL>Dn4y`d#FklsI+e9`;ug_2Eug4_B^S>5`nR zD=_v}8d%@LnZI2LaK0Rzar(RY<0e{HfIG;>iJp;3Sx2yk`*&UE6U85or>?F}K2Mnv zKSu6BUTkD!^hb?@;47Eto_*@Nc=+%k!}cVT0YAaWo|0ct7r>_z zJ-d;b?(hur-nenZ&OZ4S_;sgD7zesK7vOaTy5`8p$X@kx-W43-9Q3%jIELh(I8OLA zD=Q1@?ftu5!L29Lz?|4so)E7mxWZZyY>+{3M$4yhPr^(uFPNWKu3XveHVycIz2ARj zzNc4T_kW=D99H z^78&TIYwW%7nrZXzKK!&VXqRdo0(TsRFLPFU$su@YZ~@Md7v+WagxY+bKN9QuO$aw z5pV#xM^Dtr|6?AY0|FbVKdBu6-w1r}`iZ3gtZV6QNL{`^02jBoBPJ$_|+*i#Pv=s@1nSNtN#oq>-B z+s(Oi=l+Q_$6*HAzxkE)>&db~rVQ&xFE1|)d?A5MxVr&lldz}Z+O=zB*$~)qC@U-D z_4lV=%aN@7ZcozScgqWOY0xHMz6cBq#K1=yk+5*#!rxxc+yix;gYpRPfqed$en9m6 zkKWcH{U&uDhU5{ZzX>dT(uV%hznzbw*@%}Og6LoW|I!Bj(!Y)VpV8bnn1lkNz1Ls* zx91A}>*K$)w6q7%9)hH#Bs-%2hyRB*@VEcVfPGAbn3x#H59>wkG1_&@Id(C=Mn(EO)Yi2g7a zRH9#>tjYSa9IyujUjv=}`VywzE(_7U0{sksB6J5YL_|b55nMab@Be0P*xPjY1^L$_ zx_eUddL5Acr%xasKzxA-f*s_9chC>?1%I^82*LlK8`u7!3DD*a`h^3{@b&#qM5TCv zQQV_jtfl?)#HRMog0=0RIZN9=56Ec$%;MhunTh(j{pY_=e{0}x4g9Tvzcui;2L3;4 zz`Z@1Kh2LB?Z2~>wtpT#^~^~g$L~wYF5d)8`j)Br99G4O!Zxf z>b|4z%F7Ct*EFZx4{biYA@9?+4MKa2PCq<6>(rX@c?0|+-g6!wMA}3OAU!(y%t~p6 zkPXR?={4!fO~>aWOol7gjo&UNEG+D6J%!&_h>3|Q%uwRtVv_j)V~d*Po0gA6RK^AD z=I*!5VEy_}GYmWrttO4p+GzZt;TuV5-TI*Kgh=HPn{f(BHs( z(1xTHwkBT6&w|;0W;2Hp58KII5l!1nO&`=$m@klL$^Wp?AEg)~6yhAn%XaH!QTFGS zoha0wGdH$@udP!}p6~J*=E^o&SW}ayrsnncoG%|=a+(Zx*tENDl>o}j85t#PoTsxy z9HMWSYSMD%T)5o=&e3Dn=&f6)bioDfMN&F6D>3<|i%x`;-v+d0Nhi9>)6Hi4%WF=R zae5{#JXK|ON~`(@lwbL}DAu{Z%;sC+%?Hq(bhhI@`$q3f%Y!$EhZ!{%e%7g-MJ|JB zR@4g?R?lT9Kch!0k`lrj8fUA!KaO3T{^gzaM9<9lt#4N*74MkcygQxO-RShnyN=Io z<2P+%jXagX)TALKbi82S_PVhwL#NiQjE~YmuT7p-*Dp}Gp4MvatN~BZ2I=?dUo~zc0M)t%a_NZ zjcL0sk9Bw;W^H35EGs+Y(4j+DwlpdX9z59oC95~@I{qiY4_73OB{e-5dE*Q}Ql<3k z*H1`FY9Jz@{nLx$d>X5!FCK|b<_Zc9_IUJY?#YuUOTRT#PM<#A@|I=ml6Q%Vs<)Dy z9AuZZj2Sg*6lz1q=bSbg$id+q7pLfwc2Uge@f25AEpBe^pr9c4&+qa?g@u_sg!1>_ zY#J6&UgNkZ){N8aLbgn)CWlrS#{}2=^)x}# zsbV;4MGHJkuDu@@YnJ=Hzf{CrnYzI3Ei4nZe9=RcDf{qY#^T^r2+y+j1(Cb_{cnDF zc>2VXN(X~;PD(7tN!gC}4^Ocp;C)I`w+u6UwmSNKgKO5UTgzXjZPj$H;<=NXi=vKD zVhQ5nmK8GJJ*PiEra*K|K~MyjPQTQKp>Bm09))7i2e-xpmF z^=9MsI5uI6?%BDE1QJI&mfvEpdVY1-mM>M4w{6o|Jx-m&mc)I+^lWYM%D82B4=UT2 zq>NiVPGk1Mg}mFoz8STuIC0SYIP)m^)3-9%+?9}*5FV|KQu_XUUak!l1^efe51q;- zIr~_aH~Z*ypI*)OI(MnO>fmeMqT!~XGe%hN?H}3n};yB(YKVi zOOngV%w!VP-;;4r*~<8|(U{`(E5r{dTHHD}FM8eU+g!H&3U~6lAF3T6IN6hR*_{yS zq!raAhp)QSiKMlBGPtzfnEfEr!O7F75462^*b9-QVE=WO!P5fxGv9@84@gW@D>jW0 zuq72{S(~CAm33i;TrOgJZy3e$KaZO#PwSG?GntQ<#&s5OP_=6L->(rE_ z8Z7pkw@lm4z()UsvC#PO{Le0}w&L8*Ui#*M#Vu6rLr0F(M=>(>NwYJbJQ+1)l!D%* zNu<@%Y=eNRZ|{fgj){>gHa&BLt#lA8r@)?Fy9O3Ld-ia@Zpf!k>tDZnC#tB(xq0(u zB|d*C85xfpYY(A;Xv7Ts7Grw$c!7QC`}YG;%SL{TG71Zm^xsI_&dt4Xblis8XKT|7 z_Un$YC2fE8`t{DupWmKXYw=7z;OL=4zR~mJMAX$sTweQfTfMBR>M(Q|#>nhVn>P6z zI4~+DB_;p(WEQnmMMJl3+g97q;Dt>63~gkY1=h$0?AenQEN)<_#ABa(f?1U`{^-!@^!AJFH3RkGDE#XJq2hAP>2M(O{ z`R%>c%O3?DJh%(Be^F6U&r8Ffo^4>wzr5DM?c0abW)olM-Me?wcbMe5sv_m1+M75w z)jU5Tf7TR@AO$C;4A@vz#Dyj#`@(afg#p}XoKV{Eb{4<3^PWq?7oJUGJ#H+et}42~VwZO; z)=9D$^@yQ-e17rqO7=;Mir14!XrJ;yH3x2FK4)*RH)N3w303}r1Y7o$)KmupCeriN zEu+wut)kQ|oBXlK(K6_WU>=QCJi1e+oIRj!U}$)A=b&LL>R(&ND!YHFeg?hFY;R2Y zr#HDpR~$vRwKmM>@z+Ofof{E(51;yA)al$45>(a4a0-mi$nqVw_u#?l&HJb8#wxiR zg=v*V$EA7bN1ixW{vgU=!PWKR0fG~an)es6LDyHY9(ZdH?-cp1uFIAQOBfy9g$B)s zX=Ad^*0JU%t(-5n_|l$=J)e_SKB;YZVqt}SZ%wLcJvN-rpA|a#702>3dj{6KPB=B| zkjF4dqx`fHdS?q*^Aqf5%PqXvPbSoFX7h+?CfmP#kUU~>Jz*r89F!bi>9LL0nDxa_ z-!)a>;VHu#AJZ|TW{shdxA-Q#yB~I8MWKvLt^unj)5Mlq7PIq9d?O-6iZ4g#KZseV zdiU6b(P7$~_9rd%%ygHYbLtV#urqsE^Uuzaj4@9=d`IpRYJtjYOU9o(efnVjgy7Do_ng_Clx2H^l_?uoTF1V-`Lpw=I&8ePtGr`nqXmznh_Tl zmvH6e^Qh3BK0avn*x}qLAED3d9v&_zA}VTQQK9WxF9n@^OJhwzTK}ZOXhe8b9yrna z$dMy*)##YtWv_4Vy|VeUMD_V2hW^SVbeQL=7uV)JHq=2!fG$W{F+yWiQHI3Vn4O3( zSzdPC3!1B3wT-p4W!5e(m)cS$l+Nq=!-tY{6?+t_W#rHkll`CCR_Oexu&BY2{v{%!5*lS0>orLtS&-w|a--7w1Q6Oc(&| zA5~@U`eVp9yC9mCZ$3@1bJ+AuL@$)j;})+()R!Qp69716)%`s1NEiiDnHjA@Zn@rI5tV0eV^(eIpY};|7D^9WwpcsPeVR;Pfr%- zs-hjI43Eq#dF(Z0l+xx;)<_YSU-KoQ%;kOR;S(npE)G#+*PW4YN$J|1Iq{EbGFRKL zS?Y1)-tKkRoLSHDR~pUJK<#|ivGEHZXdwR13k?h+z6+vmNw8ZYn)4`Gy1%YII$_Jm zeG-Nj?`)sGcmDCBIF`Z1^|~i�PTJ2aaQLf6=;8Q#{N)H)?_ANpVs{gM1)|pbo#F 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 diff --git a/ico/openfire.png b/ico/openfire.png index e1c6c5ee6369f361116caab4f49600af2ee80b00..9438322ebe3800c9dedf7c77314a99686cd03ea5 100644 GIT binary patch literal 13031 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 G?Ee5LS!;a& literal 11902 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 - - - + + + + + + + + + + + + + + From ffdeca13eeff6177cdf1455c0ca3a327f461fa9a Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Fri, 17 May 2024 20:27:19 +0000 Subject: [PATCH 05/24] Updated tooltips with default values --- guiwindow.ui | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/guiwindow.ui b/guiwindow.ui index 467147d..16bcae8 100644 --- a/guiwindow.ui +++ b/guiwindow.ui @@ -510,7 +510,7 @@ - <html><head/><body><p>The intensity of rumble force feedback events - scales from 0 (disabled) to a maximum of 255.</p></body></html> + <html><head/><body><p>The intensity of rumble force feedback events - scales from 0 (disabled) to a maximum of 255.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 255 (100%)</span></p></body></html> true @@ -529,7 +529,7 @@ - <html><head/><body><p>The length of rumble motor activation when pulling the trigger offscreen, in ms.</p></body></html> + <html><head/><body><p>The length of rumble motor activation when pulling the trigger offscreen, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 150 (ms)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -545,7 +545,7 @@ - <html><head/><body><p>When <span style=" font-style:italic;">Hold To Pause</span> mode is enabled, how long should the buttons be held before Pause Mode activates?</p></body></html> + <html><head/><body><p>When <span style=" font-style:italic;">Hold To Pause</span> mode is enabled, how long (in milliseconds) should the buttons be held before Pause Mode activates?</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 2500 (ms)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -561,7 +561,7 @@ - <html><head/><body><p>How many LEDs are in the NeoPixel chain.</p></body></html> + <html><head/><body><p>How many LEDs are in the NeoPixel chain.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 1</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -580,7 +580,7 @@ - <html><head/><body><p>The time between solenoid activations when the trigger is held.</p></body></html> + <html><head/><body><p>The time between solenoid activations when the trigger is held, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 45 (ms)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -593,7 +593,7 @@ - <html><head/><body><p>The time between solenoid activations when the trigger is held in <span style=" font-style:italic;">Autofire</span> mode.</p></body></html> + <html><head/><body><p>The time between solenoid activations when the trigger is held in <span style=" font-style:italic;">Autofire</span> mode, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 30 (ms)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -606,7 +606,7 @@ - <html><head/><body><p>The time it takes to hold the trigger after a <span style=" font-style:italic;">single shot</span> solenoid activation before transitioning to a sustained fire feedback.</p></body></html> + <html><head/><body><p>The time it takes to hold the trigger after a <span style=" font-style:italic;">single shot</span> solenoid activation before transitioning to a sustained fire feedback, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 500 (ms)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -622,7 +622,7 @@ - <html><head/><body><p>The multiplier to be applied for the interval between solenoid activations when <span style=" font-style:italic;">Autofire</span> is enabled.</p></body></html> + <html><head/><body><p>The multiplier to be applied for the interval between solenoid activations when <span style=" font-style:italic;">Autofire</span> is enabled.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 3(x)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus From a9c6a92bf42d874f07e4fedfecffe534bfee50ce Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Sun, 19 May 2024 04:04:20 +0000 Subject: [PATCH 06/24] Housecleaning: Wait for final response before reading --- guiwindow.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 600c792..36827cd 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -773,15 +773,19 @@ void guiWindow::on_confirmButton_clicked() if(buffer.contains("OK:") || buffer.contains("NOENT:")) { statusProgressBar->setValue(statusProgressBar->value() + 1); } else if(i == serialQueue.length() - 1 && buffer.contains("Saving preferences...")) { - buffer = serialPort.readLine(); - if(buffer.contains("Settings saved to")) { - success = true; - // because there's probably some leftover bytes that might congest things: - while(!serialPort.atEnd()) { - serialPort.readLine(); + if(serialPort.waitForReadyRead(2000)) { + buffer = serialPort.readLine(); + if(buffer.contains("Settings saved to")) { + success = true; + // because there's probably some leftover bytes that might congest things: + while(!serialPort.atEnd()) { + serialPort.readLine(); + } + } else { + qDebug() << "Sent save command, but didn't save successfully! What the fuck happened???"; } } else { - qDebug() << "Sent save command, but didn't save successfully! What the fuck happened???"; + qDebug() << "Sent save command, but didn't receive a response!?!? What the heck Seong?"; } } else { success = false; @@ -1760,10 +1764,10 @@ void guiWindow::serialPort_readyRead() profilesTable[selection].rightOffset = rightOffset[selection]->text().toInt(); idleBuffer = serialPort.readLine(); TLled[selection]->setText(idleBuffer.trimmed()); - profilesTable[selection].TLled = TLled[selection]->text().toInt(); + profilesTable[selection].TLled = TLled[selection]->text().toFloat(); idleBuffer = serialPort.readLine(); TRled[selection]->setText(idleBuffer.trimmed()); - profilesTable[selection].TRled = TRled[selection]->text().toInt(); + profilesTable[selection].TRled = TRled[selection]->text().toFloat(); DiffUpdate(); } } From f9f2f9144bbbe805e040b7b11ebc64e1931512b3 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Sun, 19 May 2024 18:49:16 +0000 Subject: [PATCH 07/24] Fix profiles updating Forgot to make the program wait for reading. Easy mistake to make. =w=; --- guiwindow.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 36827cd..4f46d4b 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -1734,8 +1734,7 @@ void guiWindow::serialPort_readyRead() break; } } else if(idleBuffer.contains("Profile: ")) { - idleBuffer = idleBuffer.right(3); - idleBuffer = idleBuffer.trimmed(); + idleBuffer = idleBuffer.trimmed().right(1); uint8_t selection = idleBuffer.toInt(); if(selection != board.selectedProfile) { board.selectedProfile = selection; @@ -1743,28 +1742,33 @@ void guiWindow::serialPort_readyRead() } DiffUpdate(); } else if(idleBuffer.contains("UpdatedProf: ")) { - idleBuffer = idleBuffer.right(3); - idleBuffer = idleBuffer.trimmed(); + idleBuffer = idleBuffer.trimmed().right(1); uint8_t selection = idleBuffer.toInt(); if(selection != board.selectedProfile) { selectedProfile[selection]->setChecked(true); } board.selectedProfile = selection; + serialPort.waitForReadyRead(2000); idleBuffer = serialPort.readLine(); topOffset[selection]->setText(idleBuffer.trimmed()); profilesTable[selection].topOffset = topOffset[selection]->text().toInt(); + serialPort.waitForReadyRead(2000); idleBuffer = serialPort.readLine(); bottomOffset[selection]->setText(idleBuffer.trimmed()); profilesTable[selection].bottomOffset = bottomOffset[selection]->text().toInt(); + serialPort.waitForReadyRead(2000); idleBuffer = serialPort.readLine(); leftOffset[selection]->setText(idleBuffer.trimmed()); profilesTable[selection].leftOffset = leftOffset[selection]->text().toInt(); + serialPort.waitForReadyRead(2000); idleBuffer = serialPort.readLine(); rightOffset[selection]->setText(idleBuffer.trimmed()); profilesTable[selection].rightOffset = rightOffset[selection]->text().toInt(); + serialPort.waitForReadyRead(2000); idleBuffer = serialPort.readLine(); TLled[selection]->setText(idleBuffer.trimmed()); profilesTable[selection].TLled = TLled[selection]->text().toFloat(); + serialPort.waitForReadyRead(2000); idleBuffer = serialPort.readLine(); TRled[selection]->setText(idleBuffer.trimmed()); profilesTable[selection].TRled = TRled[selection]->text().toFloat(); From 3de8de84bb011ff8744df8c9672184fc4ce3172a Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Sun, 19 May 2024 19:29:27 +0000 Subject: [PATCH 08/24] Fix end-of-save check sometimes not picking up confirmation Just to be foolproof, give it three goes before calling the patient dead. --- guiwindow.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 4f46d4b..766122e 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -773,23 +773,19 @@ void guiWindow::on_confirmButton_clicked() if(buffer.contains("OK:") || buffer.contains("NOENT:")) { statusProgressBar->setValue(statusProgressBar->value() + 1); } else if(i == serialQueue.length() - 1 && buffer.contains("Saving preferences...")) { - if(serialPort.waitForReadyRead(2000)) { + for(uint8_t t = 0; t < 3; t++) { + if(serialPort.atEnd()) { serialPort.waitForReadyRead(2000); } buffer = serialPort.readLine(); if(buffer.contains("Settings saved to")) { success = true; - // because there's probably some leftover bytes that might congest things: - while(!serialPort.atEnd()) { - serialPort.readLine(); - } - } else { - qDebug() << "Sent save command, but didn't save successfully! What the fuck happened???"; + t = 3; + } + } + if(success) { + while(!serialPort.atEnd()) { + serialPort.readLine(); } - } else { - qDebug() << "Sent save command, but didn't receive a response!?!? What the heck Seong?"; } - } else { - success = false; - break; } } } From 515f850e05161f29f917ed37068d45850a8dcea3 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Sun, 19 May 2024 22:31:43 +0000 Subject: [PATCH 09/24] AdafruitItsy layout defined to Samco compatible layout --- constants.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/constants.h b/constants.h index 23a3bfd..c54ce8e 100644 --- a/constants.h +++ b/constants.h @@ -149,21 +149,21 @@ const boardLayout_t rpipicoLayout[] = { }; const boardLayout_t adafruitItsyRP2040Layout[] = { - {btnGunUp, pinDigital}, {btnGunDown, pinDigital}, + {btnUnmapped, pinDigital}, {btnUnmapped, pinDigital}, {camSDA, pinDigital}, {camSCL, pinDigital}, - {btnGunLeft, pinDigital}, {btnGunRight, pinDigital}, - {btnTrigger, pinDigital}, {btnGunA, pinDigital}, - {btnGunB, pinDigital}, {btnGunC, pinDigital}, - {btnStart, pinDigital}, {btnSelect, pinDigital}, - {btnPedal, pinDigital}, {btnReserved, pinNothing}, + {btnPedal, pinDigital}, {btnUnmapped, pinDigital}, + {btnTrigger, pinDigital}, {btnGunDown, pinDigital}, + {btnGunLeft, pinDigital}, {btnGunUp, pinDigital}, + {btnGunRight, pinDigital}, {btnGunC, pinDigital}, + {btnUnmapped, pinDigital}, {btnReserved, pinNothing}, {btnReserved, pinNothing}, {btnReserved, pinNothing}, {btnReserved, pinNothing}, {btnReserved, pinNothing}, {btnUnmapped, pinDigital}, {btnUnmapped, pinDigital}, {btnUnmapped, pinDigital}, {btnReserved, pinNothing}, {btnReserved, pinNothing}, {btnReserved, pinNothing}, {rumblePin, pinDigital}, {solenoidPin, pinDigital}, - {btnUnmapped, pinAnalog}, {btnUnmapped, pinAnalog}, - {btnUnmapped, pinAnalog}, {btnUnmapped, pinAnalog} + {btnGunB, pinAnalog}, {btnGunA, pinAnalog}, + {btnStart, pinAnalog}, {btnSelect, pinAnalog} }; const boardLayout_t adafruitKB2040Layout[] = { From 5c640ac78e1717a35536d6d2412db6544f79a72f Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Sun, 19 May 2024 22:32:23 +0000 Subject: [PATCH 10/24] Custom pins enabling copies values from defaults (only when the board original value is no custom pins) --- guiwindow.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 766122e..f7d5fdd 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -499,17 +499,29 @@ bool guiWindow::SerialInit(int portNum) void guiWindow::BoxesUpdate() { - for(uint8_t i = 0; i < 30; i++) { - pinBoxes[i]->setCurrentIndex(btnUnmapped); - pinBoxesOldIndex[i] = btnUnmapped; - } if(boolSettings[customPins]) { - currentPins.clear(); + // if the custom pins setting grabbed from the gun has been set + if(boolSettings_orig[customPins]) { + // clear map + currentPins.clear(); + // set or clear the local pins mapping + for(uint8_t i = 0; i < 30; i++) { + currentPins[i] = btnUnmapped; + } + // (re)-copy pins settings grabbed from the gun to the app catalog + inputsMap = inputsMap_orig; + // else, if the board was using default maps before switching to custom + } else { + for(uint8_t i = 0; i < 30; i++) { + if(currentPins[i] > btnUnmapped) { + inputsMap[currentPins[i]-1] = i; + } + } + } + // enable pinboxes for(uint8_t i = 0; i < 30; i++) { pinBoxes[i]->setEnabled(true); - currentPins[i] = btnUnmapped; } - inputsMap = inputsMap_orig; for(uint8_t i = 0; i < INPUTS_COUNT; i++) { if(inputsMap.value(i) >= 0) { currentPins[inputsMap.value(i)] = i+1; @@ -573,7 +585,7 @@ void guiWindow::DiffUpdate() { settingsDiff = 0; if(boolSettings_orig[customPins] != boolSettings[customPins]) { - //settingsDiff++; + settingsDiff++; } if(boolSettings[customPins]) { if(inputsMap_orig != inputsMap) { From c4cb8f65e7724886da5bdf6aaa81b1252531c991 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Sun, 19 May 2024 23:22:40 +0000 Subject: [PATCH 11/24] Added stuff at RG's request (And placeholders for NeoPixel color stuff soon shhhhh) --- constants.h | 6 ++++-- guiwindow.cpp | 17 ++++++++++------- guiwindow.h | 4 +++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/constants.h b/constants.h index c54ce8e..96c14d2 100644 --- a/constants.h +++ b/constants.h @@ -44,6 +44,7 @@ enum boardInputs_e { btnGunLeft, btnGunRight, btnPedal, + btnPedal2, btnHome, btnPump, rumblePin, @@ -59,6 +60,7 @@ enum boardInputs_e { camSCL, periphSDA, periphSCL, + battery, analogX, analogY, tempPin @@ -82,9 +84,9 @@ enum settingsTypes_e { solenoidNormalInterval, solenoidFastInterval, solenoidHoldLength, - customLEDcount, autofireWaitFactor, - holdToPauseLength + holdToPauseLength, + customLEDcount }; enum pinTypes_e { diff --git a/guiwindow.cpp b/guiwindow.cpp index f7d5fdd..b93c6ba 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -52,7 +52,7 @@ QVector profilesTable_orig(PROFILES_COUNT); // Values: -2 = N/A, -1 = reserved, 0 = available, unused QMap currentPins; -#define INPUTS_COUNT 29 +#define INPUTS_COUNT 31 // Map of what inputs are put where, // Key = button/output, Value = pin number occupying, if any. // Value of -1 means unmapped. @@ -323,8 +323,7 @@ void guiWindow::SerialLoad() // booleans QString buffer; for(uint8_t i = 0; i < sizeof(boolSettings); i++) { - buffer = serialPort.readLine(); - buffer = buffer.trimmed(); + buffer = serialPort.readLine().trimmed(); boolSettings[i] = buffer.toInt(); boolSettings_orig[i] = boolSettings[i]; } @@ -333,7 +332,7 @@ void guiWindow::SerialLoad() serialPort.write("Xlp"); serialPort.waitForReadyRead(1000); for(uint8_t i = 0; i < INPUTS_COUNT; i++) { - buffer = serialPort.readLine(); + buffer = serialPort.readLine().trimmed(); inputsMap_orig[i] = buffer.toInt(); // For some reason, QTSerial drops output shortly after this. // So we send a ping to refill the buffer. @@ -348,12 +347,16 @@ void guiWindow::SerialLoad() serialPort.write("Xls"); serialPort.waitForBytesWritten(2000); serialPort.waitForReadyRead(2000); - for(uint8_t i = 0; i < sizeof(settingsTable) / 2; i++) { - buffer = serialPort.readLine(); - buffer = buffer.trimmed(); + for(uint8_t i = 0; i < sizeof(settingsTable) / 2 + 4; i++) { + buffer = serialPort.readLine().trimmed(); settingsTable[i] = buffer.toInt(); settingsTable_orig[i] = settingsTable[i]; } + // TODO: this is for customLEDstatic and customLEDcolors1-3 + for(uint8_t i = 0; i < 4; i++) { + // nomf'd for now + buffer = serialPort.readLine().trimmed(); + } // profiles for(uint8_t i = 0; i < PROFILES_COUNT; i++) { QString genString = QString("XlP%1").arg(i); diff --git a/guiwindow.h b/guiwindow.h index d3bee1b..cbb4099 100644 --- a/guiwindow.h +++ b/guiwindow.h @@ -138,7 +138,8 @@ private slots: "D-Pad Down", "D-Pad Left", "D-Pad Right", - "External Pedal", + "External Pedal 1", + "External Pedal 2", "Home Button", "Pump Action", "Rumble Signal", @@ -154,6 +155,7 @@ private slots: "Camera SCL", "Peripherals SDA", "Peripherals SCL", + "Battery Sensor", "Analog Pin X", "Analog Pin Y", "Temp Sensor" From 1a490775e7276dd20a5a5a0bc7b73207729012a4 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Mon, 20 May 2024 00:21:32 +0000 Subject: [PATCH 12/24] Forgot to add extra pedal to tester --- guiwindow.cpp | 6 ++++++ guiwindow.ui | 41 ++++++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index b93c6ba..d8ce56b 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -1692,6 +1692,9 @@ void guiWindow::serialPort_readyRead() case btnPedal: ui->btnPedalLabel->setText("Pedal"); break; + case btnPedal2: + ui->btnPedalLabel->setText("Alt Pedal"); + break; case btnHome: ui->btnHomeLabel->setText("Home"); break; @@ -1737,6 +1740,9 @@ void guiWindow::serialPort_readyRead() case btnPedal: ui->btnPedalLabel->setText("Pedal"); break; + case btnPedal2: + ui->btnPedalLabel->setText("Alt Pedal"); + break; case btnHome: ui->btnHomeLabel->setText("Home"); break; diff --git a/guiwindow.ui b/guiwindow.ui index 16bcae8..7d2cedb 100644 --- a/guiwindow.ui +++ b/guiwindow.ui @@ -1253,8 +1253,8 @@ - - + + QFrame::Shape::Box @@ -1262,7 +1262,7 @@ QFrame::Shadow::Raised - Pedal + Home Qt::TextFormat::RichText @@ -1270,13 +1270,29 @@ Qt::AlignCenter - - 5 + + + + + + QFrame::Shape::Box + + + QFrame::Shadow::Raised + + + Pump Action + + + Qt::TextFormat::RichText + + + Qt::AlignCenter - - + + QFrame::Shape::Box @@ -1284,7 +1300,7 @@ QFrame::Shadow::Raised - Home + Pedal Qt::TextFormat::RichText @@ -1292,10 +1308,13 @@ Qt::AlignCenter + + 5 + - - + + QFrame::Shape::Box @@ -1303,7 +1322,7 @@ QFrame::Shadow::Raised - Pump Action + Alt Pedal Qt::TextFormat::RichText From 89dac076a9c78b0f9436f315773353b5cb25299b Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Mon, 20 May 2024 01:37:39 +0000 Subject: [PATCH 13/24] Add periodic testpulse to confirm board connection Pings the board every 5 seconds with a throwaway command; if write succeeds, all's good, but failure assumes that the board must have disconnected or is otherwise unavailable, so set the port off to avoid user confusion. --- guiwindow.cpp | 36 +++++++++++++++++++++++++++++++----- guiwindow.h | 8 ++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index d8ce56b..475e94c 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -32,6 +32,7 @@ #include #include #include +#include // Currently loaded board object boardInfo_s board; @@ -92,6 +93,7 @@ QPushButton *renameBtn[PROFILES_COUNT]; QSvgWidget *centerPic; QGraphicsScene *testScene; +#define ALIVE_TIMER 5000 // // ^^^-------GLOBAL VARS UP THERE----------^^^ @@ -261,6 +263,8 @@ guiWindow::guiWindow(QWidget *parent) ui->testView->scale(0.5, 0.5); // Finally get to the thing! + aliveTimer = new QTimer(); + connect(aliveTimer, &QTimer::timeout, this, &guiWindow::aliveTimer_timeout); statusBar()->showMessage("Welcome to the OpenFIRE app!", 3000); PortsSearch(); usbName.prepend("[No device]"); @@ -347,7 +351,7 @@ void guiWindow::SerialLoad() serialPort.write("Xls"); serialPort.waitForBytesWritten(2000); serialPort.waitForReadyRead(2000); - for(uint8_t i = 0; i < sizeof(settingsTable) / 2 + 4; i++) { + for(uint8_t i = 0; i < sizeof(settingsTable) / 2; i++) { buffer = serialPort.readLine().trimmed(); settingsTable[i] = buffer.toInt(); settingsTable_orig[i] = settingsTable[i]; @@ -656,6 +660,7 @@ void guiWindow::DiffUpdate() ui->confirmButton->setText("[Nothing To Save]"); ui->confirmButton->setEnabled(false); } + qDebug() << settingsDiff; } @@ -734,6 +739,7 @@ void guiWindow::on_confirmButton_clicked() if(value == QMessageBox::Yes) { if(serialPort.isOpen()) { serialActive = true; + aliveTimer->stop(); // send a signal so the gun pauses its test outputs for the save op. serialPort.write("Xm"); serialPort.waitForBytesWritten(1000); @@ -817,6 +823,7 @@ void guiWindow::on_confirmButton_clicked() ui->boardLabel->setText(PrettifyName()); } serialActive = false; + aliveTimer->start(ALIVE_TIMER); serialQueue.clear(); if(!serialPort.atEnd()) { serialPort.readAll(); @@ -830,6 +837,19 @@ void guiWindow::on_confirmButton_clicked() } +void guiWindow::aliveTimer_timeout() +{ + if(serialPort.isOpen()) { + serialPort.write("."); + if(!serialPort.waitForBytesWritten(1)) { + statusBar()->showMessage("Board hasn't responded to pulse; assuming it's been disconnected."); + serialPort.close(); + ui->comPortSelector->setCurrentIndex(0); + } + } +} + + void guiWindow::on_comPortSelector_currentIndexChanged(int index) { // Indiscriminately clears the board layout views. @@ -893,9 +913,14 @@ void guiWindow::on_comPortSelector_currentIndexChanged(int index) serialPort.close(); serialActive = false; } + // try to init serial port + // if returns false, it failed, so just turn the index back to initial. if(!SerialInit(index - 1)) { ui->comPortSelector->setCurrentIndex(0); + aliveTimer->stop(); + // else, serial port is online! What do we got? } else { + aliveTimer->start(ALIVE_TIMER); ui->versionLabel->setText(QString("v%1 - \"%2\"").arg(board.versionNumber).arg(board.versionCodename)); BoxesFill(); @@ -1257,6 +1282,7 @@ void guiWindow::on_comPortSelector_currentIndexChanged(int index) serialActive = false; } qDebug() << "COM port disabled!"; + aliveTimer->stop(); ui->tabWidget->setEnabled(false); } } @@ -1655,8 +1681,7 @@ void guiWindow::serialPort_readyRead() while(!serialPort.atEnd()) { QString idleBuffer = serialPort.readLine(); if(idleBuffer.contains("Pressed:")) { - idleBuffer = idleBuffer.right(4); - idleBuffer = idleBuffer.trimmed(); + idleBuffer = idleBuffer.right(2).trimmed(); uint8_t button = idleBuffer.toInt(); switch(button) { case btnTrigger: @@ -1703,8 +1728,7 @@ void guiWindow::serialPort_readyRead() break; } } else if(idleBuffer.contains("Released:")) { - idleBuffer = idleBuffer.right(4); - idleBuffer = idleBuffer.trimmed(); + idleBuffer = idleBuffer.right(2).trimmed(); uint8_t button = idleBuffer.toInt(); switch(button) { case btnTrigger: @@ -1839,6 +1863,7 @@ void guiWindow::on_testBtn_clicked() if(serialPort.isOpen()) { // Pre-emptively put a sock in the readyRead signal serialActive = true; + aliveTimer->stop(); serialPort.write("XT"); serialPort.waitForBytesWritten(1000); serialPort.waitForReadyRead(1000); @@ -1866,6 +1891,7 @@ void guiWindow::on_testBtn_clicked() ui->dangerZoneBox->setEnabled(true); DiffUpdate(); serialActive = false; + aliveTimer->start(ALIVE_TIMER); } } } diff --git a/guiwindow.h b/guiwindow.h index cbb4099..a0a924a 100644 --- a/guiwindow.h +++ b/guiwindow.h @@ -22,6 +22,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE namespace Ui { @@ -42,6 +43,8 @@ class guiWindow : public QMainWindow bool serialActive = false; private slots: + void aliveTimer_timeout(); + void on_comPortSelector_currentIndexChanged(int index); void on_confirmButton_clicked(); @@ -192,6 +195,11 @@ private slots: bool testMode = false; + // for timer + bool boardIsAlive = false; + + QTimer *aliveTimer; + // Test Mode screen points & colors QGraphicsEllipseItem testPointTL; QGraphicsEllipseItem testPointTR; From b636343ad58186f332081e2a36e771426b2ae792 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Mon, 20 May 2024 02:05:27 +0000 Subject: [PATCH 14/24] Test reads fixes, cleanup --- guiwindow.cpp | 120 ++++++++++++++------------------------------------ 1 file changed, 32 insertions(+), 88 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 475e94c..973d0ef 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -327,8 +327,7 @@ void guiWindow::SerialLoad() // booleans QString buffer; for(uint8_t i = 0; i < sizeof(boolSettings); i++) { - buffer = serialPort.readLine().trimmed(); - boolSettings[i] = buffer.toInt(); + boolSettings[i] = serialPort.readLine().trimmed().toInt(); boolSettings_orig[i] = boolSettings[i]; } // pins @@ -336,8 +335,7 @@ void guiWindow::SerialLoad() serialPort.write("Xlp"); serialPort.waitForReadyRead(1000); for(uint8_t i = 0; i < INPUTS_COUNT; i++) { - buffer = serialPort.readLine().trimmed(); - inputsMap_orig[i] = buffer.toInt(); + inputsMap_orig[i] = serialPort.readLine().trimmed().toInt(); // For some reason, QTSerial drops output shortly after this. // So we send a ping to refill the buffer. if(i == 14) { @@ -352,8 +350,7 @@ void guiWindow::SerialLoad() serialPort.waitForBytesWritten(2000); serialPort.waitForReadyRead(2000); for(uint8_t i = 0; i < sizeof(settingsTable) / 2; i++) { - buffer = serialPort.readLine().trimmed(); - settingsTable[i] = buffer.toInt(); + settingsTable[i] = serialPort.readLine().trimmed().toInt(); settingsTable_orig[i] = settingsTable[i]; } // TODO: this is for customLEDstatic and customLEDcolors1-3 @@ -367,52 +364,17 @@ void guiWindow::SerialLoad() serialPort.write(genString.toLocal8Bit()); serialPort.waitForBytesWritten(2000); serialPort.waitForReadyRead(2000); - buffer = serialPort.readLine().trimmed(); - topOffset[i]->setText(buffer); - profilesTable[i].topOffset = buffer.toInt(); - profilesTable_orig[i].topOffset = profilesTable[i].topOffset; - buffer = serialPort.readLine().trimmed(); - bottomOffset[i]->setText(buffer); - profilesTable[i].bottomOffset = buffer.toInt(); - profilesTable_orig[i].bottomOffset = profilesTable[i].bottomOffset; - buffer = serialPort.readLine().trimmed(); - leftOffset[i]->setText(buffer); - profilesTable[i].leftOffset = buffer.toInt(); - profilesTable_orig[i].leftOffset = profilesTable[i].leftOffset; - buffer = serialPort.readLine().trimmed(); - rightOffset[i]->setText(buffer); - profilesTable[i].rightOffset = buffer.toInt(); - profilesTable_orig[i].rightOffset = profilesTable[i].rightOffset; - buffer = serialPort.readLine().trimmed(); - TLled[i]->setText(buffer); - profilesTable[i].TLled = buffer.toFloat(); - profilesTable_orig[i].TLled = profilesTable[i].TLled; - buffer = serialPort.readLine().trimmed(); - TRled[i]->setText(buffer); - profilesTable[i].TRled = buffer.toFloat(); - profilesTable_orig[i].TRled = profilesTable[i].TRled; - buffer = serialPort.readLine().trimmed(); - profilesTable[i].irSensitivity = buffer.toInt(); - profilesTable_orig[i].irSensitivity = profilesTable[i].irSensitivity; - irSens[i]->setCurrentIndex(profilesTable[i].irSensitivity); - irSensOldIndex[i] = profilesTable[i].irSensitivity; - buffer = serialPort.readLine().trimmed(); - profilesTable[i].runMode = buffer.toInt(); - profilesTable_orig[i].runMode = profilesTable[i].runMode; - runMode[i]->setCurrentIndex(profilesTable[i].runMode); - runModeOldIndex[i] = profilesTable[i].runMode; - buffer = serialPort.readLine().trimmed(); - layoutMode[i]->setChecked(buffer.toInt()); - profilesTable[i].layoutType = buffer.toInt(); - profilesTable_orig[i].layoutType = profilesTable[i].layoutType; - buffer = serialPort.readLine().trimmed(); - color[i]->setStyleSheet(QString("background-color: #%1").arg(buffer.toLong(), 6, 16, QLatin1Char('0'))); - profilesTable[i].color = buffer.toLong(); - profilesTable_orig[i].color = profilesTable[i].color; - buffer = serialPort.readLine().trimmed(); - selectedProfile[i]->setText(buffer); - profilesTable[i].profName = buffer; - profilesTable_orig[i].profName = profilesTable[i].profName; + buffer = serialPort.readLine().trimmed(), topOffset[i]->setText(buffer), profilesTable[i].topOffset = buffer.toInt(), profilesTable_orig[i].topOffset = profilesTable[i].topOffset; + buffer = serialPort.readLine().trimmed(), bottomOffset[i]->setText(buffer), profilesTable[i].bottomOffset = buffer.toInt(), profilesTable_orig[i].bottomOffset = profilesTable[i].bottomOffset; + buffer = serialPort.readLine().trimmed(), leftOffset[i]->setText(buffer), profilesTable[i].leftOffset = buffer.toInt(), profilesTable_orig[i].leftOffset = profilesTable[i].leftOffset; + buffer = serialPort.readLine().trimmed(), rightOffset[i]->setText(buffer), profilesTable[i].rightOffset = buffer.toInt(), profilesTable_orig[i].rightOffset = profilesTable[i].rightOffset; + buffer = serialPort.readLine().trimmed(), TLled[i]->setText(buffer), profilesTable[i].TLled = buffer.toFloat(), profilesTable_orig[i].TLled = profilesTable[i].TLled; + buffer = serialPort.readLine().trimmed(), TRled[i]->setText(buffer), profilesTable[i].TRled = buffer.toFloat(), profilesTable_orig[i].TRled = profilesTable[i].TRled; + buffer = serialPort.readLine().trimmed(), profilesTable[i].irSensitivity = buffer.toInt(), profilesTable_orig[i].irSensitivity = profilesTable[i].irSensitivity, irSens[i]->setCurrentIndex(profilesTable[i].irSensitivity), irSensOldIndex[i] = profilesTable[i].irSensitivity; + buffer = serialPort.readLine().trimmed(), profilesTable[i].runMode = buffer.toInt(), profilesTable_orig[i].runMode = profilesTable[i].runMode, runMode[i]->setCurrentIndex(profilesTable[i].runMode), runModeOldIndex[i] = profilesTable[i].runMode; + buffer = serialPort.readLine().trimmed(), layoutMode[i]->setChecked(buffer.toInt()), profilesTable[i].layoutType = buffer.toInt(), profilesTable_orig[i].layoutType = profilesTable[i].layoutType; + buffer = serialPort.readLine().trimmed(), color[i]->setStyleSheet(QString("background-color: #%1").arg(buffer.toLong(), 6, 16, QLatin1Char('0'))), profilesTable[i].color = buffer.toLong(), profilesTable_orig[i].color = profilesTable[i].color; + buffer = serialPort.readLine().trimmed(), selectedProfile[i]->setText(buffer), profilesTable[i].profName = buffer, profilesTable_orig[i].profName = profilesTable[i].profName; } serialActive = false; } else { @@ -441,15 +403,11 @@ bool guiWindow::SerialInit(int portNum) QString buffer = serialPort.readLine(); if(buffer.contains("OpenFIRE")) { qDebug() << "OpenFIRE gun detected!"; - buffer = serialPort.readLine(); - buffer = buffer.trimmed(); - board.versionNumber = buffer.toFloat(); + board.versionNumber = serialPort.readLine().trimmed().toFloat(); qDebug() << "Version number:" << board.versionNumber; - buffer = serialPort.readLine(); - board.versionCodename = buffer.trimmed(); + board.versionCodename = serialPort.readLine().trimmed(); qDebug() << "Version codename:" << board.versionCodename; - buffer = serialPort.readLine(); - buffer = buffer.trimmed(); + buffer = serialPort.readLine().trimmed(); if(buffer == "rpipico") { board.type = rpipico; } else if(buffer == "adafruitItsyRP2040") { @@ -462,25 +420,22 @@ bool guiWindow::SerialInit(int portNum) board.type = generic; } //qDebug() << "Selected profile number:" << buffer; - buffer = serialPort.readLine(); - buffer = buffer.trimmed(); - board.selectedProfile = buffer.toInt(); + board.selectedProfile = serialPort.readLine().trimmed().toInt(); board.previousProfile = board.selectedProfile; selectedProfile[board.selectedProfile]->setChecked(true); //qDebug() << "Board type:" << buffer; serialPort.write("Xln"); serialPort.waitForReadyRead(1000); - buffer = serialPort.readLine(); - if(buffer.trimmed() == "SERIALREADERR01") { + buffer = serialPort.readLine().trimmed(); + if(buffer == "SERIALREADERR01") { tinyUSBtable.tinyUSBname = ""; } else { - tinyUSBtable.tinyUSBname = buffer.trimmed(); + tinyUSBtable.tinyUSBname = buffer; } tinyUSBtable_orig.tinyUSBname = tinyUSBtable.tinyUSBname; serialPort.write("Xli"); serialPort.waitForReadyRead(1000); - buffer = serialPort.readLine(); - tinyUSBtable.tinyUSBid = buffer.trimmed(); + tinyUSBtable.tinyUSBid = serialPort.readLine().trimmed(); tinyUSBtable_orig.tinyUSBid = tinyUSBtable.tinyUSBid; SerialLoad(); return true; @@ -660,7 +615,6 @@ void guiWindow::DiffUpdate() ui->confirmButton->setText("[Nothing To Save]"); ui->confirmButton->setEnabled(false); } - qDebug() << settingsDiff; } @@ -1681,8 +1635,7 @@ void guiWindow::serialPort_readyRead() while(!serialPort.atEnd()) { QString idleBuffer = serialPort.readLine(); if(idleBuffer.contains("Pressed:")) { - idleBuffer = idleBuffer.right(2).trimmed(); - uint8_t button = idleBuffer.toInt(); + uint8_t button = idleBuffer.trimmed().right(2).toInt(); switch(button) { case btnTrigger: ui->btnTriggerLabel->setText("Trigger"); @@ -1728,8 +1681,7 @@ void guiWindow::serialPort_readyRead() break; } } else if(idleBuffer.contains("Released:")) { - idleBuffer = idleBuffer.right(2).trimmed(); - uint8_t button = idleBuffer.toInt(); + uint8_t button = idleBuffer.trimmed().right(2).toInt(); switch(button) { case btnTrigger: ui->btnTriggerLabel->setText("Trigger"); @@ -1775,43 +1727,35 @@ void guiWindow::serialPort_readyRead() break; } } else if(idleBuffer.contains("Profile: ")) { - idleBuffer = idleBuffer.trimmed().right(1); - uint8_t selection = idleBuffer.toInt(); + uint8_t selection = idleBuffer.trimmed().right(1).toInt(); if(selection != board.selectedProfile) { board.selectedProfile = selection; selectedProfile[selection]->setChecked(true); } DiffUpdate(); } else if(idleBuffer.contains("UpdatedProf: ")) { - idleBuffer = idleBuffer.trimmed().right(1); - uint8_t selection = idleBuffer.toInt(); + uint8_t selection = idleBuffer.trimmed().right(1).toInt(); if(selection != board.selectedProfile) { selectedProfile[selection]->setChecked(true); } board.selectedProfile = selection; serialPort.waitForReadyRead(2000); - idleBuffer = serialPort.readLine(); - topOffset[selection]->setText(idleBuffer.trimmed()); + topOffset[selection]->setText(serialPort.readLine().trimmed()); profilesTable[selection].topOffset = topOffset[selection]->text().toInt(); serialPort.waitForReadyRead(2000); - idleBuffer = serialPort.readLine(); - bottomOffset[selection]->setText(idleBuffer.trimmed()); + bottomOffset[selection]->setText(serialPort.readLine().trimmed()); profilesTable[selection].bottomOffset = bottomOffset[selection]->text().toInt(); serialPort.waitForReadyRead(2000); - idleBuffer = serialPort.readLine(); - leftOffset[selection]->setText(idleBuffer.trimmed()); + leftOffset[selection]->setText(serialPort.readLine().trimmed()); profilesTable[selection].leftOffset = leftOffset[selection]->text().toInt(); serialPort.waitForReadyRead(2000); - idleBuffer = serialPort.readLine(); - rightOffset[selection]->setText(idleBuffer.trimmed()); + rightOffset[selection]->setText(serialPort.readLine().trimmed()); profilesTable[selection].rightOffset = rightOffset[selection]->text().toInt(); serialPort.waitForReadyRead(2000); - idleBuffer = serialPort.readLine(); - TLled[selection]->setText(idleBuffer.trimmed()); + TLled[selection]->setText(serialPort.readLine().trimmed()); profilesTable[selection].TLled = TLled[selection]->text().toFloat(); serialPort.waitForReadyRead(2000); - idleBuffer = serialPort.readLine(); - TRled[selection]->setText(idleBuffer.trimmed()); + TRled[selection]->setText(serialPort.readLine().trimmed()); profilesTable[selection].TRled = TRled[selection]->text().toFloat(); DiffUpdate(); } From d71ddbb1614c820d421c9cb187fef91b2cbae446 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Mon, 20 May 2024 15:19:24 +0000 Subject: [PATCH 15/24] Alt Pedal fixes --- guiwindow.cpp | 4 ++-- guiwindow.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 973d0ef..f9ce908 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -1671,7 +1671,7 @@ void guiWindow::serialPort_readyRead() ui->btnPedalLabel->setText("Pedal"); break; case btnPedal2: - ui->btnPedalLabel->setText("Alt Pedal"); + ui->btnPedal2Label->setText("Alt Pedal"); break; case btnHome: ui->btnHomeLabel->setText("Home"); @@ -1717,7 +1717,7 @@ void guiWindow::serialPort_readyRead() ui->btnPedalLabel->setText("Pedal"); break; case btnPedal2: - ui->btnPedalLabel->setText("Alt Pedal"); + ui->btnPedal2Label->setText("Alt Pedal"); break; case btnHome: ui->btnHomeLabel->setText("Home"); diff --git a/guiwindow.h b/guiwindow.h index a0a924a..1f2fc8d 100644 --- a/guiwindow.h +++ b/guiwindow.h @@ -141,8 +141,8 @@ private slots: "D-Pad Down", "D-Pad Left", "D-Pad Right", - "External Pedal 1", - "External Pedal 2", + "Pedal", + "Alt Pedal", "Home Button", "Pump Action", "Rumble Signal", From 765c39fc59f8944245394dba929f8fb479628116 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Mon, 20 May 2024 21:00:47 +0000 Subject: [PATCH 16/24] Ordering fixes, test tab changes Disabled buttons are now shown as disabled upon load/save. Temperature sensor and analog stick are now shown in test tab. Temp sensor will update temperature when temp update is received, and the color will reflect that (green for good, orange for warning, red for danger). Analog stick will update with loose relative position when it receives an analog update, represented by an arrow unicode glyph. --- constants.h | 2 +- guiwindow.cpp | 192 ++++++++++++++++++---------------- guiwindow.h | 4 +- guiwindow.ui | 281 +------------------------------------------------- 4 files changed, 106 insertions(+), 373 deletions(-) diff --git a/constants.h b/constants.h index 96c14d2..78d907d 100644 --- a/constants.h +++ b/constants.h @@ -52,10 +52,10 @@ enum boardInputs_e { rumbleSwitch, solenoidSwitch, autofireSwitch, + neoPixel, ledR, ledG, ledB, - neoPixel, camSDA, camSCL, periphSDA, diff --git a/guiwindow.cpp b/guiwindow.cpp index f9ce908..0339e9c 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -78,6 +78,9 @@ QComboBox *pinBoxes[30]; QLabel *pinLabel[30]; QWidget *padding[30]; +// buttons in the test screen +QLabel *testLabel[16]; + QRadioButton *selectedProfile[PROFILES_COUNT]; QLabel *topOffset[PROFILES_COUNT]; QLabel *bottomOffset[PROFILES_COUNT]; @@ -227,6 +230,36 @@ guiWindow::guiWindow(QWidget *parent) ui->profilesArea->addWidget(color[i], i+1, 20, 1, 1); } + // Setup test screen buttons + for(uint8_t i = 0; i < 16; i++) { + testLabel[i] = new QLabel; + if(i == 14) { + testLabel[i]->setText(valuesNameList[tempPin]); + } else if(i == 15) { + testLabel[i]->setText("Analog Stick"); + } else { + testLabel[i]->setText(valuesNameList[i+1]); + } + testLabel[i]->setEnabled(false); + testLabel[i]->setAlignment(Qt::AlignCenter); + testLabel[i]->setFrameStyle(QFrame::Box | QFrame::Raised); + if(i == 15) { + ui->buttonsTestLayout->addWidget(testLabel[i], 3, 3, 1, 1); + } else if(i == 14) { + ui->buttonsTestLayout->addWidget(testLabel[i], 3, 1, 1, 1); + } else if(i > 9) { + ui->buttonsTestLayout->addWidget(testLabel[i], 2, i-10, 1, 1); + } else if(i > 4) { + ui->buttonsTestLayout->addWidget(testLabel[i], 1, i-5, 1, 1); + } else { + ui->buttonsTestLayout->addWidget(testLabel[i], 0, i, 1, 1); + } + } + ui->buttonsTestLayout->setRowMinimumHeight(0, 32); + ui->buttonsTestLayout->setRowMinimumHeight(1, 32); + ui->buttonsTestLayout->setRowMinimumHeight(2, 32); + ui->buttonsTestLayout->setRowMinimumHeight(3, 32); + // Setup Test Mode screen colors testPointTLPen.setColor(Qt::green); testPointTRPen.setColor(Qt::green); @@ -502,7 +535,6 @@ void guiWindow::BoxesUpdate() pinBoxesOldIndex[i] = currentPins[i]; pinBoxes[i]->setEnabled(false); } - return; break; } case adafruitItsyRP2040: @@ -513,7 +545,6 @@ void guiWindow::BoxesUpdate() pinBoxesOldIndex[i] = currentPins[i]; pinBoxes[i]->setEnabled(false); } - return; break; } case adafruitKB2040: @@ -524,7 +555,6 @@ void guiWindow::BoxesUpdate() pinBoxesOldIndex[i] = currentPins[i]; pinBoxes[i]->setEnabled(false); } - return; break; } case arduinoNanoRP2040: @@ -535,9 +565,13 @@ void guiWindow::BoxesUpdate() pinBoxesOldIndex[i] = currentPins[i]; pinBoxes[i]->setEnabled(false); } - return; break; } + for(uint8_t i = 0; i < 30; i++) { + if(currentPins[i] > btnUnmapped) { + inputsMap[currentPins[i]-1] = i; + } + } } } } @@ -642,6 +676,7 @@ void guiWindow::SyncSettings() profilesTable_orig[i].color = profilesTable[i].color; profilesTable_orig[i].profName = profilesTable[i].profName; } + LabelsUpdate(); } @@ -877,6 +912,7 @@ void guiWindow::on_comPortSelector_currentIndexChanged(int index) aliveTimer->start(ALIVE_TIMER); ui->versionLabel->setText(QString("v%1 - \"%2\"").arg(board.versionNumber).arg(board.versionCodename)); BoxesFill(); + LabelsUpdate(); switch(board.type) { case rpipico: @@ -1268,6 +1304,40 @@ void guiWindow::BoxesFill() BoxesUpdate(); } +// Only runs either on initial load or save +void guiWindow::LabelsUpdate() +{ + // because inputsMap uses pin no. starting from 0 + for(uint8_t i = 0; i < 16; i++) { + if(i < 14) { + if(inputsMap[i] >= 0) { + testLabel[i]->setText(valuesNameList[i+1]); + testLabel[i]->setEnabled(true); + } else { + testLabel[i]->setText(valuesNameList[i+1] + " (N/C)"); + testLabel[i]->setEnabled(false); + } + } else if(i == 14) { + if(inputsMap[tempPin-1] >= 0) { + testLabel[i]->setText("Temp:"); + testLabel[i]->setEnabled(true); + } else { + testLabel[i]->setText("Temp (N/C)"); + testLabel[i]->setEnabled(false); + } + } else if(i == 15) { + if(inputsMap[analogX-1] >=0 && inputsMap[analogY-1] >= 0) { + testLabel[i]->setText("Analog"); + testLabel[i]->setEnabled(true); + } else { + testLabel[i]->setText("Analog (N/C)"); + testLabel[i]->setEnabled(false); + } + } + + } +} + void guiWindow::pinBoxes_activated(int index) { // Demultiplexing to figure out which "pin" this combobox that's calling correlates to. @@ -1636,96 +1706,36 @@ void guiWindow::serialPort_readyRead() QString idleBuffer = serialPort.readLine(); if(idleBuffer.contains("Pressed:")) { uint8_t button = idleBuffer.trimmed().right(2).toInt(); - switch(button) { - case btnTrigger: - ui->btnTriggerLabel->setText("Trigger"); - break; - case btnGunA: - ui->btnALabel->setText("Button A"); - break; - case btnGunB: - ui->btnBLabel->setText("Button B"); - break; - case btnGunC: - ui->btnCLabel->setText("Button C"); - break; - case btnStart: - ui->btnStartLabel->setText("Start"); - break; - case btnSelect: - ui->btnSelectLabel->setText("Select"); - break; - case btnGunUp: - ui->btnGunUpLabel->setText("Up"); - break; - case btnGunDown: - ui->btnGunDownLabel->setText("Down"); - break; - case btnGunLeft: - ui->btnGunLeftLabel->setText("Left"); - break; - case btnGunRight: - ui->btnGunRightLabel->setText("Right"); - break; - case btnPedal: - ui->btnPedalLabel->setText("Pedal"); - break; - case btnPedal2: - ui->btnPedal2Label->setText("Alt Pedal"); - break; - case btnHome: - ui->btnHomeLabel->setText("Home"); - break; - case btnPump: - ui->btnPumpLabel->setText("Pump Action"); - break; - } + testLabel[button-1]->setText(QString("%1").arg(valuesNameList[button])); } else if(idleBuffer.contains("Released:")) { uint8_t button = idleBuffer.trimmed().right(2).toInt(); - switch(button) { - case btnTrigger: - ui->btnTriggerLabel->setText("Trigger"); - break; - case btnGunA: - ui->btnALabel->setText("Button A"); - break; - case btnGunB: - ui->btnBLabel->setText("Button B"); - break; - case btnGunC: - ui->btnCLabel->setText("Button C"); - break; - case btnStart: - ui->btnStartLabel->setText("Start"); - break; - case btnSelect: - ui->btnSelectLabel->setText("Select"); - break; - case btnGunUp: - ui->btnGunUpLabel->setText("Up"); - break; - case btnGunDown: - ui->btnGunDownLabel->setText("Down"); - break; - case btnGunLeft: - ui->btnGunLeftLabel->setText("Left"); - break; - case btnGunRight: - ui->btnGunRightLabel->setText("Right"); - break; - case btnPedal: - ui->btnPedalLabel->setText("Pedal"); - break; - case btnPedal2: - ui->btnPedal2Label->setText("Alt Pedal"); - break; - case btnHome: - ui->btnHomeLabel->setText("Home"); - break; - case btnPump: - ui->btnPumpLabel->setText("Pump Action"); - break; + testLabel[button-1]->setText(valuesNameList[button]); + } else if(idleBuffer.contains("Temperature:")) { + uint8_t temp = idleBuffer.trimmed().right(2).toInt(); + if(temp > 70) { + testLabel[14]->setText(QString("Temp: %1").arg(temp)); + } else if(temp > 60) { + testLabel[14]->setText(QString("Temp: %1").arg(temp)); + } else { + testLabel[14]->setText(QString("Temp: %1").arg(temp)); + } + } else if(idleBuffer.contains("Analog:")) { + uint8_t analogDir = idleBuffer.trimmed().right(1).toInt(); + if(analogDir) { + switch(analogDir) { + case 1: testLabel[15]->setText("Analog 🡹"); break; + case 2: testLabel[15]->setText("Analog 🡼"); break; + case 3: testLabel[15]->setText("Analog 🡸"); break; + case 4: testLabel[15]->setText("Analog 🡿"); break; + case 5: testLabel[15]->setText("Analog 🡻"); break; + case 6: testLabel[15]->setText("Analog 🡾"); break; + case 7: testLabel[15]->setText("Analog 🡺"); break; + case 8: testLabel[15]->setText("Analog 🡽"); break; + } + } else { + testLabel[15]->setText("Analog"); } + // no idea here lol } else if(idleBuffer.contains("Profile: ")) { uint8_t selection = idleBuffer.trimmed().right(1).toInt(); if(selection != board.selectedProfile) { diff --git a/guiwindow.h b/guiwindow.h index 1f2fc8d..8463201 100644 --- a/guiwindow.h +++ b/guiwindow.h @@ -150,10 +150,10 @@ private slots: "Rumble Switch", "Solenoid Switch", "Autofire Switch", + "External NeoPixel", "RGB LED Red", "RGB LED Green", "RGB LED Blue", - "External NeoPixel", "Camera SDA", "Camera SCL", "Peripherals SDA", @@ -224,6 +224,8 @@ private slots: void BoxesUpdate(); + void LabelsUpdate(); + void DiffUpdate(); void PopupWindow(QString errorTitle, QString errorMessage, QString windowTitle, int errorType); diff --git a/guiwindow.ui b/guiwindow.ui index 7d2cedb..11a157d 100644 --- a/guiwindow.ui +++ b/guiwindow.ui @@ -1053,286 +1053,7 @@ Buttons Tester - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Right - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Button A - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Up - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - 5 - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Button C - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - true - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Trigger - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - 5 - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Left - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Down - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Button B - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Start - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Select - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Home - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Pump Action - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Pedal - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - 5 - - - - - - - QFrame::Shape::Box - - - QFrame::Shadow::Raised - - - Alt Pedal - - - Qt::TextFormat::RichText - - - Qt::AlignCenter - - - - + From 3b0db4295dbfb42d09e60a7123b9b8b714e37469 Mon Sep 17 00:00:00 2001 From: That One Seong <7321839+SeongGino@users.noreply.github.com> Date: Tue, 21 May 2024 01:39:13 +0000 Subject: [PATCH 17/24] how tf do I keep doing this??? The defaults load wasn't loading because it was in the switch-case but not actually in a case... GUH --- guiwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 0339e9c..bbf6d59 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -567,12 +567,12 @@ void guiWindow::BoxesUpdate() } break; } + } for(uint8_t i = 0; i < 30; i++) { if(currentPins[i] > btnUnmapped) { inputsMap[currentPins[i]-1] = i; } } - } } } From cd9a7634a97cd6da43bcf5b80a8ddf3852655d98 Mon Sep 17 00:00:00 2001 From: That One Seong Date: Sun, 26 May 2024 18:24:21 +0000 Subject: [PATCH 18/24] Pico W support, other fixes Sorry Waveshare, it's not you it's me + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/constants.h b/constants.h index 78d907d..0257dab 100644 --- a/constants.h +++ b/constants.h @@ -23,6 +23,7 @@ enum boardTypes_e { nothing = 0, rpipico, + rpipicow, adafruitItsyRP2040, adafruitKB2040, arduinoNanoRP2040, diff --git a/guiwindow.cpp b/guiwindow.cpp index bbf6d59..70e0701 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -443,12 +443,16 @@ bool guiWindow::SerialInit(int portNum) buffer = serialPort.readLine().trimmed(); if(buffer == "rpipico") { board.type = rpipico; + } else if(buffer == "rpipicow") { + board.type = rpipicow; } else if(buffer == "adafruitItsyRP2040") { board.type = adafruitItsyRP2040; } else if(buffer == "adafruitKB2040") { board.type = adafruitKB2040; } else if(buffer == "arduinoNanoRP2040") { board.type = arduinoNanoRP2040; + } else if(buffer == "waveshareZero") { + board.type = waveshareZero; } else { board.type = generic; } @@ -527,47 +531,40 @@ void guiWindow::BoxesUpdate() return; } else { switch(board.type) { + // pico and w are the same physical board, so why need a new layout for it? case rpipico: + case rpipicow: { - for(uint8_t i = 0; i < 30; i++) { - currentPins[i] = rpipicoLayout[i].pinAssignment; - pinBoxes[i]->setCurrentIndex(currentPins[i]); - pinBoxesOldIndex[i] = currentPins[i]; - pinBoxes[i]->setEnabled(false); - } + for(uint8_t i = 0; i < 30; i++) { currentPins[i] = rpipicoLayout[i].pinAssignment; } break; } case adafruitItsyRP2040: { - for(uint8_t i = 0; i < 30; i++) { - currentPins[i] = adafruitItsyRP2040Layout[i].pinAssignment; - pinBoxes[i]->setCurrentIndex(currentPins[i]); - pinBoxesOldIndex[i] = currentPins[i]; - pinBoxes[i]->setEnabled(false); - } + for(uint8_t i = 0; i < 30; i++) { currentPins[i] = adafruitItsyRP2040Layout[i].pinAssignment; } break; } case adafruitKB2040: { - for(uint8_t i = 0; i < 30; i++) { - currentPins[i] = adafruitKB2040Layout[i].pinAssignment; - pinBoxes[i]->setCurrentIndex(currentPins[i]); - pinBoxesOldIndex[i] = currentPins[i]; - pinBoxes[i]->setEnabled(false); - } + for(uint8_t i = 0; i < 30; i++) { currentPins[i] = adafruitKB2040Layout[i].pinAssignment; } break; } case arduinoNanoRP2040: { - for(uint8_t i = 0; i < 30; i++) { - currentPins[i] = arduinoNanoRP2040Layout[i].pinAssignment; - pinBoxes[i]->setCurrentIndex(currentPins[i]); - pinBoxesOldIndex[i] = currentPins[i]; - pinBoxes[i]->setEnabled(false); - } + for(uint8_t i = 0; i < 30; i++) { currentPins[i] = arduinoNanoRP2040Layout[i].pinAssignment; } + break; + } + case waveshareZero: + { + for(uint8_t i = 0; i < 30; i++) { currentPins[i] = waveshareZeroLayout[i].pinAssignment; } break; } } + + for(uint8_t i = 0; i < 30; i++) { + pinBoxes[i]->setCurrentIndex(currentPins[i]); + pinBoxesOldIndex[i] = currentPins[i]; + pinBoxes[i]->setEnabled(false); + } for(uint8_t i = 0; i < 30; i++) { if(currentPins[i] > btnUnmapped) { inputsMap[currentPins[i]-1] = i; @@ -695,6 +692,9 @@ QString PrettifyName() case rpipico: name = name + " | Raspberry Pi Pico"; break; + case rpipicow: + name = name + " | Raspberry Pi Pico W"; + break; case adafruitItsyRP2040: name = name + " | Adafruit ItsyBitsy RP2040"; break; @@ -973,6 +973,64 @@ void guiWindow::on_comPortSelector_currentIndexChanged(int index) centerPic->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); break; } + case rpipicow: + { + centerPic = new QSvgWidget(":/boardPics/picow.svg"); + QSvgRenderer *picRenderer = centerPic->renderer(); + picRenderer->setAspectRatioMode(Qt::KeepAspectRatio); + ui->boardLabel->setText(PrettifyName()); + + // left side + PinsLeft->addWidget(padding[0], 0, 0); // padding + PinsLeft->addWidget(pinBoxes[0], 1, 0), PinsLeft->addWidget(pinLabel[0], 1, 1); + PinsLeft->addWidget(pinBoxes[1], 2, 0), PinsLeft->addWidget(pinLabel[1], 2, 1); + PinsLeft->addWidget(padding[1], 3, 0); // gnd + PinsLeft->addWidget(pinBoxes[2], 4, 0), PinsLeft->addWidget(pinLabel[2], 4, 1); + PinsLeft->addWidget(pinBoxes[3], 5, 0), PinsLeft->addWidget(pinLabel[3], 5, 1); + PinsLeft->addWidget(pinBoxes[4], 6, 0), PinsLeft->addWidget(pinLabel[4], 6, 1); + PinsLeft->addWidget(pinBoxes[5], 7, 0), PinsLeft->addWidget(pinLabel[5], 7, 1); + PinsLeft->addWidget(padding[2], 8, 0); // gnd + PinsLeft->addWidget(pinBoxes[6], 9, 0), PinsLeft->addWidget(pinLabel[6], 9, 1); + PinsLeft->addWidget(pinBoxes[7], 10, 0), PinsLeft->addWidget(pinLabel[7], 10, 1); + PinsLeft->addWidget(pinBoxes[8], 11, 0), PinsLeft->addWidget(pinLabel[8], 11, 1); + PinsLeft->addWidget(pinBoxes[9], 12, 0), PinsLeft->addWidget(pinLabel[9], 12, 1); + PinsLeft->addWidget(padding[3], 13, 0); // gnd + PinsLeft->addWidget(pinBoxes[10], 14, 0), PinsLeft->addWidget(pinLabel[10], 14, 1); + PinsLeft->addWidget(pinBoxes[11], 15, 0), PinsLeft->addWidget(pinLabel[11], 15, 1); + PinsLeft->addWidget(pinBoxes[12], 16, 0), PinsLeft->addWidget(pinLabel[12], 16, 1); + PinsLeft->addWidget(pinBoxes[13], 17, 0), PinsLeft->addWidget(pinLabel[13], 17, 1); + PinsLeft->addWidget(padding[4], 18, 0); // gnd + PinsLeft->addWidget(pinBoxes[14], 19, 0), PinsLeft->addWidget(pinLabel[14], 19, 1); + PinsLeft->addWidget(pinBoxes[15], 20, 0), PinsLeft->addWidget(pinLabel[15], 20, 1); + + // right side + PinsRight->addWidget(padding[5], 0, 1); // padding + PinsRight->addWidget(padding[6], 1, 1); // VBUS + PinsRight->addWidget(padding[7], 2, 1); // VSYS + PinsRight->addWidget(padding[8], 3, 1); // gnd + PinsRight->addWidget(padding[9], 4, 1); // 3V3 EN + PinsRight->addWidget(padding[10], 5, 1); // 3V3 OUT + PinsRight->addWidget(padding[11], 6, 1); // ADC VREF + PinsRight->addWidget(pinBoxes[28], 7, 1), PinsRight->addWidget(pinLabel[28], 7, 0); + PinsRight->addWidget(padding[12], 8, 1); // gnd + PinsRight->addWidget(pinBoxes[27], 9, 1), PinsRight->addWidget(pinLabel[27], 9, 0); + PinsRight->addWidget(pinBoxes[26], 10, 1), PinsRight->addWidget(pinLabel[26], 10, 0); + PinsRight->addWidget(padding[13], 11, 1); // RUN + PinsRight->addWidget(pinBoxes[22], 12, 1), PinsRight->addWidget(pinLabel[22], 12, 0); + PinsRight->addWidget(padding[14], 13, 1); // gnd + PinsRight->addWidget(pinBoxes[21], 14, 1), PinsRight->addWidget(pinLabel[21], 14, 0); + PinsRight->addWidget(pinBoxes[20], 15, 1), PinsRight->addWidget(pinLabel[20], 15, 0); + PinsRight->addWidget(pinBoxes[19], 16, 1), PinsRight->addWidget(pinLabel[19], 16, 0); + PinsRight->addWidget(pinBoxes[18], 17, 1), PinsRight->addWidget(pinLabel[18], 17, 0); + PinsRight->addWidget(padding[17], 18, 1); // gnd + PinsRight->addWidget(pinBoxes[17], 19, 1), PinsRight->addWidget(pinLabel[17], 19, 0); + PinsRight->addWidget(pinBoxes[16], 20, 1), PinsRight->addWidget(pinLabel[16], 20, 0); + + // center + PinsCenter->addWidget(centerPic); + centerPic->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + break; + } case adafruitItsyRP2040: { centerPic = new QSvgWidget(":/boardPics/adafruitItsy2040.svg"); diff --git a/vectors.qrc b/vectors.qrc index d377be3..474e0f9 100644 --- a/vectors.qrc +++ b/vectors.qrc @@ -6,6 +6,7 @@ boardPics/unknown.svg boardPics/adafruitKB2040.svg boardPics/waveshareZero.svg + boardPics/picow.svg ico/openfire.png From d3f4567d5867c7b09f7c5907e423e218a979046d Mon Sep 17 00:00:00 2001 From: That One Seong Date: Mon, 27 May 2024 14:57:25 +0000 Subject: [PATCH 19/24] Clarify common sync error messages --- guiwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 70e0701..587c040 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -411,7 +411,7 @@ void guiWindow::SerialLoad() } serialActive = false; } else { - PopupWindow("Data hasn't arrived!", "Device was detected, but settings request wasn't received in time!\nThis can happen if the app was closed in the middle of an operation.\n\nTry selecting the device again.", "Oops!", 4); + PopupWindow("Data hasn't arrived!", "Device was detected, but settings request wasn't received in time!\nThis can happen if the app was closed in the middle of an operation.\n\nTry selecting the device again.", "Sync Error!", 4); //qDebug() << "Didn't receive any data in time! Dammit Seong, you jiggled the cable too much again!"; } } else { @@ -481,7 +481,7 @@ bool guiWindow::SerialInit(int portNum) return false; } } else { - PopupWindow("Data hasn't arrived!", "Device was detected, but initial settings request wasn't received in time!\nThis can happen if the app was unexpectedly closed and the gun is in a stale docked state.\n\nTry selecting the device again.", "Oops!", 3); + PopupWindow("Data hasn't arrived! (Stale state?)", "Device was detected, but initial settings request wasn't received in time!\nThis can happen if the app was unexpectedly closed and the gun is in a stale docked state.\n\nTry selecting the device again.", "Sync Error!", 3); qDebug() << "Didn't receive any data in time! Dammit Seong, you jiggled the cable too much again!"; return false; } @@ -490,7 +490,7 @@ bool guiWindow::SerialInit(int portNum) return false; } } else { - PopupWindow("Couldn't open port!", "This usually indicates that the port is being used by something else, e.g. Arduino IDE's serial monitor, or another command line app (stty, screen).\n\nPlease close the offending application and try selecting this port again.", "Oops!", 3); + PopupWindow("Serial port is blocked!", "This usually indicates that the port is being used by something else, e.g. Arduino IDE's serial monitor, or another command line app (stty, screen).\n\nPlease close the offending application and try selecting this port again.", "Port In Use!", 3); return false; } } From 3c32f9876f09de816969aba9fff77bdae461d125 Mon Sep 17 00:00:00 2001 From: That One Seong Date: Sat, 1 Jun 2024 17:09:38 +0000 Subject: [PATCH 20/24] "Good" temp uses slightly darker shade of green for readability on light themes --- guiwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index 587c040..ab846b4 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -1775,7 +1775,7 @@ void guiWindow::serialPort_readyRead() } else if(temp > 60) { testLabel[14]->setText(QString("Temp: %1").arg(temp)); } else { - testLabel[14]->setText(QString("Temp: %1").arg(temp)); + testLabel[14]->setText(QString("Temp: %1").arg(temp)); } } else if(idleBuffer.contains("Analog:")) { uint8_t analogDir = idleBuffer.trimmed().right(1).toInt(); From 44b4d0d6a440df8bdb20df6362dcc58173793d11 Mon Sep 17 00:00:00 2001 From: That One Seong Date: Sun, 2 Jun 2024 14:09:09 +0000 Subject: [PATCH 21/24] README updates --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d3a78ed..040e257 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,19 @@ ### Reference configuration utility for the [OpenFIRE light gun system](https://github.com/TeamOpenFIRE/OpenFIRE-Firmware), written in Qt & C++. ## Features: - - Cross-platform QT application, portable across desktops to Pis and other OSes. - - Simple to use: select the gun from the dropdown, and configure away! - - See and manage current pins layout, toggle on and off custom mappings, set other tunables, and change the gun's USB identifier (with built-in decimal-to-hex conversion for your convenience!). - - Also serves as a testing utility for button input and solenoid/rumble force feedback. + - **Cross-platform Qt application,** portable across Linux & Windows desktops & Raspberry Pi systems. + - **Simple to use:** select the gun from the dropdown, and configure away! + - See and manage current pins layout, toggle on and off custom mappings, set other tunables, and change the gun's USB identifier (with built-in decimal-to-hex conversion for your convenience!) all on the fly. + - Also serves as a testing utility for button input, solenoid/rumble force feedback, and camera. ## Running: Boards flashed with OpenFIRE *must be plugged in **before** launching the application.* The app will notify if it can't find any compatible boards connected. ### For Linux: ##### Requirements: Anything with QT5 support. - - Arch Linux: TODO: Aur PKGBUILD when public + - Arch Linux: **TODO: Aur PKGBUILD when public** - Other distros: Try the latest binary (built for Ubuntu 20.04 LTS, but should work for most distros?) - - Make sure your user is part of the `dialout` group (`# usermod -a -G dialout insertusernamehere`) + - Make sure your user is part of the `dialout` group (`# usermod -a -G dialout insertusernamehere`); you'll be notified on startup if this is necessary. ### For Windows: ##### Requirements: Windows 7 and up (64-bit only). @@ -26,7 +26,8 @@ Boards flashed with OpenFIRE *must be plugged in **before** launching the applic ## Building: ### For Linux: -#### Requires `qt-base`, `qt-serialport`, `qt-svg` +#### Arch: requires `qt-base` `qt-serialport` `qt-svg` +#### Debian: requires `qttools5-dev` `libqt5serialport-dev` `libqt5svg-dev` (for Qt5) - Clone the repo: ``` git clone https://github.com/TeamOpenFIRE/OpenFIRE-App @@ -34,7 +35,7 @@ Boards flashed with OpenFIRE *must be plugged in **before** launching the applic - Setup build directory: ``` cd OpenFIRE-App - mkdir build && mkdir build + mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release ``` - Make: @@ -46,15 +47,14 @@ Boards flashed with OpenFIRE *must be plugged in **before** launching the applic ./OpenFIREapp ``` ### For Windows: - - Should be buildable through CMake or the QT Creator IDE. -###### TODO: any specific instructions for this? Seong doesn't use Windows. +#### Requires the Qt SerialPort extension to be installed from the Qt Installation Wizard or Qt Maintenance Tool + - Should be buildable through CMake w/ msys2 (follow Arch Linux instructions) or the Qt Creator IDE. +###### TODO: check for accuracy ### TODO: - Implement version comparison to latest OpenFIRE GitHub release (or at least latest as of the GUI version). - Add one-click firmware installation/updating from GUI (for both already flashed guns AND RP2040 devices in bootloader mode). - Add radio buttons for preset TinyUSB Identifier settings (for the extra picky distros that depend on set PIDs or names...) - - Add icon, logo. - - Layouts code should be prettier, though Qt really encourages wanton destruction of objects just to clear pages. ### Special Thanks: - Samuel Ballentyne, Prow7, and co. for their work on the SAMCO system that lead to the creation of GUN4ALL & OpenFIRE. From 696302a9d371a2d29a4d34069666106837e6ef4d Mon Sep 17 00:00:00 2001 From: That One Seong Date: Mon, 3 Jun 2024 12:19:45 -0400 Subject: [PATCH 22/24] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 040e257..3fa27e3 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,6 @@ Boards flashed with OpenFIRE *must be plugged in **before** launching the applic - Add radio buttons for preset TinyUSB Identifier settings (for the extra picky distros that depend on set PIDs or names...) ### Special Thanks: - - Samuel Ballentyne, Prow7, and co. for their work on the SAMCO system that lead to the creation of GUN4ALL & OpenFIRE. - - ArcadeForums posters that voiced their thoughts and suggestions for the GUN4ALL project. - - GUN4ALL testers and everyone that provided feedback to GUN4ALL. + * Samuel Ballentyne, Mike Lynch/Prow7, and co. for their work on the SAMCO system & derivatives, and supporting work and conception of OpenFIRE. + * Odwalla-J, mykylegp, SawdustMaker & lemmingDev for prerelease consultation, bug testing and feedback. + * All early IR-GUN4ALL testers and ArcadeForums users whom provided early testing and feedback. From dcca30a2cf549b7a1aaf275e2bedc1139a44de64 Mon Sep 17 00:00:00 2001 From: That One Seong Date: Wed, 5 Jun 2024 06:58:25 +0000 Subject: [PATCH 23/24] Adds NeoPixel Static settings, LED tests, & Simple TUSB identifiers Pixel Static settings is now properly exposed to the user with a similar paradigm to the selectable profile colors in the profiles tab. LED tests were a requested thing during the whole Pixel troubleshooting debacle. Simple TUSB identifier presets should have a certain someone chill the fuck out at least. --- constants.h | 14 +- guiwindow.cpp | 315 ++++++++++++++++++--- guiwindow.h | 37 ++- guiwindow.ui | 764 +++++++++++++++++++++++++++++++++----------------- 4 files changed, 832 insertions(+), 298 deletions(-) diff --git a/constants.h b/constants.h index 0257dab..7c407c7 100644 --- a/constants.h +++ b/constants.h @@ -28,6 +28,7 @@ enum boardTypes_e { adafruitKB2040, arduinoNanoRP2040, waveshareZero, + vccgndYD, generic = 255 }; @@ -64,7 +65,8 @@ enum boardInputs_e { battery, analogX, analogY, - tempPin + tempPin, + boardInputsCount }; enum boolTypes_e { @@ -76,7 +78,8 @@ enum boolTypes_e { holdToPause, commonAnode, lowButtonsMode, - rumbleFF + rumbleFF, + boolTypesCount }; enum settingsTypes_e { @@ -87,7 +90,12 @@ enum settingsTypes_e { solenoidHoldLength, autofireWaitFactor, holdToPauseLength, - customLEDcount + customLEDcount, + customLEDstatic, + customLEDcolor1, + customLEDcolor2, + customLEDcolor3, + settingsTypesCount }; enum pinTypes_e { diff --git a/guiwindow.cpp b/guiwindow.cpp index ab846b4..a452da2 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -53,7 +53,6 @@ QVector profilesTable_orig(PROFILES_COUNT); // Values: -2 = N/A, -1 = reserved, 0 = available, unused QMap currentPins; -#define INPUTS_COUNT 31 // Map of what inputs are put where, // Key = button/output, Value = pin number occupying, if any. // Value of -1 means unmapped. @@ -154,7 +153,7 @@ guiWindow::guiWindow(QWidget *parent) connect(&serialPort, &QSerialPort::readyRead, this, &guiWindow::serialPort_readyRead); // just to be sure, init the inputsMap hashes - for(uint8_t i = 0; i < INPUTS_COUNT; i++) { + for(uint8_t i = 0; i < boardInputsCount-1; i++) { inputsMap[i] = -1; inputsMap_orig[i] = -1; } @@ -295,6 +294,9 @@ guiWindow::guiWindow(QWidget *parent) // TODO: is there a way of dynamically scaling QGraphicsViews? ui->testView->scale(0.5, 0.5); + // hiding tUSB elements by default since this can't be done from default + ui->tUSBLayoutAdvanced->setVisible(false); + // Finally get to the thing! aliveTimer = new QTimer(); connect(aliveTimer, &QTimer::timeout, this, &guiWindow::aliveTimer_timeout); @@ -359,15 +361,16 @@ void guiWindow::SerialLoad() if(serialPort.waitForReadyRead(2000)) { // booleans QString buffer; - for(uint8_t i = 0; i < sizeof(boolSettings); i++) { + for(uint8_t i = 0; i < boolTypesCount; i++) { boolSettings[i] = serialPort.readLine().trimmed().toInt(); boolSettings_orig[i] = boolSettings[i]; } + // pins if(boolSettings[customPins]) { serialPort.write("Xlp"); serialPort.waitForReadyRead(1000); - for(uint8_t i = 0; i < INPUTS_COUNT; i++) { + for(uint8_t i = 0; i < boardInputsCount-1; i++) { inputsMap_orig[i] = serialPort.readLine().trimmed().toInt(); // For some reason, QTSerial drops output shortly after this. // So we send a ping to refill the buffer. @@ -378,19 +381,16 @@ void guiWindow::SerialLoad() } inputsMap = inputsMap_orig; } + // settings serialPort.write("Xls"); serialPort.waitForBytesWritten(2000); serialPort.waitForReadyRead(2000); - for(uint8_t i = 0; i < sizeof(settingsTable) / 2; i++) { + for(uint8_t i = 0; i < settingsTypesCount; i++) { settingsTable[i] = serialPort.readLine().trimmed().toInt(); settingsTable_orig[i] = settingsTable[i]; } - // TODO: this is for customLEDstatic and customLEDcolors1-3 - for(uint8_t i = 0; i < 4; i++) { - // nomf'd for now - buffer = serialPort.readLine().trimmed(); - } + // profiles for(uint8_t i = 0; i < PROFILES_COUNT; i++) { QString genString = QString("XlP%1").arg(i); @@ -453,6 +453,8 @@ bool guiWindow::SerialInit(int portNum) board.type = arduinoNanoRP2040; } else if(buffer == "waveshareZero") { board.type = waveshareZero; + } else if(buffer == "vccgndYD") { + board.type = vccgndYD; } else { board.type = generic; } @@ -521,7 +523,7 @@ void guiWindow::BoxesUpdate() for(uint8_t i = 0; i < 30; i++) { pinBoxes[i]->setEnabled(true); } - for(uint8_t i = 0; i < INPUTS_COUNT; i++) { + for(uint8_t i = 0; i < boardInputsCount-1; i++) { if(inputsMap.value(i) >= 0) { currentPins[inputsMap.value(i)] = i+1; pinBoxes[inputsMap.value(i)]->setCurrentIndex(currentPins[inputsMap.value(i)]); @@ -585,12 +587,12 @@ void guiWindow::DiffUpdate() settingsDiff++; } } - for(uint8_t i = 1; i < sizeof(boolSettings); i++) { + for(uint8_t i = 1; i < boolTypesCount; i++) { if(boolSettings_orig[i] != boolSettings[i]) { settingsDiff++; } } - for(uint8_t i = 0; i < sizeof(settingsTable) / 2; i++) { + for(uint8_t i = 0; i < settingsTypesCount; i++) { if(settingsTable_orig[i] != settingsTable[i]) { settingsDiff++; } @@ -651,22 +653,22 @@ void guiWindow::DiffUpdate() void guiWindow::SyncSettings() { - for(uint8_t i = 0; i < sizeof(boolSettings); i++) { + for(uint8_t i = 0; i < boolTypesCount; i++) { boolSettings_orig[i] = boolSettings[i]; } if(boolSettings_orig[customPins]) { inputsMap_orig = inputsMap; } else { - for(uint8_t i = 0; i < INPUTS_COUNT; i++) + for(uint8_t i = 0; i < boardInputsCount-1; i++) inputsMap_orig[i] = -1; } - for(uint8_t i = 0; i < sizeof(settingsTable) / 2; i++) { + for(uint8_t i = 0; i < settingsTypesCount; i++) { settingsTable_orig[i] = settingsTable[i]; } tinyUSBtable_orig.tinyUSBid = tinyUSBtable.tinyUSBid; tinyUSBtable_orig.tinyUSBname = tinyUSBtable.tinyUSBname; board.previousProfile = board.selectedProfile; - for(uint8_t i = 0; i < 4; i++) { + for(uint8_t i = 0; i < PROFILES_COUNT; i++) { profilesTable_orig[i].irSensitivity = profilesTable[i].irSensitivity; profilesTable_orig[i].runMode = profilesTable[i].runMode; profilesTable_orig[i].layoutType = profilesTable[i].layoutType; @@ -740,17 +742,17 @@ void guiWindow::on_confirmButton_clicked() ui->confirmButton->setEnabled(false); QStringList serialQueue; - for(uint8_t i = 0; i < sizeof(boolSettings); i++) { + for(uint8_t i = 0; i < boolTypesCount; i++) { serialQueue.append(QString("Xm.0.%1.%2").arg(i).arg(boolSettings[i])); } if(boolSettings[customPins]) { - for(uint8_t i = 0; i < INPUTS_COUNT; i++) { + for(uint8_t i = 0; i < boardInputsCount-1; i++) { serialQueue.append(QString("Xm.1.%1.%2").arg(i).arg(inputsMap.value(i))); } } - for(uint8_t i = 0; i < sizeof(settingsTable) / 2; i++) { + for(uint8_t i = 0; i < settingsTypesCount; i++) { serialQueue.append(QString("Xm.2.%1.%2").arg(i).arg(settingsTable[i])); } @@ -1296,13 +1298,54 @@ void guiWindow::on_comPortSelector_currentIndexChanged(int index) ui->rumbleIntensityBox->setValue(settingsTable[rumbleStrength]); ui->rumbleLengthBox->setValue(settingsTable[rumbleInterval]); ui->holdToPauseLengthBox->setValue(settingsTable[holdToPauseLength]); - ui->neopixelStrandLengthBox->setValue(settingsTable[customLEDcount]); ui->solenoidNormalIntervalBox->setValue(settingsTable[solenoidNormalInterval]); ui->solenoidFastIntervalBox->setValue(settingsTable[solenoidFastInterval]); ui->solenoidHoldLengthBox->setValue(settingsTable[solenoidHoldLength]); ui->autofireWaitFactorBox->setValue(settingsTable[autofireWaitFactor]); ui->productIdInput->setText(tinyUSBtable.tinyUSBid); ui->productNameInput->setText(tinyUSBtable.tinyUSBname); + if(inputsMap[neoPixel-1] >= 0) { ui->neopixelGroupBox->setEnabled(true); } else { ui->neopixelGroupBox->setEnabled(false); } + ui->neopixelStrandLengthBox->setValue(settingsTable[customLEDcount]); + ui->customLEDstaticSpinbox->setValue(settingsTable[customLEDstatic]); + ui->customLEDstaticBtn1->setStyleSheet(QString("background-color: #%1").arg(settingsTable[customLEDcolor1], 6, 16, QLatin1Char('0'))); + ui->customLEDstaticBtn2->setStyleSheet(QString("background-color: #%1").arg(settingsTable[customLEDcolor2], 6, 16, QLatin1Char('0'))); + ui->customLEDstaticBtn3->setStyleSheet(QString("background-color: #%1").arg(settingsTable[customLEDcolor3], 6, 16, QLatin1Char('0'))); + + switch(tinyUSBtable.tinyUSBid.toInt()) { + case 1: + ui->tUSB_p1->setChecked(true); + ui->tUSBLayoutAdvanced->setVisible(false); + ui->tUSBLayoutSimple->setVisible(true); + ui->tinyUSBLayoutToggle->setChecked(false); + break; + case 2: + ui->tUSB_p2->setChecked(true); + ui->tUSBLayoutAdvanced->setVisible(false); + ui->tUSBLayoutSimple->setVisible(true); + ui->tinyUSBLayoutToggle->setChecked(false); + break; + case 3: + ui->tUSB_p3->setChecked(true); + ui->tUSBLayoutAdvanced->setVisible(false); + ui->tUSBLayoutSimple->setVisible(true); + ui->tinyUSBLayoutToggle->setChecked(false); + break; + case 4: + ui->tUSB_p4->setChecked(true); + ui->tUSBLayoutAdvanced->setVisible(false); + ui->tUSBLayoutSimple->setVisible(true); + ui->tinyUSBLayoutToggle->setChecked(false); + break; + default: + ui->tUSB_p1->setChecked(false); + ui->tUSB_p2->setChecked(false); + ui->tUSB_p3->setChecked(false); + ui->tUSB_p4->setChecked(false); + ui->tUSBLayoutSimple->setVisible(false); + ui->tUSBLayoutAdvanced->setVisible(true); + ui->tinyUSBLayoutToggle->setChecked(true); + break; + } } } else { ui->boardLabel->clear(); @@ -1392,8 +1435,10 @@ void guiWindow::LabelsUpdate() testLabel[i]->setEnabled(false); } } - } + if(inputsMap[ledR-1] >= 0) { ui->redLedTestBtn->setEnabled(true); } else { ui->redLedTestBtn->setEnabled(false); } + if(inputsMap[ledG-1] >= 0) { ui->greenLedTestBtn->setEnabled(true); } else { ui->greenLedTestBtn->setEnabled(false); } + if(inputsMap[ledB-1] >= 0) { ui->blueLedTestBtn->setEnabled(true); } else { ui->blueLedTestBtn->setEnabled(false); } } void guiWindow::pinBoxes_activated(int index) @@ -1433,6 +1478,7 @@ void guiWindow::pinBoxes_activated(int index) // because "->currentIndex" is already updated, we just update it at the end of activations // to check that we aren't re-selecting the index for that box. pinBoxesOldIndex[pin] = index; + if(inputsMap[neoPixel-1] >= 0) { ui->neopixelGroupBox->setEnabled(true); } else { ui->neopixelGroupBox->setEnabled(false); } DiffUpdate(); } @@ -1629,13 +1675,6 @@ void guiWindow::on_holdToPauseLengthBox_valueChanged(int arg1) } -void guiWindow::on_neopixelStrandLengthBox_valueChanged(int arg1) -{ - settingsTable[customLEDcount] = arg1; - DiffUpdate(); -} - - void guiWindow::on_solenoidNormalIntervalBox_valueChanged(int arg1) { settingsTable[solenoidNormalInterval] = arg1; @@ -1680,25 +1719,106 @@ void guiWindow::on_productIdInput_textChanged(const QString &arg1) } +void guiWindow::on_tUSB_p1_toggled(bool checked) +{ + if(checked) { + tinyUSBtable.tinyUSBid = "1"; + tinyUSBtable.tinyUSBname = "FIRECon P1"; + ui->productIdInput->setText(tinyUSBtable.tinyUSBid); + ui->productNameInput->setText(tinyUSBtable.tinyUSBname); + DiffUpdate(); + } +} + + +void guiWindow::on_tUSB_p2_toggled(bool checked) +{ + if(checked) { + tinyUSBtable.tinyUSBid = "2"; + tinyUSBtable.tinyUSBname = "FIRECon P2"; + ui->productIdInput->setText(tinyUSBtable.tinyUSBid); + ui->productNameInput->setText(tinyUSBtable.tinyUSBname); + DiffUpdate(); + } +} + + +void guiWindow::on_tUSB_p3_toggled(bool checked) +{ + if(checked) { + tinyUSBtable.tinyUSBid = "3"; + tinyUSBtable.tinyUSBname = "FIRECon P3"; + ui->productIdInput->setText(tinyUSBtable.tinyUSBid); + ui->productNameInput->setText(tinyUSBtable.tinyUSBname); + DiffUpdate(); + } +} + + +void guiWindow::on_tUSB_p4_toggled(bool checked) +{ + if(checked) { + tinyUSBtable.tinyUSBid = "4"; + tinyUSBtable.tinyUSBname = "FIRECon P4"; + ui->productIdInput->setText(tinyUSBtable.tinyUSBid); + ui->productNameInput->setText(tinyUSBtable.tinyUSBname); + DiffUpdate(); + } +} + + void guiWindow::on_productIdInput_textEdited(const QString &arg1) { tinyUSBtable.tinyUSBid = arg1; + if(ui->productNameInput->text().isEmpty()) { + switch(tinyUSBtable.tinyUSBid.toInt()) { + case 1: + ui->tUSB_p1->setChecked(true); + break; + case 2: + ui->tUSB_p2->setChecked(true); + break; + case 3: + ui->tUSB_p3->setChecked(true); + break; + case 4: + ui->tUSB_p4->setChecked(true); + break; + default: + ui->tUSB_p1->setChecked(false); + ui->tUSB_p2->setChecked(false); + ui->tUSB_p3->setChecked(false); + ui->tUSB_p4->setChecked(false); + break; + } + } + DiffUpdate(); } void guiWindow::on_productNameInput_textEdited(const QString &arg1) { - // TODO: try this: - // when this is called, iterate through arg1 by length of itself in a loop - // and check if each character of i index is > 255. - // if so, revert to last good name. - // else, make this input the last good name and allow committing. + // TODO: there should be a way of using .toLocal8Bit() and checking if it's undefined, + // as that indicates a character exceeds the normal char size, therefore + // reset the lineEdit's text and don't change. But for now, weh. tinyUSBtable.tinyUSBname = arg1; DiffUpdate(); } +void guiWindow::on_tinyUSBLayoutToggle_stateChanged(int arg1) +{ + if(arg1) { + ui->tUSBLayoutSimple->setVisible(false); + ui->tUSBLayoutAdvanced->setVisible(true); + } else { + ui->tUSBLayoutAdvanced->setVisible(false); + ui->tUSBLayoutSimple->setVisible(true); + } +} + + void guiWindow::selectedProfile_isChecked(bool isChecked) { // apparently we get two signals at once? So just filter for the on. @@ -1721,6 +1841,100 @@ void guiWindow::selectedProfile_isChecked(bool isChecked) } +void guiWindow::on_neopixelStrandLengthBox_valueChanged(int arg1) +{ + settingsTable[customLEDcount] = arg1; + DiffUpdate(); +} + + +void guiWindow::on_customLEDstaticSpinbox_valueChanged(int arg1) +{ + settingsTable[customLEDstatic] = arg1; + if(customLEDstatic) { + switch(arg1) { + case 1: + ui->customLEDstaticBtn1->setEnabled(true); + ui->customLEDstaticBtn2->setEnabled(false); + ui->customLEDstaticBtn3->setEnabled(false); + break; + case 2: + ui->customLEDstaticBtn1->setEnabled(true); + ui->customLEDstaticBtn2->setEnabled(true); + ui->customLEDstaticBtn3->setEnabled(false); + break; + case 3: + ui->customLEDstaticBtn1->setEnabled(true); + ui->customLEDstaticBtn2->setEnabled(true); + ui->customLEDstaticBtn3->setEnabled(true); + default: + ui->customLEDstaticBtn1->setEnabled(false); + ui->customLEDstaticBtn2->setEnabled(false); + ui->customLEDstaticBtn3->setEnabled(false); + break; + } + } + DiffUpdate(); +} + + +void guiWindow::on_customLEDstaticBtn1_clicked() +{ + QColor colorPick = QColorDialog::getColor(); + if(colorPick.isValid()) { + int *red = new int; + int *green = new int; + int *blue = new int; + colorPick.getRgb(red, green, blue); + uint32_t packedColor = 0; + packedColor |= *red << 16; + packedColor |= *green << 8; + packedColor |= *blue; + settingsTable[customLEDcolor1] = packedColor; + ui->customLEDstaticBtn1->setStyleSheet(QString("background-color: #%1").arg(packedColor, 6, 16, QLatin1Char('0'))); + DiffUpdate(); + } +} + + +void guiWindow::on_customLEDstaticBtn2_clicked() +{ + QColor colorPick = QColorDialog::getColor(); + if(colorPick.isValid()) { + int *red = new int; + int *green = new int; + int *blue = new int; + colorPick.getRgb(red, green, blue); + uint32_t packedColor = 0; + packedColor |= *red << 16; + packedColor |= *green << 8; + packedColor |= *blue; + settingsTable[customLEDcolor2] = packedColor; + ui->customLEDstaticBtn2->setStyleSheet(QString("background-color: #%1").arg(packedColor, 6, 16, QLatin1Char('0'))); + DiffUpdate(); + } +} + + +void guiWindow::on_customLEDstaticBtn3_clicked() +{ + QColor colorPick = QColorDialog::getColor(); + if(colorPick.isValid()) { + int *red = new int; + int *green = new int; + int *blue = new int; + colorPick.getRgb(red, green, blue); + uint32_t packedColor = 0; + packedColor |= *red << 16; + packedColor |= *green << 8; + packedColor |= *blue; + settingsTable[customLEDcolor3] = packedColor; + ui->customLEDstaticBtn3->setStyleSheet(QString("background-color: #%1").arg(packedColor, 6, 16, QLatin1Char('0'))); + DiffUpdate(); + } +} + + void guiWindow::on_calib1Btn_clicked() { serialPort.write("XC1C"); @@ -1870,6 +2084,39 @@ void guiWindow::on_solenoidTestBtn_clicked() } +void guiWindow::on_redLedTestBtn_clicked() +{ + serialPort.write("XtR"); + if(!serialPort.waitForBytesWritten(1000)) { + PopupWindow("Lost connection!", "Somehow this happened I guess???", "Oops!", 4); + } else { + ui->statusBar->showMessage("Set LED to Red.", 2500); + } +} + + +void guiWindow::on_greenLedTestBtn_clicked() +{ + serialPort.write("XtG"); + if(!serialPort.waitForBytesWritten(1000)) { + PopupWindow("Lost connection!", "Somehow this happened I guess???", "Oops!", 4); + } else { + ui->statusBar->showMessage("Set LED to Green.", 2500); + } +} + + +void guiWindow::on_blueLedTestBtn_clicked() +{ + serialPort.write("XtB"); + if(!serialPort.waitForBytesWritten(1000)) { + PopupWindow("Lost connection!", "Somehow this happened I guess???", "Oops!", 4); + } else { + ui->statusBar->showMessage("Set LED to Blue.", 2500); + } +} + + void guiWindow::on_testBtn_clicked() { if(serialPort.isOpen()) { diff --git a/guiwindow.h b/guiwindow.h index 8463201..79879ce 100644 --- a/guiwindow.h +++ b/guiwindow.h @@ -18,6 +18,7 @@ #ifndef GUIWINDOW_H #define GUIWINDOW_H +#include "constants.h" #include #include #include @@ -93,8 +94,6 @@ private slots: void on_holdToPauseLengthBox_valueChanged(int arg1); - void on_neopixelStrandLengthBox_valueChanged(int arg1); - void on_solenoidNormalIntervalBox_valueChanged(int arg1); void on_solenoidFastIntervalBox_valueChanged(int arg1); @@ -107,6 +106,8 @@ private slots: void on_productNameInput_textEdited(const QString &arg1); + void on_neopixelStrandLengthBox_valueChanged(int arg1); + void on_clearEepromBtn_clicked(); void on_productIdInput_textChanged(const QString &arg1); @@ -125,6 +126,30 @@ private slots: void on_actionAbout_UI_triggered(); + void on_customLEDstaticSpinbox_valueChanged(int arg1); + + void on_customLEDstaticBtn1_clicked(); + + void on_customLEDstaticBtn2_clicked(); + + void on_customLEDstaticBtn3_clicked(); + + void on_tinyUSBLayoutToggle_stateChanged(int arg1); + + void on_tUSB_p1_toggled(bool checked); + + void on_tUSB_p2_toggled(bool checked); + + void on_tUSB_p3_toggled(bool checked); + + void on_tUSB_p4_toggled(bool checked); + + void on_redLedTestBtn_clicked(); + + void on_greenLedTestBtn_clicked(); + + void on_blueLedTestBtn_clicked(); + private: Ui::guiWindow *ui; @@ -174,14 +199,14 @@ private slots: uint8_t settingsDiff; // Current array of booleans, meant to be used as a bitmask - bool boolSettings[9]; + bool boolSettings[boolTypesCount]; // Array of booleans, as loaded from the gun firmware - bool boolSettings_orig[9]; + bool boolSettings_orig[boolTypesCount]; // Current table of tunable settings - uint16_t settingsTable[8]; + uint32_t settingsTable[settingsTypesCount]; // Table of tunables, as loaded from gun firmware - uint16_t settingsTable_orig[8]; + uint32_t settingsTable_orig[settingsTypesCount]; // because pinBoxes' "->currentIndex" gets updated AFTER calling its activation signal, // we need to save its last index to properly compare and prevent duplicate changes, diff --git a/guiwindow.ui b/guiwindow.ui index 11a157d..92bb74d 100644 --- a/guiwindow.ui +++ b/guiwindow.ui @@ -42,7 +42,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -58,7 +58,7 @@ COM Port: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -85,14 +85,14 @@ - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter - Qt::Horizontal + Qt::Orientation::Horizontal @@ -111,7 +111,7 @@ Qt::TextFormat::PlainText - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -137,20 +137,7 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - + true @@ -191,7 +178,7 @@ Gun Settings - + @@ -214,7 +201,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -257,7 +244,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -274,7 +261,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -314,7 +301,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -331,7 +318,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -364,7 +351,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -379,7 +366,7 @@ - + true @@ -388,35 +375,6 @@ Setting Values - - - - Autofire Wait Factor: - - - Qt::TextFormat::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - Rumble Length: - - - Qt::TextFormat::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -426,23 +384,26 @@ Qt::TextFormat::PlainText - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter true - - - - Solenoid Interval: + + + + <html><head/><body><p>The time between solenoid activations when the trigger is held in <span style=" font-style:italic;">Autofire</span> mode, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 30 (ms)</span></p></body></html> - - Qt::TextFormat::PlainText + + QAbstractSpinBox::ButtonSymbols::PlusMinus - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + ms @@ -455,39 +416,7 @@ Qt::TextFormat::PlainText - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - Hold-to-Pause Length: - - - Qt::TextFormat::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - NeoPixel Strand Length: - - - Qt::TextFormat::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter true @@ -503,7 +432,7 @@ Qt::TextFormat::PlainText - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -521,11 +450,27 @@ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + / 255 + 255 + + + + Rumble Length: + + + Qt::TextFormat::PlainText + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + @@ -537,15 +482,18 @@ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + ms + 9999 - - + + - <html><head/><body><p>When <span style=" font-style:italic;">Hold To Pause</span> mode is enabled, how long (in milliseconds) should the buttons be held before Pause Mode activates?</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 2500 (ms)</span></p></body></html> + <html><head/><body><p>The time between solenoid activations when the trigger is held, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 45 (ms)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -553,15 +501,15 @@ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - 9999 + + ms - - + + - <html><head/><body><p>How many LEDs are in the NeoPixel chain.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 1</span></p></body></html> + <html><head/><body><p>The time it takes to hold the trigger after a <span style=" font-style:italic;">single shot</span> solenoid activation before transitioning to a sustained fire feedback, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 500 (ms)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -569,44 +517,47 @@ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - 1 + + ms - 100 + 9999 - - - - <html><head/><body><p>The time between solenoid activations when the trigger is held, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 45 (ms)</span></p></body></html> + + + + Hold-to-Pause Length: - - QAbstractSpinBox::ButtonSymbols::PlusMinus + + Qt::TextFormat::PlainText - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + true - - - - <html><head/><body><p>The time between solenoid activations when the trigger is held in <span style=" font-style:italic;">Autofire</span> mode, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 30 (ms)</span></p></body></html> + + + + Solenoid Interval: - - QAbstractSpinBox::ButtonSymbols::PlusMinus + + Qt::TextFormat::PlainText - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - - + + - <html><head/><body><p>The time it takes to hold the trigger after a <span style=" font-style:italic;">single shot</span> solenoid activation before transitioning to a sustained fire feedback, in milliseconds.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 500 (ms)</span></p></body></html> + <html><head/><body><p>When <span style=" font-style:italic;">Hold To Pause</span> mode is enabled, how long (in milliseconds) should the buttons be held before Pause Mode activates?</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 2500 (ms)</span></p></body></html> QAbstractSpinBox::ButtonSymbols::PlusMinus @@ -614,12 +565,31 @@ QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + ms + 9999 - + + + + Autofire Wait Factor: + + + Qt::TextFormat::PlainText + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + true + + + + <html><head/><body><p>The multiplier to be applied for the interval between solenoid activations when <span style=" font-style:italic;">Autofire</span> is enabled.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 3(x)</span></p></body></html> @@ -633,6 +603,9 @@ false + + x + 2 @@ -644,140 +617,393 @@
+ + + + NeoPixels + + + + + + + + Qt::Orientation::Horizontal + + + + 0 + 0 + + + + + + + + NeoPixel Strand Length: + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + <html><head/><body><p>How many LEDs are in the NeoPixel chain.</p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 1</span></p></body></html> + + + QAbstractSpinBox::ButtonSymbols::PlusMinus + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + Pixel(s) + + + + + + 1 + + + 100 + + + + + + + Qt::Orientation::Horizontal + + + + 0 + 0 + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 0 + 0 + + + + + + + + Static NeoPixels: + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + <html><head/><body><p>How many LEDs in the NeoPixel chain are statically colored<span style=" font-weight:700;"/>- <span style=" font-weight:700;">these won't be affected by menu elements or MAMEHOOKER LED events.</span></p><p><span style=" font-weight:700; font-style:italic;">Default:</span><span style=" font-style:italic;"> 0</span></p></body></html> + + + Pixel(s) + + + First + + + 3 + + + + + + + + true + false + false + + + + Set the color of the first static NeoPixel. + + + Static Color 1 + + + + + + + + true + + + + Set the color of the second static NeoPixel. + + + Static Color 2 + + + + + + + + true + + + + Set the color of the third static NeoPixel. + + + Static Color 3 + + + + + + + Qt::Orientation::Horizontal + + + + 0 + 0 + + + + + + + + + - - - - 0 - 0 - + + + Qt::Vertical - + - 16777215 - 115 + 20 + 40 + + + + TinyUSB Identifier - - - - - The Product Name that the gun reports to the PC. - - - - - - - - - 15 - - - (15 Characters) - - - - - - - - - - Qt::TextFormat::PlainText - - - - - - - Product ID: + + + + + + false + - - Qt::TextFormat::PlainText + + - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Product Name: - - - Qt::TextFormat::PlainText + Qt::AlignmentFlag::AlignCenter - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + true + + + + + The Product ID that the gun reports when connected to the PC, in decimal (with its hexadecimal equivalent). + + + Qt::InputMethodHint::ImhDigitsOnly + + + + + + + + + 5 + + + (In Decimal) + + + false + + + + + + + + + + Qt::TextFormat::PlainText + + + + + + + The Product Name that the gun reports to the PC. + + + + + + + + + 15 + + + (15 Characters) + + + + + + + Product ID: + + + Qt::TextFormat::PlainText + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + Product Name: + + + Qt::TextFormat::PlainText + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + + + Qt::TextFormat::PlainText + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - The Product ID that the gun reports when connected to the PC, in decimal (with its hexadecimal equivalent). - - - Qt::InputMethodHint::ImhDigitsOnly - - - - - - + + + + + false + - - 5 + + - - (In Decimal) + + Qt::AlignmentFlag::AlignCenter - - false + + true + + + + + P1 + + + + + + + P2 + + + + + + + P3 + + + + + + + P4 + + + + - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - + + - - - - Qt::TextFormat::PlainText + Advanced View @@ -791,7 +1017,7 @@ Saved Profiles - + Calibration Profiles @@ -805,7 +1031,7 @@ - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -822,7 +1048,7 @@ Right - Qt::AlignBottom|Qt::AlignHCenter + Qt::AlignmentFlag::AlignCenter @@ -839,7 +1065,7 @@ Sensitivity - Qt::AlignBottom|Qt::AlignHCenter + Qt::AlignmentFlag::AlignCenter @@ -848,6 +1074,9 @@ + + Qt::AlignmentFlag::AlignCenter + @@ -870,7 +1099,7 @@ Bottom - Qt::AlignBottom|Qt::AlignHCenter + Qt::AlignmentFlag::AlignCenter @@ -880,7 +1109,7 @@ Run Mode - Qt::AlignBottom|Qt::AlignHCenter + Qt::AlignmentFlag::AlignCenter @@ -897,7 +1126,7 @@ TRled - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -916,7 +1145,7 @@ Top - Qt::AlignBottom|Qt::AlignHCenter + Qt::AlignmentFlag::AlignCenter @@ -947,7 +1176,7 @@ Color - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -957,7 +1186,7 @@ Left - Qt::AlignBottom|Qt::AlignHCenter + Qt::AlignmentFlag::AlignCenter @@ -967,7 +1196,7 @@ TLled - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -977,7 +1206,7 @@ Layout - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -1080,6 +1309,31 @@
+ + + + + + Test Red LED + + + + + + + Test Green LED + + + + + + + Test Blue LED + + + + + @@ -1153,7 +1407,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal From 7c8f6e58e5538780ca94d30a7c6e7c82b020d26d Mon Sep 17 00:00:00 2001 From: That One Seong Date: Wed, 5 Jun 2024 07:03:21 +0000 Subject: [PATCH 24/24] Change vendor ID for OpenFIRE to 0xF143 --- guiwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guiwindow.cpp b/guiwindow.cpp index a452da2..8102674 100644 --- a/guiwindow.cpp +++ b/guiwindow.cpp @@ -113,7 +113,7 @@ void guiWindow::PortsSearch() } else { // Yeah, sue me, we reading this backwards to make stack management easier. for(int i = serialFoundList.length() - 1; i >= 0; --i) { - if(serialFoundList[i].vendorIdentifier() == 2336) { + if(serialFoundList[i].vendorIdentifier() == 0xF143) { usbName.prepend(serialFoundList[i].systemLocation()); qDebug() << "Found device @" << serialFoundList[i].systemLocation(); } else {