Running my own service inplace of mediacenter

Hi

I am using a RPI Zero W as a PVR frontend. When on Raspbian Kodi would freeze whenever i clicked on a channel. OSMC however works like a charm and I have all settings perfectly put in place. Being the greedy person I am, I want to now integrate a Retropie. I wrote a python script based on cec/curses to present a menu that the user can choose with the TV remote. I can login to SSH and run this script. Curses presents the screen on my SSH terminal. I can select OSMC and Kodi shows up. How do I integrate this into the boot sequence? So far I have created a bootlist service that points to this and sudo systemctl disable mediacenter but Rpi freezes when it reaches the end of the boot sequence. I need to plug in a init=/bin/bash to revert stuff. I also tried getting mediacenter service to point to bootlist.py and still it freezes on boot.

  1. Can somebody give me an idea of what should work. I am new to systemd but from what I gather it is a richer brother of init.

  2. Can I permanently plug in init=/bin/bash and start the bootup sequence from shell so that I dont have to repeatedly unplug and replug the SDCard when it freezes?

     	#! /usr/bin/python
    
     	import cec
     	print(cec)
     	import time
     	import curses
     	import subprocess
     	import os
    
     	class pyCecClient:
     	  key =0
     	  keypressed = False
     	  cecconfig = cec.libcec_configuration()
     	  lib = {}
     	  menuList = ["OSMC","RetroPie","Command Line"]
     	  curSel = 0
     	  # don't enable debug logging by default
     	  log_level = cec.CEC_LOG_TRAFFIC
    
     	  # create a new libcec_configuration
     	  def SetConfiguration(self):
     		self.cecconfig.strDeviceName   = "pyLibCec"
     		self.cecconfig.bActivateSource = 0
     		self.cecconfig.deviceTypes.Add(cec.CEC_DEVICE_TYPE_RECORDING_DEVICE)
     		self.cecconfig.clientVersion = cec.LIBCEC_VERSION_CURRENT
    
     	  def SetKeyPressCallback(self, callback):
     		self.cecconfig.SetKeyPressCallback(callback)
     		pass
    
     	  # detect an adapter and return the com port path
     	  def DetectAdapter(self):
     		retval = None
     		adapters = self.lib.DetectAdapters()
     		for adapter in adapters:
     		  print("found a CEC adapter:")
     		  print("port:     " + adapter.strComName)
     		  print("vendor:   " + hex(adapter.iVendorId))
     		  print("product:  " + hex(adapter.iProductId))
     		  retval = adapter.strComName
     		return retval
    
     	  # initialise libCEC
     	  def InitLibCec(self):
     		self.lib = cec.ICECAdapter.Create(self.cecconfig)
     		# print libCEC version and compilation information
     		print("libCEC version " + self.lib.VersionToString(self.cecconfig.serverVersion) + " loaded: " + self.lib.GetLibInfo())
    
     		# search for adapters
     		adapter = self.DetectAdapter()
     		if adapter == None:
     		  print("No adapters found")
     		else:
     		  if self.lib.Open(adapter):
     			print("connection opened")
     			#mythread = threading.Thread(target=self.MainLoop)
     		#mythread.start()
     			stdscr = curses.initscr()
     			curses.start_color()
     			for ctr,option in enumerate(self.menuList):
     			  if ctr == self.curSel:
     				stdscr.addstr(ctr,0,option, curses.A_STANDOUT)
     			  else:
     				stdscr.addstr(ctr,0,option)
     			stdscr.refresh()
     			while True:
     			  time.sleep(1)
     			  if self.keypressed is True:
     				#print self.key
     				if self.key == 0:
     				  break
     				else:
     				  if self.key == 1 and self.curSel>0:
     					self.curSel = self.curSel - 1
     				  elif self.key == 2 and self.curSel<len(self.menuList)-1:
     					self.curSel = self.curSel +1
     				  self.keypressed = False
     				for ctr,option in enumerate(self.menuList):
     				  if ctr == self.curSel:
     					stdscr.addstr(ctr,0,option, curses.A_STANDOUT)
     				  else:
     					stdscr.addstr(ctr,0,option)
     				stdscr.refresh()
    
     			curses.endwin()
     			if self.curSel == 0:
     			  subprocess.Popen("systemctl start mediacenter", shell=True)
     		  else:
     			print("failed to open a connection to the CEC adapter")
    
     	  # key press callback
     	  def KeyPressCallback(self, key, duration):
     		#print("[key pressed] " + str(key))
     		if duration >0:
     		  self.key = key
     		  self.keypressed = True
     		  while self.keypressed:
     			time.sleep(1)
     		return 0
    
    
     	  def __init__(self):
     		self.SetConfiguration()
    
     	# key press callback
     	def key_press_callback(key, duration):
     	  return lib.KeyPressCallback(key, duration)
    
     	if __name__ == '__main__':
     	  # initialise libCEC
     	  lib = pyCecClient()
     	  lib.SetKeyPressCallback(key_press_callback)
     	  # initialise libCEC and enter the main loop
     	  lib.InitLibCec()

