Auto-Restart the PicarX script with systemctl

Anyone ever try to auto-start app_control on PicarX? I made a few minor changes to the script and wanted to have the Pi 4 restart the script automatically on boot or if it should crash/fail. I’ve been through many web pages and tried multiple options for the .service file but haven’t been sucessful. I placed the service file in /lib/systemd/system and it starts but get this error:

picarx systemd[1]: Started PiCarX.service - PiCarX Service.**
picarx python[1324]: vilib 0.3.8 launching …**
picarx python[1324]: picamera2 0.3.21**
picarx python[1324]: websocket server start at port 8765**
picarx python[1324]: Traceback (most recent call last):**
picarx python[1324]: File “/home/stefan/app_controlNew.py”, line 39, in **
picarx python[1324]: px = Picarx()**
picarx python[1324]: ^^^^^^^^**
picarx python[1324]: File “/usr/local/lib/python3.11/dist-packages/picarx-2.0.3-py3.11.egg/picarx/picarx.py”, line 48, in init**
picarx python[1324]: self.config_flie = fileDB(config, 777, os.getlogin())**
picarx python[1324]: ^^^^^^^^^^^^^**
picarx python[1324]: OSError: [Errno 6] No such device or address**

Here is the service file I used:
[Unit]
Description=PiCarX Service
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/python /home/stefan/app_control.py
Restart=on-abort

[Install]
WantedBy=multi-user.target

Any thoughts?

If you do not use the automatic restart script and run the app_control script separately, does it work properly?

We recommend reinstalling the vilib library and then running the example script with the following commands:

cd ~/vilib
git pull
sudo python3 install.py

Please pay close attention to the installation process for any error messages. If any errors occur, kindly provide us with screenshots of those errors. Thank you!

Thanks for your response. Yes, running from the command line works fine. I changed the service file to specify: ExecStart=/usr/bin/python /home/stefan/picar-x/example/13.app_control.py and used the unmodified version on the script, I wasn’t sure how to specify the git fetch so ended up cleaning out the vilib directory and repeating the vilib (clone) instructions from the web along with running the install.py script in the vilib folder. No errors during the install. I’ve been running the following commands after changing the service file:
sudo systemctl daemon-reload
sudo systemctl enable PiCarX.service
sudo systemctl start PiCarX.service
sudo systemctl status PiCarX.service
But I still get (sorry for the line breaks):
pi@picarx:~ $ sudo systemctl status PiCarX.service
× PiCarX.service - PiCarX Service
Loaded: loaded (/lib/systemd/system/PiCarX.service; enabled; preset: enabled)
Active: failed (Result: exit-code) since Wed 2024-10-09 14:26:10 EDT; 3s ago
Duration: 3.021s
Process: 4564 ExecStart=/usr/bin/python /home/stefan/picar-x/example/13.app_control.py (code=exited, status=1/FAILURE)
Main PID: 4564 (code=exited, status=1/FAILURE)
CPU: 2.765s

Oct 09 14:26:09 picarx python[4564]: File “/home/stefan/picar-x/example/13.app_control.py”, line 19, in
Oct 09 14:26:09 picarx python[4564]: px = Picarx()
Oct 09 14:26:09 picarx python[4564]: ^^^^^^^^
Oct 09 14:26:09 picarx python[4564]: File “/usr/local/lib/python3.11/dist-packages/picarx-2.0.3-py3.11.egg/picarx/picarx.py”, line 48, in init
Oct 09 14:26:09 picarx python[4564]: self.config_flie = fileDB(config, 777, os.getlogin())
Oct 09 14:26:09 picarx python[4564]: ^^^^^^^^^^^^^
Oct 09 14:26:09 picarx python[4564]: OSError: [Errno 6] No such device or address
Oct 09 14:26:10 picarx systemd[1]: PiCarX.service: Main process exited, code=exited, status=1/FAILURE
Oct 09 14:26:10 picarx systemd[1]: PiCarX.service: Failed with result ‘exit-code’.
Oct 09 14:26:10 picarx systemd[1]: PiCarX.service: Consumed 2.765s CPU time.
e
Sorry I now see that others have respnded to this issue, will check now…

Interesting related post (Picar-x Sunfounder App)


but not sure if they were solved. Here is a screen shot of the error (as the word wrap made it difficult to see)

“picarx python[1324]: OSError: [Errno 6] No such device.”

According to getlogin(3) - Linux manual page the error code 6 means “The calling process has no controlling terminal.”.

