Run a python program at startup with keyboard support

Hi,
I am evaluating DietPi to solve a problem that has happened since the Pi3 was released.
If you run a python program at startup from /etc/rc.local that reads the keyboard you get a crash:

oldterm = termios.tcgetattr(fd)
termios error: (25, ‘inappropriate ioctl for device’)

This did not happen with the Pi2 operating system but does with the Pi3(Pixel) and DietPi.

Is there a way to run the following program automatically at startup without it crashing?

Thanks,
David Taylor

#!/usr/bin/python
#
# startup.py
import os, sys, time
import termios, fcntl


def GetKey():
   fd = sys.stdin.fileno()
   oldterm = termios.tcgetattr(fd)
   newattr = termios.tcgetattr(fd)
   newattr[3] = newattr[3] & ~TERMIOS.ICANON & ~TERMIOS.ECHO
   newattr[6][TERMIOS.VMIN] = 1
   newattr[6][TERMIOS.VTIME] = 0
   termios.tcsetattr(fd, termios.TCSANOW, newattr)
   oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
   fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
   try:
      try:
         c = sys.stdin.read(1)
#         print "Got character", repr(c)
         return c
      except IOError: pass
   finally:
      termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
      fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)


if __name__ == "__main__":

   # give us a chance to quit before launching
   print '###  Press q to prevent launch or any other key to launch  ###'
   bLaunch = True 
   TERMIOS = termios
   i = 0
   z = 5
   while i < z:
      print z - i,   
      i = i + 1
      ch = GetKey()
      if ch != None:
         if ch == 'q':
            print
            print 'Launch has been stopped by pressing ' + ch
            bLaunch = False
         break
      time.sleep(1)
         
   if bLaunch == True:
      print
      print "Launch the main loop here"

exit(0)

Hi David,

You need to enable StandardIntput=tty in your service, to allow for keyboard input.

Try systemD service that allows for std tty input and output:
Change /root/test.py accordingly, then copy/paste all

cat << _EOF_ > /etc/systemd/system/python.service
[Unit]
Description=Python
After=local-fs.target network.target

[Service]
Type=oneshot
RemainAfterExit=yes
StandardOutput=tty
StandardInput=tty
ExecStart=/usr/bin/python /root/test.py

[Install]
WantedBy=multi-user.target
_EOF_
systemctl enable python.service
systemctl daemon-reload

Thanks for your very useful reply.
What I actually did was add the missing line
StandardInput=tty
to /etc/systemd/system/rc-local.service
and now my python program starts up correctly from rc.local as it always did before “Pixel”.
I checked in the Pixel os but it does not have a /etc/systemd/system/rc-local.service file or a /etc/systemd/system/python.service file so I could not apply the fix to it so I will stay with DietPi.

I am running my home central heating with the pi and find that SD cards do not last long. I am downloading weather data and other stuff regularly so am stressing the SD I think. As these files are temporary could I be loading them into RAM memory instead? If so, what folder should I save them to?

Thanks and all the best,
David

Hi again,
I was a bit hasty when I said that this problem is fixed.
When run as I described by adding the missing line
StandardInput=tty
to /etc/systemd/system/rc-local.service
keyboard presses get displayed but my python program is not seeing them. What actually happens is that my key presses are seen by the login.

When run using the python.service you suggested it starts up really early in the boot sequence then just dies.

Is it possible to automatically start a python program (that reads the keyboard) right at the end of the boot sequence after everything else has completed?

Thanks again,
David

Hi David,

/etc/rc.local uses systemD service Type=idle. This will be launched after SystemD has finished all bootup services. So you could try idle and add After=rc.local

When run using the python.service you suggested it starts up really early in the boot sequence then just dies.

Service type is oneshot. So SystemD will launch the python script and wait until it exits, before carrying on. You could try Type=simple which will thread this into the background.

Another way to delay start of script, add a simple sleep 10 to the start of python code.

Hi again,

Thanks for your reply. I’ve tried these suggestions but am getting nowhere. I just don’t have enough Linux knowledge.
Having created DietPi you clearly like a challenge so if you can get the startup.py program I posted earlier to start and work from /etc/rc.local without crashing then I will make a generous donation to the DietPi cause of $100. It might be a simple thing or it might take you a while but as a hint, it worked perfectly well in the standard Raspian as supplied with all pi before pi3.

All the very best and in anticipation of success…
David

I do enjoy a challenge :slight_smile:

Ok, so the main issue with trying to run this under rc.local, as Type=idle runs a background process, keyboard inputs will not go to your python script currently, or, take foreground blocking mode.

