Keyboard input with a timeout using Python

After just messing about and experimenting with my Sense Hat a bit I had a short python script that just loaded each ‘.png’ file it found in the current directory and displayed it on the 8×8 LED matrix.

#!/usr/bin/python

import time

_sense = sense_hat.SenseHat()
_sense.set_rotation(180) # Optional
_sense.low_light = True # Optional - but a good idea

try:
  while True: # Repeats forever (until an exception is raised)
    for _file in glob.glob("*.png"):
      _sense.load_image(_file) # Load the image and display it
      print _file # Display filename - optional
      time.sleep(2) # Wait two seconds

except KeyboardInterrupt: # User pressed Ctrl-C so exit
  _sense.clear()
  raise SystemExit

Note – For this to work each image must be 8×8 pixels.

Though quite it is quite fun to see the minecraft characters shown on the Sense Hat’s display it soon got a bit tedious – two seconds can quickly start to seem like a long time when nothing is happening!

What I decided I really wanted was to be able to do was press a key to get the program to display the next image immediately. I couldn’t find a routine that would let me do this, though I did find a couple of articles on ‘StackOverflow’ that each solved half the problem, so all I had to do was join up the dots (or is it pixels…).

The result a function that waits for a specified number of seconds (or indefinitely if less than one second is specified) and returns the character code corresponding to the key that the user pressed or a ‘null’ character if there was no input.

Note – This only works on Linux though I think it could be made to work in Windows quite easily.

#!/usr/bin/python
#
#  pi-sense-slideshow
#
#  Attempts to display every '.png' file in the current directory on the LED
#  display of the Sense Hat. Obviously each image can only be 8x8 pixels but
#  much  silliness can still be had using the faces of Minecraft characters,
#  however - some look better than others!
#
#  This  program is free software: you can redistribute it and/or modify  it
#  under  the  terms of the GNU General Public License as published  by  the
#  Free  Software  Foundation, either version 3 of the License, or (at  your
#  option) any later version.
#
#  This  program  is  distributed in the hope that it will be  useful,  but
#  WITHOUT   ANY   WARRANTY;   without  even   the implied   warranty   of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
#  Public License for more details.
#
#  You  should have received a copy of the GNU General Public License  along
#  with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import glob, sense_hat

def _get_char(_timeout):
#
# get_char
#
# Note - This only works on Linux.
#
# Sets  up a signal to interrupt the program when the specified timeout  has
# elapsed.  It then configures the terminal so that it just reads  a  single
# character  from stdin and waits until the for the user to press a key,  or
# for the signal to interrupt program execution.
#
# If  the  user  presses a key before the signal fires then  the  signal  is
# cancelled  and the terminal characteristics restored before the  character
# code fo the key that was pressed is returned.
#
# If  the  signal  fires  then this raises an exception,  which  causes  the
# program  to stop waiting for the user to press a key, cancels the  signal,
# restores the terminal characteristics and returns a null character.
#
# Finally  if the user pressed Ctrl-C then a 'Keyboard Interrupt'  exception
# is raised.
#
# Although  it is self-contained the amount of work that has to be done each
# time  the routine is called makes it quite inefficient - if it was  to  be
# used heavily then the code to set up and restore the terminal state should
# really be moved to separate routines.
#

  import termios, fcntl, sys, os
  import signal

  def _interrupt(_signum, _frame):
    raise Exception("")

  _char ='\x00' # Set up the default
  if _timeout < 0: _timeout = 0
  signal.signal(signal.SIGALRM, _interrupt)
  if _timeout > 0: signal.alarm(_timeout) # Set the timeout
  try:
    _filedescriptor = sys.stdin.fileno()
    # Save current terminal state
    _backup_flags = fcntl.fcntl(_filedescriptor, fcntl.F_GETFL)
    _backup_attrs = termios.tcgetattr(_filedescriptor)
    # Modify terminal attributes - see termios(3) man page.
    _new_attrs = list(_backup_attrs) # Use a copy of the backup
    # - iflag
    _new_attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK
                  | termios.ISTRIP | termios.INLCR | termios. IGNCR
                  | termios.ICRNL | termios.IXON )
    # - oflag
    _new_attrs[1] &= ~termios.OPOST
    # - cflag
    _new_attrs[2] &= ~(termios.CSIZE | termios. PARENB)
    _new_attrs[2] |= termios.CS8
    # - lflag
    _new_attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON
                  | termios.ISIG | termios.IEXTEN)
    # Set terminal attributes
    termios.tcsetattr(_filedescriptor, termios.TCSANOW, _new_attrs)
    # Turn off non-blocking
    fcntl.fcntl(_filedescriptor, fcntl.F_SETFL, _backup_flags
               & ~os.O_NONBLOCK)
    # Wait for a keystroke
    _char = sys.stdin.read(1)
  except: # If an exception is raised (by the signal)
    pass
  finally: # Disable the signal and restore the terminal state
    signal.alarm(0)
    termios.tcsetattr(_filedescriptor, termios.TCSAFLUSH, _backup_attrs)
    fcntl.fcntl(_filedescriptor, fcntl.F_SETFL, _backup_flags)
    if _char == '\x03': raise KeyboardInterrupt
  return _char

_sense = sense_hat.SenseHat()
_sense.set_rotation(180) # Optional
_sense.low_light = True # Optional - but a good idea

try:
  while True: # Repeats forever (until an exception is raised)
    for _file in glob.glob("*.png"):
      _sense.load_image(_file) # Load the image and display it
      print _file # Display filename - optional
      _get_char(2) # Wait two seconds for the user to press a key

except KeyboardInterrupt: # User pressed Ctrl-C so exit
  _sense.clear() # Clear the display
  raise SystemExit

References

The code in this post is based on the following posts:

# My thanks to the authors for their write-ups.


Raspberry Pi is a trademark of the Raspberry Pi Foundation

Advertisements
This entry was posted in Debian, Linux, Programming, Raspbian, Ubuntu and tagged , , . Bookmark the permalink.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s