Byte Commander Byte Commander - 4 months ago 12
Linux Question

How to detect how often a button has been pushed consecutively?

I want to monitor the output of a command which keeps running infinitely and prints a line every now and then. It displays events of a hardware button and each line indicates a push.

My script is supposed to run other commands when it receives lines, but the problem is that not the content of those lines decide which command must be run, but the number of lines within a given delay.

In other words, the user may push this monitored button several times and different commands will be executed depending on how often the button is pressed. The user has 2 seconds time between two presses before the command will be picked based on the number of consecutive presses.

I currently have a Bash script with this structure:

#!/bin/bash
lasttouch="0"

MONITORING_COMMAND
| while read line; do
if [ $(date +%s --date="2 seconds ago") -lt $lasttouch ]
then
COMMAND2
else
lasttouch=$(date +%s)
COMMAND1
fi
done


This however does only handle up to two consecutive presses and it executes
COMMAND1
on every event, even if a subsequent press follows in time and
COMMAND2
should run instead.

I actually have no idea how to implement this properly in Bash. I think I need some kind of multithreading, one thread listening for incoming lines and increasing a counter and another thread running a countdown of 2 seconds after every event and resetting the counter and executing the appropriate command once the countdown times out without additional events.

Answer

You can set a function for your single push waiting for the required time before executing COMMAND1, record its pid with $! and kill it when you actually receive a double push before the required time.

Here is an example with a delay of 700 milliseconds :

#!/bin/bash

MONITORING_COMMAND="your monitoring command here"
PUSH_NUM=1         #1 => until double push detection | 2 => until triple push detection etc...
MAX_DELAY=700      #the delay in between push in milliseconds

inc=0
remaining_delay=0

# wait_push <command value> <time left to sleep before taking the push>
wait_push()
{
    if [ ! -z "$2" ]; then
        sleep $2
    fi
    inc=0
    #switch between all your command here
    #COMMAND0 : normal push
    #COMMAND1 : double push
    #COMMAND2 : triple push etc..
    echo "push is detected here: execute $1 here"
    pid=""
    lasttouch=""
}

$MONITORING_COMMAND | while read line ; do 

    current=$(($(date +%s%N)/1000000))

    if [ ! -z "$lasttouch" ]; then

        diff=`expr $current - $lasttouch`

        if test $diff -lt $MAX_DELAY
        then

            inc=$((inc+1))

            if [ "$inc" -lt $PUSH_NUM ]; then

                if [ ! -z "$pid" ]; then
                    kill $pid 2>/dev/null
                    wait $pid 2>/dev/null
                fi
                remaining_delay=$((remaining_delay-diff))
                time=`awk -v delay=$remaining_delay 'BEGIN { print (delay / 1000) }'`
                #normal push
                wait_push "COMMAND${inc}" $time &
                pid=$!
                continue

            elif  [ "$inc" == $PUSH_NUM ]; then

                if [ ! -z "$pid" ]; then
                    kill $pid 2>/dev/null
                    wait $pid 2>/dev/null
                fi
                wait_push "COMMAND${inc}"
                continue

            fi
        else
            inc=0
        fi
    fi

    if [ "$inc" == 0 ]; then
        remaining_delay=$MAX_DELAY
        time=`awk -v delay=$MAX_DELAY 'BEGIN { print (delay / 1000) }'`
        #normal push
        wait_push "COMMAND${inc}" $time &
        pid=$!
    fi

    lasttouch=$current
done

You can increase the push number editing variable PUSH_NUM :

  • double push : PUSH_NUM=1
  • tripple push : PUSH_NUM=2
  • etc

You will have all command processing in wait_push function. This takes into account time remaining between all consecutive push event (which dont exceed MAX_DELAY ms)