We’ll need to modify rc-local.service slightly, to launch prior to end of SystemD multi-user.target, instead of Type=idle

cat << _EOF_ > /etc/systemd/system/rc-local.service
[Unit]
Description=/etc/rc.local Compatibility
After=dietpi-boot.service dietpi-ramdisk.service dietpi-ramlog.service multi-user.target
Requires=dietpi-boot.service dietpi-ramdisk.service

[Service]
Type=oneshot
ExecStart=/etc/rc.local
StandardOutput=tty
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
_EOF_
systemctl enable rc-local.service
systemctl daemon-reload

Then, we create a service for your python script /root/test.py, with support for foreground process and blocking mode (Type=oneshot) and keyboard input (StandardInput=tty):

cat << _EOF_ > /etc/systemd/system/python.service
[Unit]
Description=Python start + foreground + keyboard input. After rc-local
Requires=multi-user.target
After=multi-user.target rc-local.service
AllowIsolate=yes

[Service]
Type=oneshot
RemainAfterExit=yes
StandardOutput=tty
StandardInput=tty
ExecStart=/usr/bin/python /root/test.py

[Install]
WantedBy=multi-user.target
_EOF_
systemctl daemon-reload
systemctl enable python.service

Make sure your script is in /root/test.py, then reboot system.

Thanks for this. It is almost what I need…

I created a fresh DietPi SD card, ran your scripts and added my test.py program. In test.py I set z = 50 to give a 50 second countdown and added an led flash every second so I could see that the program was running. I then rebooted.

During the boot my program started running, counted down for about 5 seconds then stopped and the login appeared.
After I logged in I got the # prompt.

All I need now is for my test.py program to keep running and have the focus until I press a key to stop it. Then, when test.py has exited I need the login to appear. That should be possible shouldn’t it?

Thanks for your help,
Dave

Hi Dave,

Currently, test.py is being executed, then it finishes once you press a key, or, i reaches 5. As the script is finished, the login prompt appears.

You’ll need to create a loop in your python code, that waits for user input without a time limit, before the script finishes.

I dont do Python, so syntax may be incorrect, but something like this should do it:

   while True:
      ch = GetKey()
      if ch != None:
         if ch == 'q':
            print
            print 'Launch has been stopped by pressing ' + ch
            bLaunch = False
         break
      time.sleep(1)
exit(0)

Hi again,

Unfortunately that didn’t make any difference however I have figured it out for myself and I think my solution would be a great feature for DietPi.

Here’s what I did:

modify /DietPi/dietpi/login

add a new function - I inserted it just ahead of Run_Autostart()

	#Autostart user's script
	Run_User_AutoStart(){
		sudo chmod 755 /root/autostart.sh
		./autostart.sh
	}

add a new option to function Run_Autostart - insert this before #Uae4arm standard boot

			#standard boot to Console with automatic login
			elif (( $AUTO_START_INDEX == 7 )); then
				Run_User_AutoStart

modify #Normal Login to this:

	#----------------------------------------------------------------
	#Normal Login
	if (( $DIETPI_INSTALL_STAGE == 1 )); then

		/DietPi/dietpi/dietpi-banner 1

		if (( $AUTO_START_INDEX > 0 )); then
			Run_AutoStart
		else
			Run_User_AutoStart
		fi
	#----------------------------------------------------------------

save /DietPi/dietpi/login

create file /root/autostart.sh similar to this one:

#!/bin/bash
#////////////////////////////////////
# DietPi autostart Script
#
#////////////////////////////////////
# Created by David Taylor / superdave156@gmail.com
#
#////////////////////////////////////
#
# Info:
# - filename /root/autostart.sh
# - activated when DietPi-config item 9 (Autostart Options) then items 0 or 7 selected.
# - Allows you to autostart your own Python program with full keyboard support - unlike Pixel!
#////////////////////////////////////
#
#////////////////////////////////////
# Add your own autostart commands here:

#for example
sudo chmod 755 /root/Python/test.py
/usr/bin/python /root/Python/test.py
exit 0

That’s it!

If you have the boot configured to “Console: Manual Login” then you have to login then autostart.sh runs
If you have the boot configured to “Console: Automatic Login” then autostart.sh runs and you have a headless autostarting python program. Because the keyboard is available you can break into the program loop to work on the code.


To add this feature to DietPi all you would need to do is apply the above mods with the python lines commented out of autostart.sh and put test.py into /root/Python.
Then anyone who wants to run a python program at startup can use test.py as a base.

What do you think?

Dave