1

I am writing a small program for a Museum display that captures a still from a webcam, displays it on screen, waits a few seconds and takes another, loop forever. This all works fine from the command line. However, I also have a need to run this as a service so I can stop and start it remotely, i.e. shut it down when the Museum is closed. When I do use the service - nothing shows up on screen and the syslog says that the service starts and stops repeatedly.

Here is the rough, but working code, for the program

    #!/usr/bin/python
import os
import pygame, sys
import time
from timeloop import Timeloop
from datetime import timedelta, datetime
from pytz import timezone, utc
import logging
import subprocess

from pygame.locals import *
import pygame.camera

LOG_LEVEL = logging.DEBUG
LOG_FILENAME = os.path.splitext(os.path.basename(__file__))[0] + '.log'

def customTime(*args):
    ''' Custom Timezone for logging
    '''
    utc_dt = utc.localize(datetime.utcnow())
    my_tz = timezone("US/Eastern")
    converted = utc_dt.astimezone(my_tz)
    return converted.timetuple()

# Logging Handlers
file_handler = logging.FileHandler(filename=LOG_FILENAME)
stdout_handler = logging.StreamHandler(sys.stdout)
stderr_handler = logging.StreamHandler(sys.stderr)
log_handlers = [file_handler, stdout_handler, stderr_handler]

# Setup general logging
logging.basicConfig(
        format='%(asctime)s | %(levelname)s | %(lineno)d | %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S',
        level=LOG_LEVEL,
        handlers=log_handlers)

# Set the logger to use the custom time format
logging.Formatter.converter = customTime

tl = Timeloop()

#initialise pygame
pygame.init()
pygame.camera.init()
pygame.mouse.set_visible(0)

width = 1920
height = 1080
cam = pygame.camera.Camera("/dev/video0",(width,height))

screen = pygame.display.set_mode((width,height), pygame.FULLSCREEN)

def hide_command_line():
    """ This sets the text colour of the command line to black
    to hide any screen output
    NOTE: this will hide all text output on the command line
     - exit and log back in to get it back or type 'reset'.
    """
    hide_text_command = 'sudo sh -c\
        "TERM=linux setterm -foreground black -clear all > /dev/tty0"'
    subprocess.Popen(
        hide_text_command,
        shell=True,
        stdout=subprocess.PIPE)


def show_command_line():
    """ This sets the text colour of the command line to white
    """
    show_text_command = 'sudo sh -c\
        "TERM=linux setterm -foreground white -clear all > /dev/tty0"'
    subprocess.Popen(
        show_text_command,
        shell=True,
        stdout=subprocess.PIPE)

@tl.job(interval=timedelta(seconds=7))
def take_picture():
    cam.start()
    #take a picture
    image = cam.get_image()
    # Show image
    screen.blit(image,(0,0))
    pygame.display.update()
    cam.stop()

if __name__ == "__main__":
    hide_command_line()
    tl.start()

    running = True
    while running:
        # other code
        event = pygame.event.wait()
        if event.type == pygame.QUIT:
             running = False  # Be interpreter friendly
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                show_command_line()
                pygame.display.quit()
                pygame.quit()
                sys.exit(0)

    show_command_line()
    pygame.display.quit()
    pygame.quit()
    sys.exit(0)

And here is the syslog output - which just repeats.

Apr  4 19:51:05 raspberrypi systemd[1]: Stopped SpyCamera.
Apr  4 19:51:05 raspberrypi systemd[1]: Started SpyCamera.
Apr  4 19:51:08 raspberrypi systemd[1]: interactive_service.service: Service hold-off time over, scheduling restart.
Apr  4 19:51:08 raspberrypi systemd[1]: Stopped SpyCamera.
Apr  4 19:51:08 raspberrypi systemd[1]: Started SpyCamera.
Apr  4 19:51:11 raspberrypi systemd[1]: interactive_service.service: Service hold-off time over, scheduling restart.
Apr  4 19:51:11 raspberrypi systemd[1]: Stopped SpyCamera.
Apr  4 19:51:11 raspberrypi systemd[1]: Started SpyCamera.

Other than that there are no errors thrown at all (I removed some try blocks just to see what would happen, maybe I was masking an error message - still no errors).

Just to be complete here is the service.

[Unit]
Description=SpyCamera
After=syslog.target

