From 17cc335788b029fdf39c152ded10e5b2b73432fb Mon Sep 17 00:00:00 2001 From: bowman Date: Wed, 22 Jul 2026 19:10:17 -0700 Subject: [PATCH] feat: dedicated OK button for Fnirsi HS-02 The HS-02 has three physical buttons (UP/DOWN/OK) but IronOS is a two-button firmware, so the OK button (PB3) was previously unused. Wire it up as a real independent third button. - Add ButtonState BUTTON_OK_SHORT / BUTTON_OK_LONG. - getButtonState() reads OK as a third bit; OK long-press is one-shot so a "back" action steps up a single level instead of cascading. - getButtonOK() is weakly defined (returns 0 on two-button devices); the Fnirsi BSP overrides it to read PB3. UP/DOWN keep the two logical buttons, so other devices are unaffected. - Per-screen actions (short = enter/confirm/select, long = back): Home : OK enters soldering Soldering : OK opens temp adjust; long returns to home TempAdjust : OK confirms & exits SettingsMenu: OK selects/enters; long goes back one level --- source/Core/BSP/BSP.h | 3 +++ source/Core/BSP/Fnirsi/BSP.cpp | 4 ++++ source/Core/Drivers/Buttons.cpp | 16 ++++++++++++++++ source/Core/Drivers/Buttons.hpp | 2 ++ source/Core/Threads/UI/logic/HomeScreen.cpp | 1 + source/Core/Threads/UI/logic/SettingsMenu.cpp | 6 ++++++ source/Core/Threads/UI/logic/Soldering.cpp | 3 +++ .../Core/Threads/UI/logic/TemperatureAdjust.cpp | 2 ++ 8 files changed, 37 insertions(+) diff --git a/source/Core/BSP/BSP.h b/source/Core/BSP/BSP.h index 37508597c7..962edcddd7 100644 --- a/source/Core/BSP/BSP.h +++ b/source/Core/BSP/BSP.h @@ -48,6 +48,9 @@ uint16_t getInputVoltageX10(uint16_t divisor, uint8_t sample); // !! Returns 1 if held down, 0 if released uint8_t getButtonA(); uint8_t getButtonB(); +// Optional dedicated OK button for 3-button devices (e.g. Fnirsi HS-02). +// Weakly defined to return 0 in Buttons.cpp; devices with a third button override it. +uint8_t getButtonOK(); // This is a work around that will be called if I2C starts to bug out // This should toggle the SCL line until SDA goes high to end the current transaction diff --git a/source/Core/BSP/Fnirsi/BSP.cpp b/source/Core/BSP/Fnirsi/BSP.cpp index 8858fab678..22637adb27 100644 --- a/source/Core/BSP/Fnirsi/BSP.cpp +++ b/source/Core/BSP/Fnirsi/BSP.cpp @@ -107,8 +107,12 @@ void setTipPWM(const uint8_t pulse, const bool shouldUseFastModePWM) { pendingPWM = scaledPWM; } +// The HS-02 has three physical buttons; IronOS's two logical buttons map to UP/DOWN, +// and the dedicated OK button is exposed via getButtonOK() so each UI screen can give +// it a context-appropriate action (see the BUTTON_OK_SHORT handlers in the UI logic). uint8_t getButtonA() { return GPIO_ReadInputDataBit(BUTTON_Port, BUTTON_DOWN_Pin) == Bit_RESET ? 1 : 0; } uint8_t getButtonB() { return GPIO_ReadInputDataBit(BUTTON_Port, BUTTON_UP_Pin) == Bit_RESET ? 1 : 0; } +uint8_t getButtonOK() { return GPIO_ReadInputDataBit(BUTTON_Port, BUTTON_OK_Pin) == Bit_RESET ? 1 : 0; } void BSPInit(void) { switchToFastPWM(); } diff --git a/source/Core/Drivers/Buttons.cpp b/source/Core/Drivers/Buttons.cpp index 8263742713..4f0e4aacbd 100644 --- a/source/Core/Drivers/Buttons.cpp +++ b/source/Core/Drivers/Buttons.cpp @@ -11,6 +11,9 @@ #include TickType_t lastButtonTime = 0; +// Devices without a dedicated OK button fall back to this (no third button). +__attribute__((weak)) uint8_t getButtonOK() { return 0; } + ButtonState getButtonState() { /* * Read in the buttons and then determine if a state change needs to occur @@ -25,11 +28,13 @@ ButtonState getButtonState() { */ static uint8_t previousState = 0; static bool longPressed = false; + static bool okLongFired = false; // OK long-press is one-shot (back = single level) static TickType_t previousStateChange = 0; const TickType_t timeout = TICKS_100MS * 4; uint8_t currentState; currentState = (getButtonA()) << 0; currentState |= (getButtonB()) << 1; + currentState |= (getButtonOK()) << 2; // Dedicated OK button (0 on 2-button devices) if (currentState) { lastButtonTime = xTaskGetTickCount(); @@ -46,6 +51,14 @@ ButtonState getButtonState() { return BUTTON_F_LONG; } else if (currentState == 0x02) { return BUTTON_B_LONG; + } else if (currentState == 0x04) { + // Dedicated OK button held: emit BUTTON_OK_LONG only once per hold so a + // "back one level" action doesn't cascade up multiple levels while held. + if (okLongFired) { + return BUTTON_NONE; + } + okLongFired = true; + return BUTTON_OK_LONG; } else { return BUTTON_BOTH_LONG; // Both being held case } @@ -73,12 +86,15 @@ ButtonState getButtonState() { retVal = BUTTON_F_SHORT; } else if (previousState == 0x02) { retVal = BUTTON_B_SHORT; + } else if (previousState == 0x04) { + retVal = BUTTON_OK_SHORT; // Dedicated OK button pressed } else { retVal = BUTTON_BOTH; // Both being held case } } previousState = 0; longPressed = false; + okLongFired = false; // reset one-shot latch on release } previousStateChange = xTaskGetTickCount(); return retVal; diff --git a/source/Core/Drivers/Buttons.hpp b/source/Core/Drivers/Buttons.hpp index 2e4211182a..2007ce7f30 100644 --- a/source/Core/Drivers/Buttons.hpp +++ b/source/Core/Drivers/Buttons.hpp @@ -18,6 +18,8 @@ enum ButtonState { BUTTON_B_LONG = 8, /* User is holding the back button*/ BUTTON_BOTH = 16, /* User has pressed both buttons*/ BUTTON_BOTH_LONG = 32, /* User is holding both buttons*/ + BUTTON_OK_SHORT = 64, /* User has pressed a dedicated OK button (3-button devices only)*/ + BUTTON_OK_LONG = 128,/* User is holding a dedicated OK button (3-button devices only)*/ /* * Note: diff --git a/source/Core/Threads/UI/logic/HomeScreen.cpp b/source/Core/Threads/UI/logic/HomeScreen.cpp index afc06fa5c5..0c16af44b9 100644 --- a/source/Core/Threads/UI/logic/HomeScreen.cpp +++ b/source/Core/Threads/UI/logic/HomeScreen.cpp @@ -35,6 +35,7 @@ OperatingMode handleHomeButtons(const ButtonState buttons, guiContext *cxt) { return OperatingMode::TemperatureAdjust; #endif break; + case BUTTON_OK_SHORT: // Dedicated OK button enters soldering (same as front-press) case BUTTON_F_SHORT: if (!isTipDisconnected()) { bool detailedView = getSettingValue(SettingsOptions::DetailedIDLE) && getSettingValue(SettingsOptions::DetailedSoldering); diff --git a/source/Core/Threads/UI/logic/SettingsMenu.cpp b/source/Core/Threads/UI/logic/SettingsMenu.cpp index ac68407d55..15862fa66b 100644 --- a/source/Core/Threads/UI/logic/SettingsMenu.cpp +++ b/source/Core/Threads/UI/logic/SettingsMenu.cpp @@ -213,6 +213,12 @@ OperatingMode gui_SettingsMenu(const ButtonState buttons, guiContext *cxt) { case BUTTON_B_SHORT: buttonPress = swapButtonSettings ? BUTTON_F_SHORT : BUTTON_B_SHORT; break; + case BUTTON_OK_SHORT: + buttonPress = BUTTON_F_SHORT; // Dedicated OK button selects/enters, ignoring button swap + break; + case BUTTON_OK_LONG: + buttonPress = BUTTON_BOTH; // OK long-press goes back one level (submenu -> root -> home) + break; default: buttonPress = buttons; break; diff --git a/source/Core/Threads/UI/logic/Soldering.cpp b/source/Core/Threads/UI/logic/Soldering.cpp index e0d078f3a2..ebef23d595 100644 --- a/source/Core/Threads/UI/logic/Soldering.cpp +++ b/source/Core/Threads/UI/logic/Soldering.cpp @@ -56,6 +56,8 @@ OperatingMode handleSolderingButtons(const ButtonState buttons, guiContext *cxt) cxt->scratch_state.state2 = 0; cxt->scratch_state.state1 = 0; break; + case BUTTON_OK_LONG: // Dedicated OK long-press returns to the home screen + /*Fall through*/ case BUTTON_BOTH: /*Fall through*/ case BUTTON_B_LONG: @@ -67,6 +69,7 @@ OperatingMode handleSolderingButtons(const ButtonState buttons, guiContext *cxt) cxt->scratch_state.state2 = 1; } break; + case BUTTON_OK_SHORT: // Dedicated OK button opens temperature adjust case BUTTON_F_SHORT: case BUTTON_B_SHORT: cxt->transitionMode = TransitionAnimation::Left; diff --git a/source/Core/Threads/UI/logic/TemperatureAdjust.cpp b/source/Core/Threads/UI/logic/TemperatureAdjust.cpp index 1e79984b4b..84421c3417 100644 --- a/source/Core/Threads/UI/logic/TemperatureAdjust.cpp +++ b/source/Core/Threads/UI/logic/TemperatureAdjust.cpp @@ -22,6 +22,8 @@ OperatingMode gui_solderingTempAdjust(const ButtonState buttonIn, guiContext *cx // stay (*autoRepeatAcceleration) = 0; break; + case BUTTON_OK_SHORT: // Dedicated OK button confirms & exits (same as both-press) + case BUTTON_OK_LONG: // OK long-press also exits (back to previous screen) case BUTTON_BOTH: // exit saveSettings();