def __init__(self): """Whole project is run from this constructor """ # +++++++++++++++ Init +++++++++++++++ # self.keep_run = 0 # used in run for daemon loop, reset by SIGTERM self.log = Log(self) self.settings = Settings(self) self.led = LED(self) self.dbhandle = DBHandle(self) self.mixer = AlsaMixer(self) self.usb = UsbManager(self) self.rfcomm = RFCommServer(self) self.cmd = EvalCmd(self) self.listmngr = PlayListManager(self) self.play = Play(self) self.lirc = LircThread(self) self.buttons = Buttons(self) self.keep_run = 1 self.ret_code = 0 # return code to command line (10 = shutdown) self.led.reset_leds() # invoke arg parser and parse config or create new config if not found self.settings.parse() # check if we can load database, create otherwise self.dbhandle.dbconnect() # restore alsa mixer settings self.mixer.restore_mixer() # load connected usb before bluetooth self.usb.check_new_usb() # initialize sound mixer self.play.init_mixer() # load latest playlist from database self.listmngr.load_active_playlist() # open cmd fifo to read commands self.cmd.open_fifo() # fire up bluetooth service self.rfcomm.start_server_thread() # start lirc thread self.lirc.start() # fire up one thread per each button self.buttons.start() # +++++++++++++++ Daemoninze +++++++++++++++ # self.check_pidfile() self.daemonize() self.create_pidfile() self.led.show_init_done() # +++++++++++++++ Daemon loop +++++++++++++++ # self.run() # +++++++++++++++ Finalize +++++++++++++++ # self.listmngr.save_active() self.led.cleanup() self.delete_pidfile()
class PyBlaster: """Daemon for PiBlaster project""" def __init__(self): """Whole project is run from this constructor """ # +++++++++++++++ Init +++++++++++++++ # self.keep_run = 0 # used in run for daemon loop, reset by SIGTERM self.log = Log(self) self.settings = Settings(self) self.led = LED(self) self.dbhandle = DBHandle(self) self.mixer = AlsaMixer(self) self.usb = UsbManager(self) self.rfcomm = RFCommServer(self) self.cmd = EvalCmd(self) self.listmngr = PlayListManager(self) self.play = Play(self) self.lirc = LircThread(self) self.buttons = Buttons(self) self.keep_run = 1 self.ret_code = 0 # return code to command line (10 = shutdown) self.led.reset_leds() # invoke arg parser and parse config or create new config if not found self.settings.parse() # check if we can load database, create otherwise self.dbhandle.dbconnect() # restore alsa mixer settings self.mixer.restore_mixer() # load connected usb before bluetooth self.usb.check_new_usb() # initialize sound mixer self.play.init_mixer() # load latest playlist from database self.listmngr.load_active_playlist() # open cmd fifo to read commands self.cmd.open_fifo() # fire up bluetooth service self.rfcomm.start_server_thread() # start lirc thread self.lirc.start() # fire up one thread per each button self.buttons.start() # +++++++++++++++ Daemoninze +++++++++++++++ # self.check_pidfile() self.daemonize() self.create_pidfile() self.led.show_init_done() # +++++++++++++++ Daemon loop +++++++++++++++ # self.run() # +++++++++++++++ Finalize +++++++++++++++ # self.listmngr.save_active() self.led.cleanup() self.delete_pidfile() def run(self): """Daemon loop""" # Expensive operations like new usb drive check # should not be run every loop run. poll_count = 0 # -e flag is set, run only init and exit directly. self.keep_run = 0 if self.settings.exitafterinit else 1 reset_poll_count = self.settings.keep_alive_count * 30 * 4 # # # # # # DAEMON LOOP ENTRY # # # # # # while self.keep_run: poll_count += 1 # Check cmd fifo for new commands. if poll_count % 10 == 0: # each 300 ms is enough self.cmd.read_fifo() # Check button events if self.buttons.has_button_events(): self.buttons.read_buttons() # Check bluetooth channel for new messages/connections. self.rfcomm.check_incomming_commands() # Check if lirc thread has command in queue if self.lirc.queue_not_empty(): ircmd = self.lirc.read_command() if ircmd is not None: self.cmd.evalcmd(ircmd, "lirc") # Check if song has ended if poll_count % 4 == 0: # every 120 ms self.play.check_pygame_events() time.sleep(self.settings.polltime / 1000.) # 30ms default in # config if poll_count % self.settings.keep_alive_count == 0: self.led.set_led_green(1) if (poll_count - self.settings.flash_count) % \ self.settings.keep_alive_count == 0: self.led.set_led_green(0) # Check for new USB drives. if poll_count % 30 == 0: # If new usb device found, new usbdev instance will be created, # including dir and mp3 entries. # If usb device got lost, all its entries will be removed. # To check every ~900ms is enough self.usb.check_new_usb() # Multiple of all poll counts reached: # may reset poll count at reset_poll_count. if poll_count >= reset_poll_count: poll_count = 0 # end daemon loop # # # # # # # DAEMON LOOP EXIT # # # # # # # join remaining threads self.mixer.save_mixer() self.lirc.join() self.buttons.join() self.rfcomm.join() self.log.write(log.MESSAGE, "---- closed regularly ----") # end run() # def daemonize(self): """Fork process and disable print in log object""" signal.signal(signal.SIGTERM, self.term_handler) signal.signal(signal.SIGINT, self.term_handler) if not self.settings.daemonize: self.log.init_log() return self.log.write(log.DEBUG1, "daemonizing") try: pid = os.fork() except OSError: self.log.write(log.EMERGENCY, "Failed to fork daemon") raise if pid == 0: os.setsid() try: pid = os.fork() except OSError: self.log.write(log.EMERGENCY, "Failed to fork daemon") raise if pid == 0: os.chdir("/tmp") os.umask(0) else: os._exit(0) else: os._exit(0) self.settings.is_daemonized = True self.log.init_log() self.log.write(log.MESSAGE, "daemonized.") # end daemonize() # def term_handler(self, *args): """ Signal handler to stop daemon loop""" self.keep_run = 0 def check_pidfile(self): """Check if daemon already running, throw if pid file found""" if os.path.exists(self.settings.pidfile): self.log.write(log.EMERGENCY, "Found pid file for pyblaster, " "another process running?") raise Exception("pid file found") def create_pidfile(self): """Write getpid() to file after daemonize()""" try: fpid = open(self.settings.pidfile, "w") except IOError: self.log.write(log.EMERGENCY, "failed to create pidfile %s" % self.settings.pidfile) raise fpid.write("%s\n" % os.getpid()) def delete_pidfile(self): """Try to remove pid file after daemon should exit""" if os.path.exists(self.settings.pidfile): try: os.remove(self.settings.pidfile) except OSError: self.log.write(log.EMERGENCY, "failed to remove pidfile %s" % self.settings.pidfile) raise def kill_other_pyblaster(self): """Check if pid found in pid file and try to kill this (old) process""" if not os.path.exists(self.settings.pidfile): return try: f = open(self.settings.pidfile, "r") except IOError: self.log.write(log.EMERGENCY, "failed to read pidfile %s" % self.settings.pidfile) raise pid = int(f.readline().strip()) print("Trying to kill old process with pid %s..." % pid) try: os.kill(pid, signal.SIGTERM) except OSError: self.log.write(log.EMERGENCY, "failed to kill process with pid %s" % pid) raise exit(0)