How to do a long or short button press

#1

I’m posting before attempting to integrate it into the current MGOS code. If there is interest in integration with the library and a PR, happy to chat.

The simple use case this addresses is being able to tell if a button press is short or long. Current mgos int handler only lets you choose release or press edges, so you can’t use it to time the start and end with different callbacks. This code basically starts a timer on the first press and then that timer keeps repeating until the button is released, so effectively provides protection against multiple event fires while the button keeps being pressed!
Note, this code could be, and likely will be tomorrow, improved with mgos events being fired on short or long press detections that an user could then subscribe to with a callback. As the code seems to me to be non blocking (unlike the provision library mgos sleep approach) I can’t really see why this code could be built in as standard functionality or even a config flag/function on the gpio.

// User button variables
#define SF_USERBUTTON_GPIO (14)
long int userButton_time_start = (long int)time(NULL);
mgos_gpio_pull_type userButton_gpio_direction = MGOS_GPIO_PULL_UP;
mgos_timer_id userButton_timer_id;
int userButton_timer_delay = 150;
bool userButton_running = false;
bool userButton_detected = false;
int userButton_count = 0;
int userButton_count_required = 5;

unsigned long IRAM_ATTR millis() {
    return (unsigned long)(esp_timer_get_time() / 1000ULL);
}
void timer_userButton_checkLongPress(void* arg) {
    int level = mgos_gpio_read(SF_USERBUTTON_GPIO);
    if ((userButton_gpio_direction == MGOS_GPIO_PULL_UP && level == 0)
        || (userButton_gpio_direction == MGOS_GPIO_PULL_DOWN && level == 1)){
        // Means it's still being pressed
        userButton_count++;
    } else {
        // Did we see a short press?
        if (!userButton_detected && userButton_count < userButton_count_required){
            LOG(LL_INFO, ("User button press (%d) SHORT press detected! count of %d ", SF_USERBUTTON_GPIO, userButton_count));
        } else {
            LOG(LL_INFO, ("User button press (%d) cleared after a LONG press count of %d ", SF_USERBUTTON_GPIO, userButton_count));
        }
        mgos_clear_timer(userButton_timer_id);
        userButton_count = 0;
        userButton_detected = false;
        userButton_running = false;
    }

    if (!userButton_detected && userButton_count >= userButton_count_required) {
        // We take a threshold approach, once reached we ignore if the button keeps being pressed
        userButton_detected = true; // the timer will keep going until released, but this won't trigger multiple times
        long int diff = millis() - userButton_time_start;
        LOG(LL_INFO, ("User button press (%d) detected LONG press %lu", SF_USERBUTTON_GPIO, diff));
    }

    (void)arg;
}
static void gpio_userbutton_press_cb(int pin, void* arg) {
    long int now = millis();
    long int diff = now - userButton_time_start;

    if (!userButton_running || diff > 1000 * 10) {
        LOG(LL_INFO, ("Starting user button fresh (%d) time diff was %lu", pin, diff));
        userButton_count = 0;
        userButton_time_start = now;
        userButton_detected = false;
        if (!userButton_running) {
            userButton_timer_id = mgos_set_timer(userButton_timer_delay, MGOS_TIMER_REPEAT, timer_userButton_checkLongPress, NULL);
            userButton_running = true;
        }
    } else {
        LOG(LL_INFO, ("Ignored button press (%d) interrupt diff was %lu", pin, diff));
    }


    (void)arg;
}


// init code:
enum mgos_app_init_result mgos_app_init(void) {
    // Press button
    if (!mgos_gpio_set_mode(SF_USERBUTTON_GPIO, MGOS_GPIO_MODE_INPUT)
        || !mgos_gpio_set_pull(SF_USERBUTTON_GPIO, MGOS_GPIO_PULL_UP)
        || !mgos_gpio_set_int_handler(SF_USERBUTTON_GPIO, MGOS_GPIO_INT_EDGE_NEG, gpio_userbutton_press_cb, NULL)
        || !mgos_gpio_enable_int(SF_USERBUTTON_GPIO)) {

        LOG(LL_WARN, ("Failed to setup userbutton PIN (%d) for PRESS", SF_USERBUTTON_GPIO));
    }
    return MGOS_APP_INIT_SUCCESS;
}
#2

Thanks for posting this, I have a need to do the same thing. In fact it seems to be a common question, although not super common. I’ll be looking at this carefully, did you ever do the update you mentioned?.

#3

Try to use mgos_gpio_set_button_handler with MGOS_GPIO_INT_EDGE_ANY and handle the interval between edges in the callback.

1 Like
#4

I did try before writing the code. Simply doesn’t work. Edge any was basically commented out as a detection handler in the library. You can only do POS or NEG, not both, not ANY, or I would have simply had two functions playing tennis with each other with a stopwatch.

My code above could be incorporated into the MOS library, hence my question about interest.

#5

Hrm, i see there was a january commit to add edge any support. Trying now to remember why it didn’t work.