class PyBlaster: """Daemon for PiBlaster project""" def __init__(self): """Whole project is run from this constructor """ # +++++++++++++++ Init +++++++++++++++ # self.keep_run = 1 # used in run for daemon loop, reset by SIGTERM self.ret_code = 0 # return code to command line (10 = shutdown) # +++++++++++++++ Objects +++++++++++++++ # # Each inner object will get reference to PyBlaster as self.main. # exceptions in child threads are put here self.ex_queue = queue.Queue() self.log = Log(self) self.settings = Settings(self) self.settings.parse() PB_GPIO.init_gpio(self) self.led = LED(self) self.buttons = Buttons(self) self.lirc = Lirc(self) self.dbhandle = DBHandle(self) self.cmd = Cmd(self) self.mpc = MPC(self) self.bt = RFCommServer(self) self.alsa = AlsaMixer(self) self.i2c = I2C(self) self.usb = UsbDrive(self) # +++++++++++++++ Init Objects +++++++++++++++ # # Make sure to run init functions in proper order! # Some might depend upon others ;) self.led.init_leds() self.dbhandle.dbconnect() self.mpc.connect() self.bt.start_server_thread() self.alsa.init_alsa() self.i2c.open_bus() self.usb.start_uploader_thread() # +++++++++++++++ Daemoninze +++++++++++++++ # self.check_pidfile() self.daemonize() self.create_pidfile() # +++++++++++++++ Daemon loop +++++++++++++++ # self.led.show_init_done() self.buttons.start() self.lirc.start() self.run() # +++++++++++++++ Finalize +++++++++++++++ # self.log.write(log.MESSAGE, "Joining threads...") # join remaining threads self.usb.join() self.bt.join() self.buttons.join() self.lirc.join() self.mpc.join() self.led.join() self.log.write(log.MESSAGE, "leaving...") # cleanup self.mpc.exit_client() self.delete_pidfile() PB_GPIO.cleanup(self) def run(self): """Daemon loop""" # Expensive operations like new usb drive check # should not be run every loop run. poll_count = 0 led_count = 1 # -e flag is set, run only init and exit directly. # self.keep_run = 0 if self.settings.exitafterinit else 1 # # # # # # DAEMON LOOP ENTRY # # # # # # self.log.write(log.MESSAGE, "Entering daemon loop...") while self.keep_run: poll_count += 1 time.sleep(50. / 1000.) # 50ms default in config self.buttons.read_buttons() self.mpc.process_idler_events() self.lirc.read_lirc() self.bt.check_incomming_commands() # TODO: play LEDs while playing -- if paused, do something else... if poll_count % 10 == 0: self.led.play_leds(led_count) led_count += 1 # try: # exc = self.ex_queue.get(block=False) # except queue.Empty: # pass # else: # exc_type, exc_obj, exc_trace = exc # print(exc_type, exc_obj) # print(exc_trace) # self.ret_code = 1 # self.keep_run = False # self.led.indicate_error() # end daemon loop # # # # # # # DAEMON LOOP EXIT # # # # # # 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: exit(0) else: exit(0) self.settings.is_daemonized = True self.log.init_log() self.log.write(log.MESSAGE, "daemonized.") def term_handler(self, *args): """ Signal handler to stop daemon loop""" self.log.write(log.MESSAGE, "Got TERM or INT signal -- leaving!") 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)