Skip to content

bgaebel/ButtonTransitions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 

Repository files navigation

ButtonTransitions – Hybrid Button State Machine (Arduino)

Hybrid evaluation of exact full-release combos and tap-while-hold gestures. Includes debounced input masks, a short coalescing window for near-simultaneous presses, and optional per-entry conditions.

Author: Björn Gaebel · Date: 2025-09-11 · License: MIT


Features

  • Full-release combos (exact): Execute once when all buttons are released. If multiple thresholds exist for the same mask, the largest satisfied minDuration wins.
  • Tap-while-hold: While an anchor (holdMask) is fully pressed, detect each press+release of a tapMask and execute an action per tap (supports repeated taps).
  • Optional conditions: Gate any entry with COND(...), e.g. COND(machineState), COND(!machineState), or arbitrary boolean expressions.
  • Stable input: Built-in debounce (BTN_DEBOUNCE_MS) and initial coalescing (COMBO_COALESCE_MS) to treat near-simultaneous presses as one combo.
  • No accidental singles after combos: A single-key full-release can be suppressed if a multi-press occurred in the same session — valid multi-mask full-release still fires.

Getting Started

Files

  • ButtonTransitions.h – API, types, and macros
  • ButtonTransitions.cpp – implementation

Include both in your project and provide this hook:

uint8_t getButtonMask(void)
{
    // Return current button bitmask (bit i => Si pressed), or 0 if none.
}

Initialize and call the state machine regularly:

void setup()
{
#if defined(DEBUG)
    Serial.begin(500000);
    delay(200);
    Serial.println(F("DEBUG ENABLED"));
#endif

    transitionsInit();
}

void loop()
{
    processTransitionsHybrid(tableEx, NUM_ENTRIES, millis());
}

Style: Opening braces on a new line. English names like cntCtrl, lastMeasurement, newMeasurement. Prefer a clear state-machine structure.


Defining Transitions

Use the macros from the header. Conditions are optional via COND(...) (a capture-less lambda that decays to bool(*)()).

extern bool machineState;
extern int  cntCtrl;

// Actions
void startAutoMode()
{
    // ...
}

void incCounter()
{
    // ...
}

// Table
ButtonTransitionEx tableEx[] =
{
    // Full-release without condition (exact mask, min duration in ms)
    FULL_RELEASE(BTN_TOP_LEFT | BTN_TOP_RIGHT, 2000, ModeAuto, startAutoMode),

    // Full-release with condition (machineState must be true)
    FULL_RELEASE(BTN_TOP_LEFT | BTN_TOP_RIGHT, 2000, ModeAuto, COND(machineState), startAutoMode),

    // Tap-while-hold: hold TL, tap TR between 30..250 ms (repeatable)
    TAP_WHILE_HOLD(BTN_TOP_LEFT, BTN_TOP_RIGHT, 30, 250, ModeNoChange, incCounter),

    // Tap-while-hold with condition (only if counter > 0)
    TAP_WHILE_HOLD(BTN_TOP_LEFT, BTN_BOTTOM_LEFT, 50, 0, ModeNoChange, COND(cntCtrl > 0), incCounter)
};

constexpr size_t NUM_ENTRIES = sizeof(tableEx) / sizeof(tableEx[0]);

Policies

  • Full-release: exact mask must match what was held until release; the entry with the greatest satisfied minDuration is chosen.
  • Tap-while-hold: exact holdMask must remain pressed; exact tapMask must be pressed then released; tapMin ≤ dur ≤ tapMax (or tapMax == 0 for open upper bound).

Configuration

Macro Meaning Default
BTN_DEBOUNCE_MS Debounce commit for the mask level 20 ms
COMBO_COALESCE_MS Group near-simultaneous initial presses 30 ms
BTNTRANS_NO_NEXTMODE Define to omit nextMode handling (unset)

Tweak these in a project config header or via build flags.


API (Public)

// Reset all internal state at startup
void transitionsInit(void);

// Main evaluation; call each loop/tick
void processTransitionsHybrid(struct ButtonTransitionEx* tableEx, size_t num, uint32_t now);

// Drop current press/tap context (e.g., when changing app context)
void transitionsReset(void);

// Provided by your app: read the current button mask (bit i => Si pressed)
uint8_t getButtonMask(void);

Types & Macros (from the header):

  • enum OperationMode { ModeNoChange=-1, ModeBasic, ModeAuto };
  • enum ComboEvalType { ComboEvalOnFullRelease, ComboEvalTapWhileHold };
  • struct ButtonTransitionEx { ... } (supports .cond predicate)
  • FULL_RELEASE(...), TAP_WHILE_HOLD(...), and COND(expr)

Debugging

Define DEBUG globally (e.g., PlatformIO build_flags = -DDEBUG) and ensure Serial.begin(...) in setup().
You’ll see messages like:

[BTN] pressed: mask=0x03 (S0+S1)
Tap while hold: end
Full-release exec

License

This project is licensed under the MIT License.
© 2025 Björn Gaebel. See source headers for full text.

About

ButtonTransitions – Hybrid Button State Machine (Arduino)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors