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;
}
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?.
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.
If works great if you press the button firmly and slowly, but if you press the button normally it doesn’t register the DOWN/UP event as expected.
Basically when pressing the button normally I see:
Normal first press:
Button Presses: ON/OFF on pin
Button down
Normal second press:
Button Presses: ON/OFF on pin
Button up
But if I press the buttons slowly I see
Slow first press:
Button Presses: ON/OFF on pin
Button down
Button Presses: ON/OFF on pin
Button up
Slow second press:
Button Presses: ON/OFF on pin
Button down
Button Presses: ON/OFF on pin
Button up
I haven’t looked at the waveforms yet, could be something there?
Or maybe even something around the way I’m I’m checking for the down/up button state by reading the gpio… Couldn’t find any examples of how to know which side of the wave was being detected when using MGOS_GPIO_INT_EDGE_ANY
Working example of long press ripped off from the provision library. Tested on ESP32:
static mgos_timer_id s_hold_timer = MGOS_INVALID_TIMER_ID; // The var for long press detection
static void button_timer_cb(void *arg)
{
(void)arg;
int pin = 35;
LOG(LL_INFO, ("Button Timer Callback"));
int n = 0; /* Number of times the button is reported down */
for (int i = 0; i < 10; i++)
{
if (mgos_gpio_read(pin) == false)
n++;
mgos_msleep(1);
}
if (s_hold_timer != MGOS_INVALID_TIMER_ID)
mgos_clear_timer(s_hold_timer);
if (n > 7)
{
enable_ap();
LOG(LL_INFO, ("Long Press"));
}
}
static void on_off_button_cb(int pin, void *arg)
{
(void)arg;
int duration = 5000;
LOG(LL_INFO, ("Button Pressed"));
// Do any short press stuff
// Do the long press scheduling stuff
if (s_hold_timer != MGOS_INVALID_TIMER_ID)
mgos_clear_timer(s_hold_timer);
LOG(LL_INFO, ("on_off_button_cb | Setting %d ms timer", duration));
s_hold_timer = mgos_set_timer(duration, 0, button_timer_cb, arg);
}
enum mgos_app_init_result mgos_app_init(void)
{
mgos_gpio_set_mode(on_off_button_pin, MGOS_GPIO_MODE_INPUT);
mgos_gpio_set_button_handler(on_off_button_pin, MGOS_GPIO_PULL_UP, MGOS_GPIO_INT_EDGE_NEG, 50, on_off_button_cb, NULL);
return MGOS_APP_INIT_SUCCESS;
}
Hey, just spotted this. Yeah it was working. I think I made a few more changes.
It neatly detects short and long presses. I haven’t really tested it for the slightest touch, but I believe it’s working ok. If anything I wouldn’t want a very casual press to trigger it!