I’ll not be able to help you debug your script, but it seems to me that you might need to boot your Pi Zero into what used to be called runlevel 3, which is a non-graphic multi-user state. From this you’ll still have SSH access.

To do this you’d need to run:

sudo systemctl set-default multi-user.target

and to revert to its usual state:

sudo systemctl set-default graphical.target

So if I set default to multi-user, I should be able to SSH in and make modifications. But what would be the command to proceed to graphical.target without rebooting? Something like sudo systemctl continue graphical.target so that my changes to services can be tested. Thanks!

You could either try

sudo systemctl start mediacenter

or

sudo systemctl isolate graphical.target

which will change to the new target/runlevel without needing a reboot.

I just tested sudo systemctl set-default multi-user.target but Kodi still loads on reboot. Looks into systemd folder mediacenter.service is under multi-user.target.wants folder.

Damn, you’re right.

Backtracking a bit, you said:

Could you show us this bootlist service?

I can’t speak for the Pi Zero, since I’ve never owned one, but the Pi2/3 can be booted with mediacenter disabled.

I think I finally figured what you were suggesting. So I disabled mediacenter in systemd. Then, I rebooted. This time I ssh’ed and enabled bootlist manually and checked status. Lo and behold, I come to my real source of problem.

	bootlist.py[823]:   File "/home/osmc/bootlist.py", line 267, in <module>
	bootlist.py[823]:     lib.InitLibCec()
	bootlist.py[823]:   File "/home/osmc/bootlist.py", line 98, in InitLibCec
	bootlist.py[823]:     stdscr = curses.initscr()
	bootlist.py[823]:   File "/usr/lib/python2.7/curses/__init__.py", line 33, in initscr
	bootlist.py[823]:     fd=_sys.__stdout__.fileno())
	bootlist.py[823]: _curses.error: setupterm: could not find terminal

Here is my bootlist service. It is a ripoff from mediacenter but I think the terminal is not setup for curses. Is there a service that bootlist should run after which would set up the terminal? Or is there an alternative graphical interface to curses for me to show a menu for the user to select from?

	[Unit]
	Description = media center application
	After = remote-fs.target network-online.target mysql.service mariadb.service
	Wants = network-online.target

	[Service]
	Type = simple
	TimeoutStopSec = 20
	ExecStart = /home/osmc/bootlist.py >/home/osmc/out.log 2>/home/osmc/err.log
	Restart = on-abort

	[Install]
	WantedBy = multi-user.target

I tried these two variable exports and still status reports same error at current time.

export TERM=linux
export TERMINFO=/lib/terminfo

Just a quick guess but during startup the TV will probably act like the system console, so the device will be something like /dev/console or perhaps /dev/tty0. (The system journal contains the line console [tty0] enabled, FYI.)

Nice suggestion. I was able to throw some text to my TV screen from my SSH session using

echo TextHere > /dev/tty0

I need to now figure out how to get curses to default to /dev/tty0 if it fails to initialize otherwise. Will keep you posted!

So, here is an update. Here is the command in bootlist.service. When I run it from my SSH session, it works perfectly throwing up the menu on my TV and responding to the remote but when I run sudo systemctl start bootlist curses is unable to find terminal. I think there is something in the service configuration that is preventing it from detecting /dev/tty0. Any ideas?

/home/osmc/bootlist.py >/dev/tty0 2>&1

Looks like you’re sending it stdout, which with systemd is probably the sys journal.

I’m out of time for today, but check here, with regard to StandardOutput=

Finally, got it working. From elsewhere, got to know that systemd doesnt understand redirection. So, here is the working service file. No need of any env variables. Thanks a bunch @dillthedog

[Unit]
Description = media center application
After = remote-fs.target network-online.target mysql.service mariadb.service
Wants = network-online.target

[Service]
Type = simple
TimeoutStopSec = 20
ExecStart = /home/osmc/bootlist.py
StandardInput = tty
StandardOutput = tty
TTYPath = /dev/tty0
Restart = on-abort

[Install]
WantedBy = multi-user.target

We unbind FB on Kodi start to save memory and restore in watchdog

Thanks for the info…my code shouldnt be messing with Kodi rite since the redirection will be dead anyways once my code exits after starting mediacenter