[Service]
Type=simple
User=root
group=root
WorkingDirectory=/home/pi/spycamera
ExecStart=/usr/bin/python3 /home/pi/spycamera/spycamera.py
Restart=always
RestartSec=2
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target

From the command line it's just "python3 spycamera.py" as user pi. At the moment I terminate by killing the script (still working on the quit code). If I run the service as pi I get the error in syslog of "SystemError: ioctl(VIDIOC_S_FMT) failure: no supported formats". When the service is run as root the error above "SpyCamera Stared", etc... is what I get.

So what I want to know is why it runs fine from the command line, but not when run at startup from a service. Thanks for any and all help.

Update:

I have updated the code some - lessons from other sites attempting to run pygame from systemd - still no joy. However with the edits to the service from @ingo I get new errors (hence the new code)

File "/home/pi/spycamera/spycamera.py", line 51, in <module>
Apr  8 14:23:03 raspberrypi python3[680]:     windowSurfaceObj = 
pygame.display.set_mode((width,height),1,16)
Apr  8 14:23:03 raspberrypi python3[680]: pygame.error: Unable to open 
a console terminal

Update 2:

This is now working. I have updated the code above to match what works for my project. The trick to running it is found here How do I let an SDL app (not running as root) use the console. Use this service to run pygame from systemd.

[Unit]
Description=SpyCamera
After=multi-user.target

[Service]
User=pi
WorkingDirectory=/home/pi/spycamera
ExecStart=/usr/bin/python3 /home/pi/spycamera/spycamera.py
Restart = always
RestartSec = 5
StandardInput=tty
StandardOutput=tty
TTYPath=/dev/tty7
ExecStartPre=/bin/chvt 7

[Install]
WantedBy=graphical.target
  • What exactly is the command do you run the script from the command line? Do you run it as user pi or as root? How do you terminate the script? With ctrl+C? Please address me with @Ingo, otherwise it may be that I don't see your reply. – Ingo Apr 4 at 21:49
  • @Ingo - From the command line it's just "python3 spycamera.py" as user pi. At the moment I terminate by killing the script (still working on the quit code). If I run the service as pi I get the error in syslog of "SystemError: ioctl(VIDIOC_S_FMT) failure: no supported formats". When the service is run as root the error above "SpyCamera Stared", etc... is what I get. – archdata Apr 5 at 15:24
0

As far as I can see, the problem is that you try to output a graphics, but your unit file does not provide the environment for doing it. You must have the graphical.target running. You can see the dependencies at System bootup process.

Then to the other settings in your unit file:

Type=simple is default, not needed.
User=root Your program runs as user pi on the command line
group=root syntax error, must be Group=root, but not needed for user pi
Restart=always and RestartSec=2 You cannot fix a buggy script with this. Starting it over and over again makes no sense. You must have a clean running script.
StandardOutput=syslog and StandardError=syslog is default, not needed.

So I suggest to try this unit file:

[Unit]
Description=SpyCamera
After=graphical.target

[Service]
User=pi
Environment=DISPLAY=:0
WorkingDirectory=/home/pi/spycamera
ExecStart=/usr/bin/python3 /home/pi/spycamera/spycamera.py

[Install]
WantedBy=graphical.target

You can stop your script simply with sudo systemctl stop spycamera.service.

If it doesn't work then that's because systemd provides a very limited environment so you have to examine what exactly is needed to run the script. It gives you an error message "Unable to open a console terminal". Now you have to find what environment is needed to open a console terminal. In the unit files in the [Service] section you can temporary add a line:

ExecStartPre=/usr/bin/env

The output you will find with journalctl -b -e. Compare it with the environment you get on the command line.

  • Updated the code a bit, but get new error message (see updates above). Still work fine from the command line. However, it still fails to launch from systemd – archdata Apr 8 at 13:59
  • @archdata Seems that's a step forward. I have updated my answer at the end. – Ingo Apr 8 at 17:40
  • Thanks for the information. I went a slightly different direction after spending the morning reading as much as I could. I found this page How do I let an SDL app (not running as root) use the console. Using tty7 (see comment on answer) and it works like a charm. I have updated the code in the question to reflect the new information. Hopefully someone else finds it and it can help them too. – archdata Apr 8 at 17:50
  • @archdata Instead of updating the question with the answer you should make an answer and then after two days you should accept it yourself. This way you could earn some reputation and it will finish the question and show others that it has a solution. – Ingo Apr 8 at 19:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.