os.getlogin() returns the name of the user logged in on the controlling terminal of the process. Typically processes in user session (tty, X session) have a controlling terminal. Processes spawned by a service manager like init, systemd, or upstart usually do not have a controlling terminal. You have to get the user information by other means. The documentation for os.getlogin() recommends getpass.getuser().

Suggest you provide a video of the issue to help us analyze and resolve it.
If the video file is large, please upload it to OneDrive and share the link with us, along with access permissions.

I can’t imagine what I could video/capture or how it would help debugging the problem. Thanks much Spf50! That makes perfect sense since the script was executed by the system and not from a CLI. I tried a number of different approaches includingi making edits to the specified picarx class but using a valid user for the last argument picarx (self.config_flie = fileDB(config, 777, ‘pi’) just caused downstream problems in class initialization. (per your suggestion I also tried to use getpass.getuser() but couldn’t resolve getpass). In frustration I had an extra SD card to I restarted from scratch following the posted instructions. Same results; works find from a terminal session but gets the same error when run as a service. Would greatly appreciate any suggestions but it clearly is in the sunfounder picarx module. Had hoped my grandson could just power it up and used it vs having to ssh to the pi to start the script.

I suggest recording a video of the connection process and sharing it with us so we can analyze and resolve the issue more effectively. Written descriptions can make it difficult to assess the situation accurately.

If the video file is large, please upload it to OneDrive and share the link with us, along with the necessary access permissions. Thank you!

Did you import getpass?

#python
import getpass
getpass.getuser()
‘astroberry’

Try this minimalist example

sudo systemctl --force --full edit RobApp.service

Add following to above file, swapping names for your own paths etc
[Unit]
After=network.target

[Service]
ExecStart=/usr/bin/python /home/robot/pidog/examples/12_app_control.py

[Install]
WantedBy=multi-user.target

sudo systemctl daemon-reload
sudo systemctl enable RobApp.service
sudo systemctl start RobApp.service

This executes fine for me on pidog and allows me to connect to my phone immediately on boot. Have to be honest, it looks very similar to yours, so maybe I just got lucky
Unfortunately I cannot verify this on a picarx, which may behave differently… as I don’t have one!

Please be more specific as to how to grant permission to an MP4 video file in OndDrive. I tried to send a line to the file to you but OneDrive rejected the email address. I resorted to an online compression routine attached to this email

(Attachment IMG_0026 (1).mp4 is missing)

Trying; here is part 1 - video of running the app from the CLI

Ok here is a screen shot of the failure occurs when tyring to start 13.

Thanks again Spf650; worked on it this weekend. I’m getting the same error (at getlogin() in sunfounder’s file picarx.py (class definition) with error 6. Does your PiDog code include an equivalent of the line [self.config_file = fileDB(config, 777, getlogin())]? I re-tried the import and it ran (must have been something I did the first time) but when I substituted [getpass.getuser()] for [getlogin] it crashed further down in the code when it tried to use the [config_file] variable. (can only assume that it returned None)

Does your PiDog code include an equivalent of the line [self.config_file = fileDB(config, 777, getlogin())]?
No, I should have checked that! it uses quite a different mechanism

I’m not an expert here, but I cannot see how to get past the Err6 without changing os.login to getpass.getuser (based on the documentation)

However, the problem then seems to be running via systemctl means that /opt/picar-x directory and file picar-x.conf file are then owned by root and cannot be subsequently accessed…

Can I make one final suggestion?

In the Sunfounder py file change

CONFIG = ‘/opt/picar-x/picar-x.conf’

to somewhere that you do have permission? such as

CONFIG = ‘/home/stefan/picar-x.conf’

and ALSO do the os.login swap too.

Very much guesswork though as I don’t have requisite hardware to verify.

Incidentally, I used a local standalone version of Sunfounders filedb.py to come to above conclusions. It is copied below, in the hope that it may be able to help you to progress further.

and these are the two lines that I had to change to get this to run via systemctl, but this may not be exactly your problem of course!

CONFIG = ‘/home/astroberry/picar-x.conf’
config_flie = fileDB(config, 777, getpass.getuser())

#!/usr/bin/env python3
‘’’



‘’’
import os
from time import sleep
import getpass

class fileDB(object):
“”"A file based database.

A file based database, read and write arguements in the specific file.
"""
def __init__(self, db:str, mode:str=None, owner:str=None):  
	'''
	Init the db_file is a file to save the datas.
	
	:param db: the file to save the datas.
	:type db: str
	:param mode: the mode of the file.
	:type mode: str
	:param owner: the owner of the file.
	:type owner: str
	'''

	self.db = db
	# Check if db_file is existed, otherwise create one
	if self.db != None:	
		self.file_check_create(db, mode, owner)
	else:
		raise ValueError('db: Missing file path parameter.')


def file_check_create(self, file_path:str, mode:str=None, owner:str=None):
	"""
	Check if file is existed, otherwise create one.
	
	:param file_path: the file to check
	:type file_path: str
	:param mode: the mode of the file.
	:type mode: str
	:param owner: the owner of the file.
	:type owner: str
	"""
	dir = file_path.rsplit('/',1)[0]
	try:
		if os.path.exists(file_path):
			if not os.path.isfile(file_path):
				print('Could not create file, there is a folder with the same name')
				return
		else:
			if os.path.exists(dir):
				if not os.path.isdir(dir):
					print('Could not create directory, there is a file with the same name')
					return
			else:
				os.makedirs(file_path.rsplit('/',1)[0], mode=0o754)
				sleep(0.001)

			with open(file_path, 'w') as f:
				f.write("# robot-hat config and calibration value of robots\n\n")

		if mode != None:
			os.popen('sudo chmod %s %s'%(mode, file_path))
		if owner != None:
			os.popen('sudo chown -R %s:%s %s'%(owner, owner, file_path.rsplit('/',1)[0]))		
	except Exception as e:
		raise(e) 

def get(self, name, default_value=None):
	"""
	Get value with data's name
	
	:param name: the name of the arguement
	:type name: str
	:param default_value: the default value of the arguement
	:type default_value: str
	:return: the value of the arguement
	:rtype: str
	"""
	try:
		conf = open(self.db,'r')
		lines=conf.readlines()
		conf.close()
		file_len=len(lines)-1
		flag = False
		# Find the arguement and set the value
		for i in range(file_len):
			if lines[i][0] != '#':
				if lines[i].split('=')[0].strip() == name:
					value = lines[i].split('=')[1].replace(' ', '').strip()
					flag = True
		if flag:
			return value
		else:
			return default_value
	except FileNotFoundError:
		conf = open(self.db,'w')
		conf.write("")
		conf.close()
		return default_value
	except :
		return default_value

def set(self, name, value):
	"""
	Set value by with name. Or create one if the arguement does not exist
	
	:param name: the name of the arguement
	:type name: str
	:param value: the value of the arguement
	:type value: str
	"""
	# Read the file
	conf = open(self.db,'r')
	lines=conf.readlines()
	conf.close()
	file_len=len(lines)-1
	flag = False
	# Find the arguement and set the value
	for i in range(file_len):
		if lines[i][0] != '#':
			if lines[i].split('=')[0].strip() == name:
				lines[i] = '%s = %s\n' % (name, value)
				flag = True
	# If arguement does not exist, create one
	if not flag:
		lines.append('%s = %s\n\n' % (name, value))

	# Save the file
	conf = open(self.db,'w')
	conf.writelines(lines)
	conf.close()

#CONFIG = ‘/opt/picar-x/picar-x.conf’
CONFIG = ‘/home/astroberry/picar-x.conf’

config:str=CONFIG

--------- config_flie ---------

#config_flie = fileDB(config, 777, os.getlogin())
config_flie = fileDB(config, 777, getpass.getuser())

Good & logical suggestions - thanks. I tried hacking the picarx.py code as you suggested. No effect that I could tell by moving the config file. Replaced os.getlogin with getpass.getuser() but got an strange error later in the code when trying to reference the fileDB


I stuffed a print in the source. which you can see and the getpass.getuser returned ‘root’ (b/c of the sudo); later I found that os.getlogin returned ‘stefan’. Guessing that the later uses an env var. Switching back to the originial os.getlogin code & it worked but only from the CLI. Will look at the fileDB code next.

Did you accidentally correct Sunfounders spelling when you changed the getuser? I,e to this

self.config_file = fileDB(config, 777, …

Should be this

self.config_flie = fileDB(config, 777, …

LI not IL

Sorry couldn’t help.

Good catch. I actually didn’t notice that. The sunfounder source is consistent and uses config_flie throughout, which might just be a tanslation problem. When I modified the source I commented out the original fileDB line and added one with ‘config_file’ not thinking twice. Changing what I inserted back to ‘config_flie’ and using getpass vs getlogin and it worked from the CLI. Will have to try it as a service

Ok, mostly working now as a service but did have to replace os.getlogin in the picarx.py file with getpass.getuser(). Using my PiCarX.service file in /etc/systemd/system folder it does start execution once the Pi starts up. Will need to spend some more time on it but basicallly works now.

1 Like