Friday, 27 July 2012

command line remote control

Regularly, I work on machines connected to TVs which respond to interactions with remote control devices. Yesterday, while doing some actions at the device command line, I realized that I don't need the remote control. All I need to control applications is in the shell. This is an Android based machine, so it is possible to move the menu selector to the left by executing:

$ input keyevent 21 

I made a conf file with some keys I needed:

$ cat androidKeys.sh
KEY_LEFT=21
KEY_RIGHT=22
KEY_UP=19
KEY_DOWN=20
KEY_BACK=4
KEY_ENTER=66
KEY_MENU=3
KEY_VOLUME_UP=24
KEY_VOLUME_DOWN=25
KEY_VOLUME_MUTE=164


And created a script to easily send commands:

$ cat send.sh
#!/usr/bin/env bash

source ./androidKeys.sh
HOST_IP=$1
EVENT_KEY=$2

KEY_CODE=$(eval "echo \$${EVENT_KEY}")

ssh $HOST_IP "input keyevent ${KEY_CODE}" 

Noticed the eval "echo \$${EVENT_KEY}"? It lets you obtain a variable's value by using the content of another variable to indicate the name of the first variable. So, the $EVENT_KEY may contain 'KEY_LEFT', which when passed to eval is evaluated as "echo $KEY_LEFT" , giving us the value of $KEY_LEFT.

Then I did:
 
$ ./send.sh 10.0.0.123 KEY_LEFT

And the cursor moved to the left. This is better, I thought, but it could be even better. I could be using the directional keys on my keyboard.

But it is not a simple task, the following has to possible:
1) obtain the key pressed
2) convert into a numerical representation
3) translate that representation into our KEY_* codes
4) invoke send.sh, passing the host IP and the key code

For the first task, an stty/dd combo comes to rescue. We use stty in raw mode, and capture three bytes of input with dd (bs=3). This will allow us to capture single and multi byte keys.
Then we parse this string (ord function below), by iterating over it and converting each character to a number by printfing it as integer.
Finally, we pass the numerical sequence returned by ord to another charCodeTokey function which translates to our known KEY_* codes.

$ cat auto.sh 
#!/usr/bin/env bash

HOST_IP=$1

function charCodeToKey ()
{
        CHAR_CODE=($@)
        CHAR_STRING="${CHAR_CODE[@]}"

        [ "$CHAR_STRING" = "27 91 68" ] && echo 'KEY_LEFT'
        [ "$CHAR_STRING" = "27 91 67" ] && echo 'KEY_RIGHT'
        [ "$CHAR_STRING" = "27 91 65" ] && echo 'KEY_UP'
        [ "$CHAR_STRING" = "27 91 66" ] && echo 'KEY_DOWN'
        [ "$CHAR_STRING" = "127" ]      && echo 'KEY_BACK'
        [ "$CHAR_STRING" = "13" ]       && echo 'KEY_ENTER'
        [ "$CHAR_STRING" = "27 79 72" ] && echo 'KEY_MENU'
        [ "$CHAR_STRING" = "43" ]       && echo 'KEY_VOLUME_UP'
        [ "$CHAR_STRING" = "45" ]       && echo 'KEY_VOLUME_DOWN'

}

function ord()
{
        str="$@"
        strLen=${#str}
        for i in $(seq 0 $(($strLen - 1)))
        do
                c=${str:$i:1}
                printf '%d ' "'$c"
        done
}

while :
do
        old_tty_settings=$(stty -g)
        stty raw -echo
        DATA=$(dd bs=3 count=1 2>/dev/null)
        stty "$old_tty_settings"

        CHARS=$(ord $DATA)

        if [ "${CHARS}" = "3 " ]
        then
                exit
        fi 

        KEY_EVENT=$(charCodeToKey "$CHARS")

        if [ "$KEY_EVENT" != "" ]
        then
                echo $KEY_EVENT
                ./send.sh $HOST_IP $KEY_EVENT
        else
                echo "Unsupported key"
        fi
done


One caveat of using stty's this way is that after starting the infinite while loop, it is impossible to stop. This is because stty captures the ^C command, and translates it too. So we need to check for it (the if [ "${CHARS}" = "3 " ].. exit above), and exit the script. Also, we must save the terminal settings ( old_tty_settings=$(stty -g) ) and put it back ( stty "$old_tty_settings" ), otherwise the terminal will be unusable after we exit the script.

So now I can control all my applications on the TV through my shell, no more remotes :)
Well, maybe not all applications yet. These scripts will need more key codes... and mouse support.

No comments:

Post a Comment