Friday, November 11, 2016

Raspberry Pi & Arch Linux - Day 18 - Noncanonical mode input and child process in Python


In this post, we will talk about two thing: (1) noncanonical mode of input and (2) child process in Python. The knowledge of the child process is not necessary for our project whereas the first is crucial. Before we can build algorithm to instruct the robot to move automatically, we first need to know how to control the robot manually. In other words, we are going to build a remote control robot car before anything else.

The idea is that we enter the command through the keyboard and once the program receives the command it controls the speed and the direction of the robot accordingly. For example, when we type the up arrow key, the robot will increase its speed or when we type the left arrow key, the robot will turn left.

There is one problem. Most of the time, the default setting of the user input is based on line. The user will enter whatever he wants and then press "return" to submit the command. However in our case, we want to enter the command char by char. We do not want to type 10 times the up arrow key plus a "return" key to tell the program that we want to quickly increase the speed of the robot.

A quick search on Google gives us the following solution.


def getchar():
   #Returns a single character from standard input
   import tty, termios, sys
   fd = sys.stdin.fileno()
   old_settings = termios.tcgetattr(fd)
   try:
      tty.setraw(sys.stdin.fileno())
      ch = sys.stdin.read(1)
   finally:
      termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
   return ch


Note that the three packages used in this function are quite low-level.

This solution has one drawback. When we call the getchar() function, it will block and wait for user to enter the character. This is not convenient when the user does not want to any command. To deal with this problem, we need to have timeout feature. We can find many solutions on this issue.  Some of the solutions uses a separate process to handle the inputs. Here we have another solution.


def getchar(timeout=3):
    fd = sys.stdin.fileno()
    old_setting = termios.tcgetattr(fd)

    try:
        # switch to noncanonical mode
        tty.setraw(sys.stdin.fileno())
        new_setting = termios.tcgetattr(fd)

        # set the timeout
        cc = new_setting[6]
        cc[termios.VTIME] = timeout # timeout = 0.3s
        cc[termios.VMIN]  = 0       # start the timer immediately

        # update termios struct
        termios.tcsetattr(fd, termios.TCSADRAIN, new_setting)

        # read
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_setting)
    return ch


As we mentioned, termios is a quite low-level package. What we need is actually called the noncanonical mode of input. It is controlled in the struct termios and it does provides parameters to control the timeout behavior.

The second thing we want to cover in this post is the child process. The following code shows how we can send the interrupt signal to all the processes.


if key_press == 'q':
    print("Exiting system")
    pid = os.getpid()
    parent = psutil.Process(pid)
    children = parent.children(recursive=True)
    for process in children:
        os.kill(process.pid, signal.SIGINT)

    os.kill(pid, signal.SIGINT)

In Python, we use os package to get the current process id and send signal to a given process. The signals are defined in the signal package. To send the signals to the child processes, we need to get the child process id of a given process. This functionality is provided in the psutil package. More specifically, we use the psutil.Process(pid) to get a list of child process object of the given process id.


--END--