class PiPresents(object): def pipresents_version(self): vitems = self.pipresents_issue.split('.') if len(vitems) == 2: # cope with 2 digit version numbers before 1.3.2 return 1000 * int(vitems[0]) + 100 * int(vitems[1]) else: return 1000 * int(vitems[0]) + 100 * int(vitems[1]) + int( vitems[2]) def __init__(self): # gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL) gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_SAVEALL) self.pipresents_issue = "1.4.4" self.pipresents_minorissue = '1.4.4a' # position and size of window without -f command line option self.nonfull_window_width = 0.45 # proportion of width self.nonfull_window_height = 0.7 # proportion of height self.nonfull_window_x = 0 # position of top left corner self.nonfull_window_y = 0 # position of top left corner StopWatch.global_enable = False # set up the handler for SIGTERM signal.signal(signal.SIGTERM, self.handle_sigterm) # **************************************** # Initialisation # *************************************** # get command line options self.options = command_options() # print (self.options) # get Pi Presents code directory pp_dir = sys.path[0] self.pp_dir = pp_dir if not os.path.exists(pp_dir + "/pipresents.py"): if self.options['manager'] is False: tkinter.messagebox.showwarning("Pi Presents", "Bad Application Directory") exit(102) # Initialise logging and tracing Monitor.log_path = pp_dir self.mon = Monitor() # Init in PiPresents only self.mon.init() # uncomment to enable control of logging from within a class # Monitor.enable_in_code = True # enables control of log level in the code for a class - self.mon.set_log_level() # make a shorter list to log/trace only some classes without using enable_in_code. Monitor.classes = [ 'PiPresents', 'HyperlinkShow', 'RadioButtonShow', 'ArtLiveShow', 'ArtMediaShow', 'MediaShow', 'LiveShow', 'MenuShow', 'GapShow', 'Show', 'ArtShow', 'AudioPlayer', 'BrowserPlayer', 'ImagePlayer', 'MenuPlayer', 'MessagePlayer', 'VideoPlayer', 'Player', 'MediaList', 'LiveList', 'ShowList', 'PathManager', 'ControlsManager', 'ShowManager', 'PluginManager', 'IOPluginManager', 'MplayerDriver', 'OMXDriver', 'UZBLDriver', 'TimeOfDay', 'ScreenDriver', 'Animate', 'OSCDriver', 'CounterManager', 'BeepsManager', 'Network', 'Mailer' ] # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver'] # Monitor.classes=['OSCDriver'] # get global log level from command line Monitor.log_level = int(self.options['debug']) Monitor.manager = self.options['manager'] # print self.options['manager'] self.mon.newline(3) self.mon.sched( self, None, "Pi Presents is starting, Version:" + self.pipresents_minorissue + ' at ' + time.strftime("%Y-%m-%d %H:%M.%S")) self.mon.log( self, "Pi Presents is starting, Version:" + self.pipresents_minorissue + ' at ' + time.strftime("%Y-%m-%d %H:%M.%S")) # self.mon.log (self," OS and separator:" + os.name +' ' + os.sep) self.mon.log(self, "sys.path[0] - location of code: " + sys.path[0]) # log versions of Raspbian and omxplayer, and GPU Memory with open("/boot/issue.txt") as ifile: self.mon.log(self, '\nRaspbian: ' + ifile.read()) self.mon.log( self, '\n' + check_output(["omxplayer", "-v"], universal_newlines=True)) self.mon.log( self, '\nGPU Memory: ' + check_output(["vcgencmd", "get_mem", "gpu"], universal_newlines=True)) if os.geteuid() == 0: print('Do not run Pi Presents with sudo') self.mon.log(self, 'Do not run Pi Presents with sudo') self.mon.finish() sys.exit(102) if "DESKTOP_SESSION" not in os.environ: print('Pi Presents must be run from the Desktop') self.mon.log(self, 'Pi Presents must be run from the Desktop') self.mon.finish() sys.exit(102) else: self.mon.log(self, 'Desktop is ' + os.environ['DESKTOP_SESSION']) # optional other classes used self.root = None self.ppio = None self.tod = None self.animate = None self.ioplugin_manager = None self.oscdriver = None self.osc_enabled = False self.tod_enabled = False self.email_enabled = False user = os.getenv('USER') if user is None: tkinter.messagebox.showwarning( "You must be logged in to run Pi Presents") exit(102) if user != 'pi': self.mon.warn(self, "You must be logged as pi to use GPIO") self.mon.log(self, 'User is: ' + user) # self.mon.log(self,"os.getenv('HOME') - user home directory (not used): " + os.getenv('HOME')) # does not work # self.mon.log(self,"os.path.expanduser('~') - user home directory: " + os.path.expanduser('~')) # does not work # check network is available self.network_connected = False self.network_details = False self.interface = '' self.ip = '' self.unit = '' # sets self.network_connected and self.network_details self.init_network() # start the mailer and send email when PP starts self.email_enabled = False if self.network_connected is True: self.init_mailer() if self.email_enabled is True and self.mailer.email_at_start is True: subject = '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime( "%Y-%m-%d %H:%M") message = time.strftime( "%Y-%m-%d %H:%M" ) + '\nUnit: ' + self.unit + ' Profile: ' + self.options[ 'profile'] + '\n ' + self.interface + '\n ' + self.ip self.send_email('start', subject, message) # get profile path from -p option if self.options['profile'] != '': self.pp_profile_path = "/pp_profiles/" + self.options['profile'] else: self.mon.err(self, "Profile not specified in command ") self.end('error', 'Profile not specified with the commands -p option') # get directory containing pp_home from the command, if self.options['home'] == "": home = os.sep + 'home' + os.sep + user + os.sep + "pp_home" else: home = self.options['home'] + os.sep + "pp_home" self.mon.log(self, "pp_home directory is: " + home) # check if pp_home exists. # try for 10 seconds to allow usb stick to automount found = False for i in range(1, 10): self.mon.log(self, "Trying pp_home at: " + home + " (" + str(i) + ')') if os.path.exists(home): found = True self.pp_home = home break time.sleep(1) if found is True: self.mon.log( self, "Found Requested Home Directory, using pp_home at: " + home) else: self.mon.err(self, "Failed to find pp_home directory at " + home) self.end('error', "Failed to find pp_home directory at " + home) # check profile exists self.pp_profile = self.pp_home + self.pp_profile_path if os.path.exists(self.pp_profile): self.mon.sched(self, None, "Running profile: " + self.pp_profile_path) self.mon.log( self, "Found Requested profile - pp_profile directory is: " + self.pp_profile) else: self.mon.err( self, "Failed to find requested profile: " + self.pp_profile) self.end('error', "Failed to find requested profile: " + self.pp_profile) self.mon.start_stats(self.options['profile']) if self.options['verify'] is True: self.mon.err(self, "Validation option not supported - use the editor") self.end('error', 'Validation option not supported - use the editor') # initialise and read the showlist in the profile self.showlist = ShowList() self.showlist_file = self.pp_profile + "/pp_showlist.json" if os.path.exists(self.showlist_file): self.showlist.open_json(self.showlist_file) else: self.mon.err(self, "showlist not found at " + self.showlist_file) self.end('error', "showlist not found at " + self.showlist_file) # check profile and Pi Presents issues are compatible if self.showlist.profile_version() != self.pipresents_version(): self.mon.err( self, "Version of showlist " + self.showlist.profile_version_string + " is not same as Pi Presents") self.end( 'error', "Version of showlist " + self.showlist.profile_version_string + " is not same as Pi Presents") # get the 'start' show from the showlist index = self.showlist.index_of_start_show() if index >= 0: self.showlist.select(index) self.starter_show = self.showlist.selected_show() else: self.mon.err(self, "Show [start] not found in showlist") self.end('error', "Show [start] not found in showlist") # ******************** # SET UP THE GUI # ******************** # turn off the screenblanking and saver if self.options['noblank'] is True: call(["xset", "s", "off"]) call(["xset", "s", "-dpms"]) # find connected displays and create a canvas for each display self.dm = DisplayManager() status, message, self.root = self.dm.init(self.options, self.handle_user_abort) if status != 'normal': self.mon.err(self, message) self.end('error', message) self.mon.log( self, str(DisplayManager.num_displays) + ' Displays are connected:') for display_name in DisplayManager.display_map: status, message, display_id, canvas_obj = self.dm.id_of_canvas( display_name) if status != 'normal': continue width, height = self.dm.real_display_dimensions(display_id) self.mon.log( self, ' - ' + self.dm.name_of_display(display_id) + ' Id: ' + str(display_id) + ' ' + str(width) + '*' + str(height)) canvas_obj.config(bg=self.starter_show['background-colour']) # **************************************** # INITIALISE THE TOUCHSCREEN DRIVER # **************************************** # each driver takes a set of inputs, binds them to symboic names # and sets up a callback which returns the symbolic name when an input event occurs self.sr = ScreenDriver() # read the screen click area config file reason, message = self.sr.read(pp_dir, self.pp_home, self.pp_profile) if reason == 'error': self.end('error', 'cannot find, or error in screen.cfg') # create click areas on the canvases, must be polygon as outline rectangles are not filled as far as find_closest goes reason, message = self.sr.make_click_areas(self.handle_input_event) if reason == 'error': self.mon.err(self, message) self.end('error', message) # **************************************** # INITIALISE THE APPLICATION AND START # **************************************** self.shutdown_required = False self.reboot_required = False self.terminate_required = False self.exitpipresents_required = False # initialise the Beeps Manager self.beepsmanager = BeepsManager() self.beepsmanager.init(self.pp_home, self.pp_profile) # initialise the I/O plugins by importing their drivers self.ioplugin_manager = IOPluginManager() reason, message = self.ioplugin_manager.init(self.pp_dir, self.pp_profile, self.root, self.handle_input_event, self.pp_home) if reason == 'error': # self.mon.err(self,message) self.end('error', message) # kick off animation sequencer self.animate = Animate() self.animate.init(pp_dir, self.pp_home, self.pp_profile, self.root, 200, self.handle_output_event) self.animate.poll() #create a showmanager ready for time of day scheduler and osc server show_id = -1 self.show_manager = ShowManager(show_id, self.showlist, self.starter_show, self.root, self.pp_dir, self.pp_profile, self.pp_home) # first time through set callback to terminate Pi Presents if all shows have ended. self.show_manager.init(self.all_shows_ended_callback, self.handle_command, self.showlist) # Register all the shows in the showlist reason, message = self.show_manager.register_shows() if reason == 'error': self.mon.err(self, message) self.end('error', message) # Init OSCDriver, read config and start OSC server self.osc_enabled = False if self.network_connected is True: if os.path.exists(self.pp_profile + os.sep + 'pp_io_config' + os.sep + 'osc.cfg'): self.oscdriver = OSCDriver() reason, message = self.oscdriver.init( self.pp_profile, self.unit, self.interface, self.ip, self.handle_command, self.handle_input_event, self.e_osc_handle_animate) if reason == 'error': self.mon.err(self, message) self.end('error', message) else: self.osc_enabled = True self.root.after(1000, self.oscdriver.start_server()) # initialise ToD scheduler calculating schedule for today self.tod = TimeOfDay() reason, message, self.tod_enabled = self.tod.init( pp_dir, self.pp_home, self.pp_profile, self.showlist, self.root, self.handle_command) if reason == 'error': self.mon.err(self, message) self.end('error', message) # warn if the network not available when ToD required if self.tod_enabled is True and self.network_connected is False: self.mon.warn( self, 'Network not connected so Time of Day scheduler may be using the internal clock' ) # init the counter manager self.counter_manager = CounterManager() if self.starter_show['counters-store'] == 'yes': store_enable = True else: store_enable = False reason, message = self.counter_manager.init( self.pp_profile + '/counters.cfg', store_enable, self.options['loadcounters'], self.starter_show['counters-initial']) if reason == 'error': self.mon.err(self, message) self.end('error', message) # warn about start shows and scheduler if self.starter_show['start-show'] == '' and self.tod_enabled is False: self.mon.sched( self, None, "No Start Shows in Start Show and no shows scheduled") self.mon.warn( self, "No Start Shows in Start Show and no shows scheduled") if self.starter_show['start-show'] != '' and self.tod_enabled is True: self.mon.sched( self, None, "Start Shows in Start Show and shows scheduled - conflict?") self.mon.warn( self, "Start Shows in Start Show and shows scheduled - conflict?") # run the start shows self.run_start_shows() # kick off the time of day scheduler which may run additional shows if self.tod_enabled is True: self.tod.poll() # start the I/O plugins input event generation self.ioplugin_manager.start() # start Tkinters event loop self.root.mainloop() # ********************* # RUN START SHOWS # ******************** def run_start_shows(self): self.mon.trace(self, 'run start shows') # parse the start shows field and start the initial shows show_refs = self.starter_show['start-show'].split() for show_ref in show_refs: reason, message = self.show_manager.control_a_show( show_ref, 'open') if reason == 'error': self.mon.err(self, message) # ********************* # User inputs # ******************** def e_osc_handle_animate(self, line): #jump out of server thread self.root.after(1, lambda arg=line: self.osc_handle_animate(arg)) def osc_handle_animate(self, line): self.mon.log(self, "animate command received: " + line) #osc sends output events as a string reason, message, delay, name, param_type, param_values = self.animate.parse_animate_fields( line) if reason == 'error': self.mon.err(self, message) self.end(reason, message) self.handle_output_event(name, param_type, param_values, 0) # output events are animate commands def handle_output_event(self, symbol, param_type, param_values, req_time): reason, message = self.ioplugin_manager.handle_output_event( symbol, param_type, param_values, req_time) if reason == 'error': self.mon.err(self, message) self.end(reason, message) # all input events call this callback providing a symbolic name. # handle events that affect PP overall, otherwise pass to all active shows def handle_input_event(self, symbol, source): self.mon.log(self, "event received: " + symbol + ' from ' + source) if symbol == 'pp-terminate': self.handle_user_abort() elif symbol == 'pp-shutdown': self.mon.err( self, 'pp-shutdown removed in version 1.3.3a, see Release Notes') self.end( 'error', 'pp-shutdown removed in version 1.3.3a, see Release Notes') elif symbol == 'pp-shutdownnow': # need root.after to grt out of st thread self.root.after(1, self.shutdownnow_pressed) return elif symbol == 'pp-exitpipresents': self.exitpipresents_required = True if self.show_manager.all_shows_exited() is True: # need root.after to grt out of st thread self.root.after(1, self.e_all_shows_ended_callback) return reason, message = self.show_manager.exit_all_shows() else: # pass the input event to all registered shows for show in self.show_manager.shows: show_obj = show[ShowManager.SHOW_OBJ] if show_obj is not None: show_obj.handle_input_event(symbol) # commands are generated by tracks and shows # they can open or close shows, generate input events and do special tasks # commands also generate osc outputs to other computers # handles one command provided as a line of text def handle_command(self, command_text, source='', show=''): # print 'PIPRESENTS ',command_text,'\n Source',source,'from',show self.mon.log(self, "command received: " + command_text) if command_text.strip() == "": return fields = command_text.split() if fields[0] in ('osc', 'OSC'): if self.osc_enabled is True: status, message = self.oscdriver.parse_osc_command(fields[1:]) if status == 'warn': self.mon.warn(self, message) if status == 'error': self.mon.err(self, message) self.end('error', message) return else: return if fields[0] == 'counter': status, message = self.counter_manager.parse_counter_command( fields[1:]) if status == 'error': self.mon.err(self, message) self.end('error', message) return if fields[0] == 'beep': # cheat, field 0 will always be beep message, fields = self.beepsmanager.parse_beep(command_text) if message != '': self.mon.err(self, message) self.end('error', message) return location = self.beepsmanager.complete_path(fields[1]) if not os.path.exists(location): message = 'Beep file does not exist: ' + location self.mon.err(self, message) self.end('error', message) return else: self.beepsmanager.do_beep(location) return show_command = fields[0] if len(fields) > 1: show_ref = fields[1] else: show_ref = '' if show_command in ('open', 'close', 'closeall', 'openexclusive'): self.mon.sched(self, TimeOfDay.now, command_text + ' received from show:' + show) if self.shutdown_required is False and self.terminate_required is False: reason, message = self.show_manager.control_a_show( show_ref, show_command) else: return elif show_command == 'monitor': self.handle_monitor_command(show_ref) return elif show_command == 'cec': self.handle_cec_command(show_ref) return elif show_command == 'event': self.handle_input_event(show_ref, 'Show Control') return elif show_command == 'exitpipresents': self.exitpipresents_required = True if self.show_manager.all_shows_exited() is True: # need root.after to get out of st thread self.root.after(1, self.e_all_shows_ended_callback) return else: reason, message = self.show_manager.exit_all_shows() elif show_command == 'shutdownnow': # need root.after to get out of st thread self.root.after(1, self.shutdownnow_pressed) return elif show_command == 'reboot': # need root.after to get out of st thread self.root.after(1, self.reboot_pressed) return else: reason = 'error' message = 'command not recognised: ' + show_command if reason == 'error': self.mon.err(self, message) return def handle_monitor_command(self, command): if command == 'on': os.system('vcgencmd display_power 1 >/dev/null') elif command == 'off': os.system('vcgencmd display_power 0 >/dev/null') def handle_cec_command(self, command): if command == 'on': os.system('echo "on 0" | cec-client -s') elif command == 'standby': os.system('echo "standby 0" | cec-client -s') elif command == 'scan': os.system('echo scan | cec-client -s -d 1') # deal with differnt commands/input events def shutdownnow_pressed(self): self.shutdown_required = True if self.show_manager.all_shows_exited() is True: self.all_shows_ended_callback('normal', 'no shows running') else: # calls exit method of all shows, results in all_shows_closed_callback self.show_manager.exit_all_shows() def reboot_pressed(self): self.reboot_required = True if self.show_manager.all_shows_exited() is True: self.all_shows_ended_callback('normal', 'no shows running') else: # calls exit method of all shows, results in all_shows_closed_callback self.show_manager.exit_all_shows() def handle_sigterm(self, signum, fframe): self.mon.log(self, 'SIGTERM received - ' + str(signum)) self.terminate() def handle_user_abort(self): self.mon.log(self, 'User abort received') self.terminate() def terminate(self): self.mon.log(self, "terminate received") self.terminate_required = True needs_termination = False for show in self.show_manager.shows: # print show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF] if show[ShowManager.SHOW_OBJ] is not None: needs_termination = True self.mon.log( self, "Sent terminate to show " + show[ShowManager.SHOW_REF]) # call shows terminate method # eventually the show will exit and after all shows have exited all_shows_callback will be executed. show[ShowManager.SHOW_OBJ].terminate() if needs_termination is False: self.end('killed', 'killed - no termination of shows required') # ****************************** # Ending Pi Presents after all the showers and players are closed # ************************** def e_all_shows_ended_callback(self): self.all_shows_ended_callback('normal', 'no shows running') # callback from ShowManager when all shows have ended def all_shows_ended_callback(self, reason, message): for display_name in DisplayManager.display_map: status, message, display_id, canvas_obj = self.dm.id_of_canvas( display_name) if status != 'normal': continue canvas_obj.config(bg=self.starter_show['background-colour']) if reason in ( 'killed', 'error' ) or self.shutdown_required is True or self.exitpipresents_required is True or self.reboot_required is True: self.end(reason, message) def end(self, reason, message): self.mon.log(self, "Pi Presents ending with reason: " + reason) if self.root is not None: self.root.destroy() self.tidy_up() if reason == 'killed': if self.email_enabled is True and self.mailer.email_on_terminate is True: subject = '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated' message = time.strftime( "%Y-%m-%d %H:%M" ) + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip self.send_email(reason, subject, message) self.mon.sched(self, None, "Pi Presents Terminated, au revoir\n") self.mon.log(self, "Pi Presents Terminated, au revoir") # close logging files self.mon.finish() print('Uncollectable Garbage', gc.collect()) # objgraph.show_backrefs(objgraph.by_type('Canvas'),filename='backrefs.png') sys.exit(101) elif reason == 'error': if self.email_enabled is True and self.mailer.email_on_error is True: subject = '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error' message_text = 'Error message: ' + message + '\n' + time.strftime( "%Y-%m-%d %H:%M" ) + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip self.send_email(reason, subject, message_text) self.mon.sched(self, None, "Pi Presents closing because of error, sorry\n") self.mon.log(self, "Pi Presents closing because of error, sorry") # close logging files self.mon.finish() print('uncollectable garbage', gc.collect()) sys.exit(102) else: self.mon.sched(self, None, "Pi Presents exiting normally, bye\n") self.mon.log(self, "Pi Presents exiting normally, bye") # close logging files self.mon.finish() if self.reboot_required is True: # print 'REBOOT' call(['sudo', 'reboot']) if self.shutdown_required is True: # print 'SHUTDOWN' call(['sudo', 'shutdown', 'now', 'SHUTTING DOWN']) print('uncollectable garbage', gc.collect()) sys.exit(100) # tidy up all the peripheral bits of Pi Presents def tidy_up(self): self.handle_monitor_command('on') self.mon.log(self, "Tidying Up") # turn screen blanking back on if self.options['noblank'] is True: call(["xset", "s", "on"]) call(["xset", "s", "+dpms"]) # tidy up animation if self.animate is not None: self.animate.terminate() # tidy up i/o plugins if self.ioplugin_manager != None: self.ioplugin_manager.terminate() if self.osc_enabled is True: self.oscdriver.terminate() # tidy up time of day scheduler if self.tod_enabled is True: self.tod.terminate() # ******************************* # Connecting to network and email # ******************************* def init_network(self): timeout = int(self.options['nonetwork']) if timeout == 0: self.network_connected = False self.unit = '' self.ip = '' self.interface = '' return self.network = Network() self.network_connected = False # try to connect to network self.mon.log(self, 'Waiting up to ' + str(timeout) + ' seconds for network') success = self.network.wait_for_network(timeout) if success is False: self.mon.warn( self, 'Failed to connect to network after ' + str(timeout) + ' seconds') # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock") return self.network_connected = True self.mon.sched( self, None, 'Time after network check is ' + time.strftime("%Y-%m-%d %H:%M.%S")) self.mon.log( self, 'Time after network check is ' + time.strftime("%Y-%m-%d %H:%M.%S")) # Get web configuration self.network_details = False network_options_file_path = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_web.cfg' if not os.path.exists(network_options_file_path): self.mon.warn( self, "pp_web.cfg not found at " + network_options_file_path) return self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path) self.network.read_config(network_options_file_path) self.unit = self.network.unit # get interface and IP details of preferred interface self.interface, self.ip = self.network.get_preferred_ip() if self.interface == '': self.network_connected = False return self.network_details = True self.mon.log( self, 'Network details ' + self.unit + ' ' + self.interface + ' ' + self.ip) def init_mailer(self): self.email_enabled = False email_file_path = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_email.cfg' if not os.path.exists(email_file_path): self.mon.log(self, 'pp_email.cfg not found at ' + email_file_path) return self.mon.log(self, 'Found pp_email.cfg at ' + email_file_path) self.mailer = Mailer() self.mailer.read_config(email_file_path) # all Ok so can enable email if config file allows it. if self.mailer.email_allowed is True: self.email_enabled = True self.mon.log(self, 'Email Enabled') def try_connect(self): tries = 1 while True: success, error = self.mailer.connect() if success is True: return True else: self.mon.log( self, 'Failed to connect to email SMTP server ' + str(tries) + '\n ' + str(error)) tries += 1 if tries > 5: self.mon.log( self, 'Failed to connect to email SMTP server after ' + str(tries)) return False def send_email(self, reason, subject, message): if self.try_connect() is False: return False else: success, error = self.mailer.send(subject, message) if success is False: self.mon.log(self, 'Failed to send email: ' + str(error)) success, error = self.mailer.disconnect() if success is False: self.mon.log(self, 'Failed disconnect after send:' + str(error)) return False else: self.mon.log(self, 'Sent email for ' + reason) success, error = self.mailer.disconnect() if success is False: self.mon.log( self, 'Failed disconnect from email server ' + str(error)) return True
class PiPresents(object): def pipresents_version(self): vitems=self.pipresents_issue.split('.') if len(vitems)==2: # cope with 2 digit version numbers before 1.3.2 return 1000*int(vitems[0])+100*int(vitems[1]) else: return 1000*int(vitems[0])+100*int(vitems[1])+int(vitems[2]) def __init__(self): gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL) self.pipresents_issue="1.3.2" self.pipresents_minorissue = '1.3.2a' # position and size of window without -f command line option self.nonfull_window_width = 0.45 # proportion of width self.nonfull_window_height= 0.7 # proportion of height self.nonfull_window_x = 0 # position of top left corner self.nonfull_window_y=0 # position of top left corner StopWatch.global_enable=False # set up the handler for SIGTERM signal.signal(signal.SIGTERM,self.handle_sigterm) # **************************************** # Initialisation # *************************************** # get command line options self.options=command_options() # get Pi Presents code directory pp_dir=sys.path[0] self.pp_dir=pp_dir if not os.path.exists(pp_dir+"/pipresents.py"): if self.options['manager'] is False: tkMessageBox.showwarning("Pi Presents","Bad Application Directory") exit(102) # Initialise logging and tracing Monitor.log_path=pp_dir self.mon=Monitor() # Init in PiPresents only self.mon.init() # uncomment to enable control of logging from within a class # Monitor.enable_in_code = True # enables control of log level in the code for a class - self.mon.set_log_level() # make a shorter list to log/trace only some classes without using enable_in_code. Monitor.classes = ['PiPresents', 'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow', 'GapShow','Show','ArtShow', 'AudioPlayer','BrowserPlayer','ImagePlayer','MenuPlayer','MessagePlayer','VideoPlayer','Player', 'MediaList','LiveList','ShowList', 'PathManager','ControlsManager','ShowManager','PluginManager', 'MplayerDriver','OMXDriver','UZBLDriver', 'KbdDriver','GPIODriver','TimeOfDay','ScreenDriver','Animate','OSCDriver', 'Network','Mailer' ] # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver'] # get global log level from command line Monitor.log_level = int(self.options['debug']) Monitor.manager = self.options['manager'] # print self.options['manager'] self.mon.newline(3) self.mon.sched (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue + ' at '+time.strftime("%Y-%m-%d %H:%M.%S")) self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue+ ' at '+time.strftime("%Y-%m-%d %H:%M.%S")) # self.mon.log (self," OS and separator:" + os.name +' ' + os.sep) self.mon.log(self,"sys.path[0] - location of code: "+sys.path[0]) # log versions of Raspbian and omxplayer, and GPU Memory with open("/boot/issue.txt") as file: self.mon.log(self,'\nRaspbian: '+file.read()) self.mon.log(self,'\n'+check_output(["omxplayer", "-v"])) self.mon.log(self,'\nGPU Memory: '+check_output(["vcgencmd", "get_mem", "gpu"])) if "DESKTOP_SESSION" not in os.environ: print 'Pi Presents must be run from the Desktop' self.mon.log(self,'Pi Presents must be run from the Desktop') self.mon.finish() sys.exit(102) else: self.mon.log(self,'Desktop is '+ os.environ['DESKTOP_SESSION']) # optional other classes used self.root=None self.ppio=None self.tod=None self.animate=None self.gpiodriver=None self.oscdriver=None self.osc_enabled=False self.gpio_enabled=False self.tod_enabled=False self.email_enabled=False if os.geteuid() == 0: self.mon.err(self,'Do not run Pi Presents with sudo') self.end('error','Do not run Pi Presents with sudo') user=os.getenv('USER') self.mon.log(self,'User is: '+ user) # self.mon.log(self,"os.getenv('HOME') - user home directory (not used): " + os.getenv('HOME')) # does not work # self.mon.log(self,"os.path.expanduser('~') - user home directory: " + os.path.expanduser('~')) # does not work # check network is available self.network_connected=False self.network_details=False self.interface='' self.ip='' self.unit='' # sets self.network_connected and self.network_details self.init_network() # start the mailer and send email when PP starts self.email_enabled=False if self.network_connected is True: self.init_mailer() if self.email_enabled is True and self.mailer.email_at_start is True: subject= '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime("%Y-%m-%d %H:%M") message = time.strftime("%Y-%m-%d %H:%M") + '\nUnit: ' + self.unit + ' Profile: '+ self.options['profile']+ '\n ' + self.interface + '\n ' + self.ip self.send_email('start',subject,message) # get profile path from -p option if self.options['profile'] != '': self.pp_profile_path="/pp_profiles/"+self.options['profile'] else: self.mon.err(self,"Profile not specified in command ") self.end('error','Profile not specified with the commands -p option') # get directory containing pp_home from the command, if self.options['home'] == "": home = os.sep+ 'home' + os.sep + user + os.sep+"pp_home" else: home = self.options['home'] + os.sep+ "pp_home" self.mon.log(self,"pp_home directory is: " + home) # check if pp_home exists. # try for 10 seconds to allow usb stick to automount found=False for i in range (1, 10): self.mon.log(self,"Trying pp_home at: " + home + " (" + str(i)+')') if os.path.exists(home): found=True self.pp_home=home break time.sleep (1) if found is True: self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home) else: self.mon.err(self,"Failed to find pp_home directory at " + home) self.end('error',"Failed to find pp_home directory at " + home) # check profile exists self.pp_profile=self.pp_home+self.pp_profile_path if os.path.exists(self.pp_profile): self.mon.sched(self,"Running profile: " + self.pp_profile_path) self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile) else: self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile) self.end('error',"Failed to find requested profile: "+ self.pp_profile) self.mon.start_stats(self.options['profile']) if self.options['verify'] is True: val =Validator() if val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) is False: self.mon.err(self,"Validation Failed") self.end('error','Validation Failed') # initialise and read the showlist in the profile self.showlist=ShowList() self.showlist_file= self.pp_profile+ "/pp_showlist.json" if os.path.exists(self.showlist_file): self.showlist.open_json(self.showlist_file) else: self.mon.err(self,"showlist not found at "+self.showlist_file) self.end('error',"showlist not found at "+self.showlist_file) # check profile and Pi Presents issues are compatible if self.showlist.profile_version() != self.pipresents_version(): self.mon.err(self,"Version of showlist " + self.showlist.profile_version_string + " is not same as Pi Presents") self.end('error',"Version of showlist " + self.showlist.profile_version_string + " is not same as Pi Presents") # get the 'start' show from the showlist index = self.showlist.index_of_show('start') if index >=0: self.showlist.select(index) self.starter_show=self.showlist.selected_show() else: self.mon.err(self,"Show [start] not found in showlist") self.end('error',"Show [start] not found in showlist") # ******************** # SET UP THE GUI # ******************** # turn off the screenblanking and saver if self.options['noblank'] is True: call(["xset","s", "off"]) call(["xset","s", "-dpms"]) self.root=Tk() self.title='Pi Presents - '+ self.pp_profile self.icon_text= 'Pi Presents' self.root.title(self.title) self.root.iconname(self.icon_text) self.root.config(bg=self.starter_show['background-colour']) self.mon.log(self, 'monitor screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixels') if self.options['screensize'] =='': self.screen_width = self.root.winfo_screenwidth() self.screen_height = self.root.winfo_screenheight() else: reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize']) if reason =='error': self.mon.err(self,message) self.end('error',message) self.mon.log(self, 'forced screen dimensions (--screensize) are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixels') # set window dimensions and decorations if self.options['fullscreen'] is False: self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width) self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height) self.window_x=self.nonfull_window_x self.window_y=self.nonfull_window_y self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y)) else: self.window_width=self.screen_width self.window_height=self.screen_height self.root.attributes('-fullscreen', True) os.system('unclutter &') self.window_x=0 self.window_y=0 self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y)) self.root.attributes('-zoomed','1') # canvas cover the whole screen whatever the size of the window. self.canvas_height=self.screen_height self.canvas_width=self.screen_width # make sure focus is set. self.root.focus_set() # define response to main window closing. self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort) # setup a canvas onto which will be drawn the images or text self.canvas = Canvas(self.root, bg=self.starter_show['background-colour']) if self.options['fullscreen'] is True: self.canvas.config(height=self.canvas_height, width=self.canvas_width, highlightthickness=0) else: self.canvas.config(height=self.canvas_height, width=self.canvas_width, highlightthickness=1, highlightcolor='yellow') self.canvas.place(x=0,y=0) # self.canvas.config(bg='black') self.canvas.focus_set() # **************************************** # INITIALISE THE INPUT DRIVERS # **************************************** # each driver takes a set of inputs, binds them to symboic names # and sets up a callback which returns the symbolic name when an input event occurs/ # use keyboard driver to bind keys to symbolic names and to set up callback kbd=KbdDriver() if kbd.read(pp_dir,self.pp_home,self.pp_profile) is False: self.end('error','cannot find, or error in keys.cfg') kbd.bind_keys(self.root,self.handle_input_event) self.sr=ScreenDriver() # read the screen click area config file reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile) if reason == 'error': self.end('error','cannot find, or error in screen.cfg') # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes # click areas are made on the Pi Presents canvas not the show canvases. reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event) if reason == 'error': self.mon.err(self,message) self.end('error',message) # **************************************** # INITIALISE THE APPLICATION AND START # **************************************** self.shutdown_required=False self.terminate_required=False self.exitpipresents_required=False # delete omxplayer dbus files # if os.path.exists("/tmp/omxplayerdbus.{}".format(user)): # os.remove("/tmp/omxplayerdbus.{}".format(user)) # if os.path.exists("/tmp/omxplayerdbus.{}.pid".format(user)): # os.remove("/tmp/omxplayerdbus.{}.pid".format(user)) # kick off GPIO if enabled by command line option self.gpio_enabled=False if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+os.sep+ 'gpio.cfg'): # initialise the GPIO self.gpiodriver=GPIODriver() reason,message=self.gpiodriver.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.handle_input_event) if reason == 'error': self.end('error',message) else: self.gpio_enabled=True # and start polling gpio self.gpiodriver.poll() # kick off animation sequencer self.animate = Animate() self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event) self.animate.poll() #create a showmanager ready for time of day scheduler and osc server show_id=-1 self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home) # first time through set callback to terminate Pi Presents if all shows have ended. self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist) # Register all the shows in the showlist reason,message=self.show_manager.register_shows() if reason == 'error': self.mon.err(self,message) self.end('error',message) # Init OSCDriver, read config and start OSC server self.osc_enabled=False if self.network_connected is True: if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'): self.oscdriver=OSCDriver() reason,message=self.oscdriver.init(self.pp_profile,self.handle_command,self.handle_input_event,self.e_osc_handle_output_event) if reason == 'error': self.mon.err(self,message) self.end('error',message) else: self.osc_enabled=True self.root.after(1000,self.oscdriver.start_server()) # enable ToD scheduler if schedule exists if os.path.exists(self.pp_profile + os.sep + 'schedule.json'): self.tod_enabled = True else: self.tod_enabled=False # warn if the network not available when ToD required if self.tod_enabled is True and self.network_connected is False: self.mon.warn(self,'Network not connected so Time of Day scheduler may be using the internal clock') # warn about start shows and scheduler if self.starter_show['start-show']=='' and self.tod_enabled is False: self.mon.sched(self,"No Start Shows in Start Show and no shows scheduled") self.mon.warn(self,"No Start Shows in Start Show and no shows scheduled") if self.starter_show['start-show'] !='' and self.tod_enabled is True: self.mon.sched(self,"Start Shows in Start Show and shows scheduled - conflict?") self.mon.warn(self,"Start Shows in Start Show and shows scheduled - conflict?") # run the start shows self.run_start_shows() # kick off the time of day scheduler which may run additional shows if self.tod_enabled is True: self.tod=TimeOfDay() self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.root,self.handle_command) self.tod.poll() # start Tkinters event loop self.root.mainloop( ) def parse_screen(self,size_text): fields=size_text.split('*') if len(fields)!=2: return 'error','do not understand --screensize comand option',0,0 elif fields[0].isdigit() is False or fields[1].isdigit() is False: return 'error','dimensions are not positive integers in --screensize',0,0 else: return 'normal','',int(fields[0]),int(fields[1]) # ********************* # RUN START SHOWS # ******************** def run_start_shows(self): self.mon.trace(self,'run start shows') # parse the start shows field and start the initial shows show_refs=self.starter_show['start-show'].split() for show_ref in show_refs: reason,message=self.show_manager.control_a_show(show_ref,'open') if reason == 'error': self.mon.err(self,message) # ********************* # User inputs # ******************** # handles one command provided as a line of text def handle_command(self,command_text,source='',show=''): # print 'PIPRESENTS ',command_text,source,'from',show self.mon.log(self,"command received: " + command_text) if command_text.strip()=="": return if command_text[0]=='/': if self.osc_enabled is True: self.oscdriver.send_command(command_text) return fields= command_text.split() show_command=fields[0] if len(fields)>1: show_ref=fields[1] else: show_ref='' if show_command in ('open','close'): self.mon.sched(self, command_text + ' received from show:'+show) if self.shutdown_required is False and self.terminate_required is False: reason,message=self.show_manager.control_a_show(show_ref,show_command) else: return elif show_command =='monitor': self.handle_monitor_command(show_ref) return elif show_command == 'event': self.handle_input_event(show_ref,'Show Control') return elif show_command == 'exitpipresents': self.exitpipresents_required=True if self.show_manager.all_shows_exited() is True: # need root.after to get out of st thread self.root.after(1,self.e_all_shows_ended_callback) return else: reason,message= self.show_manager.exit_all_shows() elif show_command == 'shutdownnow': # need root.after to get out of st thread self.root.after(1,self.e_shutdown_pressed) return else: reason='error' message = 'command not recognised: '+ show_command if reason=='error': self.mon.err(self,message) return def handle_monitor_command(self,command): if command == 'on': os.system('vcgencmd display_power 1 >/dev/null') elif command == 'off': os.system('vcgencmd display_power 0 >/dev/null') def e_all_shows_ended_callback(self): self.all_shows_ended_callback('normal','no shows running') def e_shutdown_pressed(self): self.shutdown_pressed('now') def e_osc_handle_output_event(self,line): #jump out of server thread self.root.after(1, lambda arg=line: self.osc_handle_output_event(arg)) def osc_handle_output_event(self,line): self.mon.log(self,"output event received: "+ line) #osc sends output events as a string reason,message,delay,name,param_type,param_values=self.animate.parse_animate_fields(line) if reason == 'error': self.mon.err(self,message) self.end(reason,message) self.handle_output_event(name,param_type,param_values,0) def handle_output_event(self,symbol,param_type,param_values,req_time): if self.gpio_enabled is True: reason,message=self.gpiodriver.handle_output_event(symbol,param_type,param_values,req_time) if reason =='error': self.mon.err(self,message) self.end(reason,message) else: self.mon.warn(self,'GPIO not enabled') # all input events call this callback with a symbolic name. # handle events that affect PP overall, otherwise pass to all active shows def handle_input_event(self,symbol,source): self.mon.log(self,"event received: "+symbol + ' from '+ source) if symbol == 'pp-terminate': self.handle_user_abort() elif symbol == 'pp-shutdown': self.shutdown_pressed('delay') elif symbol == 'pp-shutdownnow': # need root.after to grt out of st thread self.root.after(1,self.e_shutdown_pressed) return elif symbol == 'pp-exitpipresents': self.exitpipresents_required=True if self.show_manager.all_shows_exited() is True: # need root.after to grt out of st thread self.root.after(1,self.e_all_shows_ended_callback) return reason,message= self.show_manager.exit_all_shows() else: # events for shows affect the show and could cause it to exit. for show in self.show_manager.shows: show_obj=show[ShowManager.SHOW_OBJ] if show_obj is not None: show_obj.handle_input_event(symbol) def shutdown_pressed(self, when): if when == 'delay': self.root.after(5000,self.on_shutdown_delay) else: self.shutdown_required=True if self.show_manager.all_shows_exited() is True: self.all_shows_ended_callback('normal','no shows running') else: # calls exit method of all shows, results in all_shows_closed_callback self.show_manager.exit_all_shows() def on_shutdown_delay(self): # 5 second delay is up, if shutdown button still pressed then shutdown if self.gpiodriver.shutdown_pressed() is True: self.shutdown_required=True if self.show_manager.all_shows_exited() is True: self.all_shows_ended_callback('normal','no shows running') else: # calls exit method of all shows, results in all_shows_closed_callback self.show_manager.exit_all_shows() def handle_sigterm(self,signum,frame): self.mon.log(self,'SIGTERM received - '+ str(signum)) self.terminate() def handle_user_abort(self): self.mon.log(self,'User abort received') self.terminate() def terminate(self): self.mon.log(self, "terminate received") self.terminate_required=True needs_termination=False for show in self.show_manager.shows: # print show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF] if show[ShowManager.SHOW_OBJ] is not None: needs_termination=True self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF]) # call shows terminate method # eventually the show will exit and after all shows have exited all_shows_callback will be executed. show[ShowManager.SHOW_OBJ].terminate() if needs_termination is False: self.end('killed','killed - no termination of shows required') # ****************************** # Ending Pi Presents after all the showers and players are closed # ************************** # callback from ShowManager when all shows have ended def all_shows_ended_callback(self,reason,message): self.canvas.config(bg=self.starter_show['background-colour']) if reason in ('killed','error') or self.shutdown_required is True or self.exitpipresents_required is True: self.end(reason,message) def end(self,reason,message): self.mon.log(self,"Pi Presents ending with reason: " + reason) if self.root is not None: self.root.destroy() self.tidy_up() # gc.collect() # print gc.garbage if reason == 'killed': if self.email_enabled is True and self.mailer.email_on_terminate is True: subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated' message = time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip self.send_email(reason,subject,message) self.mon.sched(self, "Pi Presents Terminated, au revoir\n") self.mon.log(self, "Pi Presents Terminated, au revoir") # close logging files self.mon.finish() sys.exit(101) elif reason == 'error': if self.email_enabled is True and self.mailer.email_on_error is True: subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error' message_text = 'Error message: '+ message + '\n'+ time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip self.send_email(reason,subject,message_text) self.mon.sched(self, "Pi Presents closing because of error, sorry\n") self.mon.log(self, "Pi Presents closing because of error, sorry") # close logging files self.mon.finish() sys.exit(102) else: self.mon.sched(self,"Pi Presents exiting normally, bye\n") self.mon.log(self,"Pi Presents exiting normally, bye") # close logging files self.mon.finish() if self.shutdown_required is True: # print 'SHUTDOWN' call (['sudo','shutdown','now','SHUTTING DOWN']) sys.exit(100) def init_network(self): timeout=int(self.options['nonetwork']) if timeout== 0: self.network_connected=False self.unit='' self.ip='' self.interface='' return self.network=Network() self.network_connected=False # try to connect to network self.mon.log (self, 'Waiting up to '+ str(timeout) + ' seconds for network') success=self.network.wait_for_network(timeout) if success is False: self.mon.warn(self,'Failed to connect to network after ' + str(timeout) + ' seconds') # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock") return self.network_connected=True self.mon.sched (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S")) self.mon.log (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S")) # Get web configuration self.network_details=False network_options_file_path=self.pp_dir+os.sep+'pp_config'+os.sep+'pp_web.cfg' if not os.path.exists(network_options_file_path): self.mon.warn(self,"pp_web.cfg not found at "+network_options_file_path) return self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path) self.network.read_config(network_options_file_path) self.unit=self.network.unit # get interface and IP details of preferred interface self.interface,self.ip = self.network.get_preferred_ip() if self.interface == '': self.network_connected=False return self.network_details=True self.mon.log (self, 'Network details ' + self.unit + ' ' + self.interface + ' ' +self.ip) def init_mailer(self): self.email_enabled=False email_file_path = self.pp_dir+os.sep+'pp_config'+os.sep+'pp_email.cfg' if not os.path.exists(email_file_path): self.mon.log(self,'pp_email.cfg not found at ' + email_file_path) return self.mon.log(self,'Found pp_email.cfg at ' + email_file_path) self.mailer=Mailer() self.mailer.read_config(email_file_path) # all Ok so can enable email if config file allows it. if self.mailer.email_allowed is True: self.email_enabled=True self.mon.log (self,'Email Enabled') def send_email(self,reason,subject,message): if self.try_connect() is False: return False else: success,error = self.mailer.send(subject,message) if success is False: self.mon.log(self, 'Failed to send email: ' + str(error)) success,error=self.mailer.disconnect() if success is False: self.mon.log(self,'Failed disconnect after send:' + str(error)) return False else: self.mon.log(self,'Sent email for ' + reason) success,error=self.mailer.disconnect() if success is False: self.mon.log(self,'Failed disconnect from email server ' + str(error)) return True def try_connect(self): tries=1 while True: success, error = self.mailer.connect() if success is True: return True else: self.mon.log(self,'Failed to connect to email SMTP server ' + str(tries) + '\n ' +str(error)) tries +=1 if tries >5: self.mon.log(self,'Failed to connect to email SMTP server after ' + str(tries)) return False # tidy up all the peripheral bits of Pi Presents def tidy_up(self): self.handle_monitor_command('on') self.mon.log(self, "Tidying Up") # turn screen blanking back on if self.options['noblank'] is True: call(["xset","s", "on"]) call(["xset","s", "+dpms"]) # tidy up animation and gpio if self.animate is not None: self.animate.terminate() if self.gpio_enabled==True: self.gpiodriver.terminate() if self.osc_enabled is True: self.oscdriver.terminate() # tidy up time of day scheduler if self.tod_enabled is True: self.tod.terminate()
class TimeOfDay(object): # CLASS VARIABLES # change this for another language DAYS_OF_WEEK = [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday' ] """ TimeOfDay.events is a dictionary the keys being show-refs. Each dictionary entry is a list of time_elements sorted by descending time Each time element is a list with the fields: 0 - command 1 - time, seconds since midnight """ events = { } # list of times of day used to generate callbacks, earliest first # executed by main program and by each object using tod def __init__(self): self.mon = Monitor() # executed once from main program only def init(self, pp_dir, pp_home, pp_profile, showlist, root, callback): # instantiate arguments TimeOfDay.root = root self.pp_dir = pp_dir self.pp_home = pp_home self.pp_profile = pp_profile self.showlist = showlist self.callback = callback # init variables self.testing = False self.tod_tick = 500 self.tick_timer = None # set time of day for if no schedule TimeOfDay.now = datetime.now().replace(microsecond=0) # read and error check the schedule reason, message, schedule_enabled = self.read_schedule() if reason == 'error': return 'error', message, False if schedule_enabled is False: return 'normal', '', False #create the initial events list if self.simulate_time is True: year = int(self.sim_year) month = int(self.sim_month) day = int(self.sim_day) hour = int(self.sim_hour) minute = int(self.sim_minute) second = int(self.sim_second) TimeOfDay.now = datetime(day=day, month=month, year=year, hour=hour, minute=minute, second=second) self.testing = True print '\nInitial SIMULATED time', TimeOfDay.now.ctime() self.mon.sched( self, TimeOfDay.now, 'Testing is ON, Initial SIMULATED time ' + str(TimeOfDay.now.ctime())) else: #get the current date/time only this once TimeOfDay.now = datetime.now().replace(microsecond=0) self.mon.sched( self, TimeOfDay.now, 'Testing is OFF, Initial REAL time ' + str(TimeOfDay.now.ctime())) # print '\nInitial REAL time',TimeOfDay.now.ctime() self.testing = False # print 'init',TimeOfDay.now TimeOfDay.last_now = TimeOfDay.now - timedelta(seconds=1) reason, message = self.build_schedule_for_today() if reason == 'error': return 'error', message, False self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule()) if self.testing: self.print_todays_schedule() self.build_events_lists() if self.testing: self.print_events_lists() # and do exitpipresents or start any show that should be running at start up time self.do_catchup() return 'normal', '', True def do_catchup(self): TimeOfDay.scheduler_time = TimeOfDay.now.time() # do the catchup for each real show in turn # nothing required for start show, all previous events are just ignored. for show_ref in TimeOfDay.events: if show_ref != 'start' and self.enable_catchup[show_ref] == 'yes': if self.testing: print 'Catch Up', show_ref times = TimeOfDay.events[show_ref] # go through the event list for a show rembering show state until the first future event is found. # then if last command was to start the show send it show_running = False last_start_element = [] for time_element in reversed(times): # print 'now', now_seconds, 'time from events', time_element[1], time_element[0] # got past current time can give up catch up if time_element[1] >= TimeOfDay.scheduler_time: # print ' gone past time - break' break if time_element[0] == 'open': last_start_element = time_element # print 'open - show-running= true' show_running = True elif time_element[0] == 'close': # print 'close - show-running= false' show_running = False if show_running is True: #if self.testing: # print 'End of Catch Up Search', show_ref,last_start_element self.mon.sched( self, TimeOfDay.now, 'Catch up for show: ' + show_ref + ' requires ' + last_start_element[0] + ' ' + str(last_start_element[1])) self.do_event(show_ref, last_start_element) return 'not exiting' # called by main program only def poll(self): if self.testing: poll_time = TimeOfDay.now else: poll_time = datetime.now() # print 'poll time: ',poll_time.time(),'scheduler time: ',TimeOfDay.now.time() # if poll_time != TimeOfDay.now : print 'times different ',poll_time.time(),TimeOfDay.now.time() # is current time greater than last time the scheduler was run # run in a loop to catch up because root.after can get behind when images are being rendered etc. # poll time can be the same twice as poll is run at half second intervals. catchup_time = 0 while TimeOfDay.now <= poll_time: if TimeOfDay.now - TimeOfDay.last_now != timedelta(seconds=1): print 'POLL TIME FAILED', TimeOfDay.last_now, TimeOfDay.now #if catchup_time != 0: # print 'scheduler behind by: ',catchup_time, TimeOfDay.now.time(),poll_time.time() self.do_scheduler() TimeOfDay.last_now = TimeOfDay.now catchup_time += 1 # print 'poll',TimeOfDay.now, timedelta(seconds=1) TimeOfDay.now = TimeOfDay.now + timedelta(seconds=1) # and loop if self.testing: self.tick_timer = TimeOfDay.root.after(1000, self.poll) else: self.tick_timer = TimeOfDay.root.after(self.tod_tick, self.poll) # called by main program only def terminate(self): if self.tick_timer is not None: TimeOfDay.root.after_cancel(self.tick_timer) self.clear_events_lists() # execute events at the appropriate time. # called by main program only def do_scheduler(self): # if its midnight then build the events lists for the new day TimeOfDay.scheduler_time = TimeOfDay.now.time() if TimeOfDay.scheduler_time == time(hour=0, minute=0, second=0): # if self.testing: # print 'Its midnight, today is now', TimeOfDay.now.ctime() self.mon.sched( self, TimeOfDay.now, 'Its midnight, today is now ' + str(TimeOfDay.now.ctime())) reason, message = self.build_schedule_for_today() if reason == 'error': self.mon.err(self, 'system error- illegal time at midnight') return self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule()) # if self.testing: # self.print_todays_schedule() self.build_events_lists() # self.mon.sched(self,TimeOfDay.now,self.pretty_events_lists()) # self.print_events_lists() # print TimeOfDay.scheduler_time for show_ref in TimeOfDay.events: # print 'scheduler time match', show_ref times = TimeOfDay.events[show_ref] # now send a command if time matches for time_element in reversed(times): # print time_element[1],TimeOfDay.scheduler_time if time_element[1] == TimeOfDay.scheduler_time: self.do_event(show_ref, time_element) # execute an event def do_event(self, show_ref, time_element): self.mon.log( self, 'Event : ' + time_element[0] + ' ' + show_ref + ' required at: ' + time_element[1].isoformat()) self.mon.sched( self, TimeOfDay.now, ' ToD Scheduler : ' + time_element[0] + ' ' + show_ref + ' required at: ' + time_element[1].isoformat()) if self.testing: print 'Event : ' + time_element[ 0] + ' ' + show_ref + ' required at: ' + time_element[ 1].isoformat() if show_ref != 'start': self.callback(time_element[0] + ' ' + show_ref) else: self.callback(time_element[0]) # # ************************************************ # The methods below can be called from many classes so need to operate on class variables # ************************************************ # clear events list def clear_events_lists(self): self.mon.log(self, 'clear time of day events list ') # empty event list TimeOfDay.events = {} # *********************************** # Preparing schedule and todays event list # ************************************ def read_schedule(self): # get schedule from showlist index = self.showlist.index_of_start_show() self.showlist.select(index) starter_show = self.showlist.selected_show() sched_enabled = starter_show['sched-enable'] if sched_enabled != 'yes': return 'normal', '', False if starter_show['simulate-time'] == 'yes': self.simulate_time = True self.sim_second = starter_show['sim-second'] if not self.sim_second.isdigit(): return 'error', 'Simulate time - second is not a positive integer ' + self.sim_second, False if int(self.sim_second) > 59: return 'error', 'Simulate time - second is out of range ' + self.sim_second, False self.sim_minute = starter_show['sim-minute'] if not self.sim_minute.isdigit(): return 'error', 'Simulate time - minute is not a positive integer ' + self.sim_minute, False if int(self.sim_minute) > 59: return 'error', 'Simulate time - minute is out of range ' + self.sim_minute, False self.sim_hour = starter_show['sim-hour'] if not self.sim_hour.isdigit(): return 'error', 'Simulate time - hour is not a positive integer ' + self.sim_hour, False if int(self.sim_hour) > 23: return 'error', 'Simulate time - hour is out of range ' + self.sim_hour, False self.sim_day = starter_show['sim-day'] if not self.sim_day.isdigit(): return 'error', 'Simulate time - day is not a positive integer ' + self.sim_day, False if int(self.sim_day) > 31: return 'error', 'Simulate time - day is out of range ' + self.sim_day, False self.sim_month = starter_show['sim-month'] if not self.sim_month.isdigit(): return 'error', 'Simulate time - month is not a positive integer ' + self.sim_month, False if int(self.sim_month) > 12: return 'error', 'Simulate time - month is out of range ' + self.sim_month, False self.sim_year = starter_show['sim-year'] if not self.sim_year.isdigit(): return 'error', 'Simulate time - year is not a positive integer ' + self.sim_year, False if int(self.sim_year) < 2019: return 'error', 'Simulate time - year is out of range ' + self.sim_year, False else: self.simulate_time = False return 'normal', '', True def build_schedule_for_today(self): # print this_day.year, this_day.month, this_day.day, TimeOfDay.DAYS_OF_WEEK[ this_day.weekday()] """ self.todays_schedule is a dictionary the keys being show-refs. Each dictionary entry is a list of time_elements Each time element is a list with the fields: 0 - command 1 - time hour:min[:sec] """ self.enable_catchup = {} self.todays_schedule = {} for index in range(self.showlist.length()): show = self.showlist.show(index) show_type = show['type'] show_ref = show['show-ref'] if show['type'] != 'start': self.enable_catchup[show_ref] = show['enable-catchup'] # print 'looping build ',show_type,show_ref,self.showlist.length() if 'sched-everyday' in show: text = show['sched-everyday'] lines = text.splitlines() while len(lines) != 0: status, message, day_lines, lines = self.get_one_day( lines, show_ref) if status == 'error': return 'error', message status, message, days_list, times_list = self.parse_day( day_lines, 'everyday', show_ref, show_type) if status == 'error': return 'error', message #print 'everyday ',status,message,days_list,times_list self.todays_schedule[show['show-ref']] = copy.deepcopy( times_list) # print '\nafter everyday' # self.print_todays_schedule() if 'sched-weekday' in show: text = show['sched-weekday'] lines = text.splitlines() while len(lines) != 0: status, message, day_lines, lines = self.get_one_day( lines, show_ref) if status == 'error': return 'error', message status, message, days_list, times_list = self.parse_day( day_lines, 'weekday', show_ref, show_type) if status == 'error': return 'error', message #print 'weekday ',status,message,days_list,times_list # is current day of the week in list of days in schedule if TimeOfDay.DAYS_OF_WEEK[ TimeOfDay.now.weekday()] in days_list: self.todays_schedule[show['show-ref']] = copy.deepcopy( times_list) #print '\nafter weekday' #self.print_todays_schedule() if 'sched-monthday' in show: text = show['sched-monthday'] lines = text.splitlines() while len(lines) != 0: status, message, day_lines, lines = self.get_one_day( lines, show_ref) # print 'in monthday',day_lines if status == 'error': return 'error', message status, message, days_list, times_list = self.parse_day( day_lines, 'monthday', show_ref, show_type) if status == 'error': return 'error', message #print 'monthday ',status,message,days_list,times_list if TimeOfDay.now.day in map(int, days_list): self.todays_schedule[show['show-ref']] = copy.deepcopy( times_list) #print '\nafter monthday' #self.print_todays_schedule() if 'sched-specialday' in show: text = show['sched-specialday'] lines = text.splitlines() while len(lines) != 0: status, message, day_lines, lines = self.get_one_day( lines, show_ref) if status == 'error': return 'error', message status, message, days_list, times_list = self.parse_day( day_lines, 'specialday', show_ref, show_type) if status == 'error': return 'error', message # print 'specialday ',status,message,days_list,times_list for day in days_list: sdate = datetime.strptime(day, '%Y-%m-%d') if sdate.year == TimeOfDay.now.year and sdate.month == TimeOfDay.now.month and sdate.day == TimeOfDay.now.day: self.todays_schedule[ show['show-ref']] = copy.deepcopy(times_list) #print '\nafter specialday' #self.print_todays_schedule() # print self.enable_catchup return 'normal', '' def get_one_day(self, lines, show_ref): this_day = [] left_over = [] #print 'get one day',lines # check first line is day and move tt output #print lines[0] if not lines[0].startswith('day'): return 'error', 'first line of section is not day ' + lines[ 0] + ' ' + show_ref, [], [] this_day = [lines[0]] #print ' this day',this_day left_over = lines[1:] # print 'left over',left_over x_left_over = lines[1:] for line in x_left_over: #print 'in loop',line if line.startswith('day'): # print 'one day day',this_day,left_over return 'normal', '', this_day, left_over this_day.append(line) left_over = left_over[1:] # print 'one day end',this_day,left_over return 'normal', '', this_day, left_over def parse_day(self, lines, section, show_ref, show_type): # text # day monday # open 1:42 # close 1:45 # returns status,message,list of days,list of time lines if section == 'everyday': status, message, days_list = self.parse_everyday( lines[0], show_ref) elif section == 'weekday': status, message, days_list = self.parse_weekday(lines[0], show_ref) elif section == 'monthday': # print 'parse_day',lines status, message, days_list = self.parse_monthday( lines[0], show_ref) elif section == 'specialday': status, message, days_list = self.parse_specialday( lines[0], show_ref) else: return 'error','illegal section name '+section + ' '+ show_ref,[],[] if status == 'error': return 'error', message, [], [] if len(lines) > 1: time_lines = lines[1:] status, message, times_list = self.parse_time_lines( time_lines, show_ref, show_type) if status == 'error': return 'error', message, [], [] else: times_list = [] return 'normal', '', days_list, times_list def parse_everyday(self, line, show_ref): words = line.split() if words[0] != 'day': return 'error','day line does not contain day '+ line + ' ' + show_ref,[] if words[1] != 'everyday': return 'error','everday line does not contain everyday '+ show_ref,[] return 'normal', '', ['everyday'] def parse_weekday(self, line, show_ref): words = line.split() if words[0] != 'day': return 'error','day line does not contain day ' + line + ' ' + show_ref,[] days = words[1:] for day in days: if day not in TimeOfDay.DAYS_OF_WEEK: return 'error','weekday line has illegal day '+ day + ' '+ show_ref,[] return 'normal', '', days def parse_monthday(self, line, show_ref): words = line.split() if words[0] != 'day': return 'error', 'day line does not contain day ' + show_ref, [] days = words[1:] for day in days: if not day.isdigit(): return 'error','monthday line has illegal day '+ day + ' '+ show_ref,[] if int(day) < 1 or int(day) > 31: return 'error','monthday line has out of range day '+ line+ ' '+ show_ref,[] return 'normal', '', days def parse_specialday(self, line, show_ref): words = line.split() if words[0] != 'day': return 'error', 'day line does not contain day ' + show_ref, [] days = words[1:] for day in days: status, message = self.parse_date(day, show_ref) if status == 'error': return 'error', message, '' return 'normal', '', days def parse_time_lines(self, lines, show_ref, show_type): # lines - list of lines each with text 'command time' # returns list of lists each being [command, time] time_lines = [] for line in lines: # split line into time,command words = line.split() if len(words) < 2: return 'error','time line has wrong length '+ line+ ' '+ show_ref,[] status, message, time_item = self.parse_time(words[0], show_ref) if status == 'error': return 'error', message, [] if show_type == 'start': command = ' '.join(words[1:]) time_lines.append([command, time_item]) else: if words[1] not in ('open', 'close'): return 'error','illegal command in '+ line+ ' '+ show_ref,[] time_lines.append([words[1], time_item]) return 'normal', '', time_lines def build_events_lists(self): # builds events dictionary from todays_schedule by # converting times in todays schedule from hour:min:sec to datetime # and sorts them earliest last TimeOfDay.events = {} for show_ref in self.todays_schedule: # print show_ref times = self.todays_schedule[show_ref] for time_element in times: time_element[1] = self.parse_event_time(time_element[1]) sorted_times = sorted(times, key=lambda time_element: time_element[1], reverse=True) TimeOfDay.events[show_ref] = sorted_times # print times def parse_event_time(self, time_text): fields = time_text.split(':') if len(fields) > 2: secs = int(fields[2]) else: secs = 0 hours = int(fields[0]) mins = int(fields[1]) return time(hour=hours, minute=mins, second=secs) def parse_time(self, item, show_ref): fields = item.split(':') if len(fields) == 0: return 'error', 'Time field is empty ' + item + ' ' + show_ref, item if len(fields) > 3: return 'error', 'Too many fields in ' + item + ' ' + show_ref, item if len(fields) == 1: seconds = fields[0] minutes = '0' hours = '0' if len(fields) == 2: seconds = fields[1] minutes = fields[0] hours = '0' if len(fields) == 3: seconds = fields[2] minutes = fields[1] hours = fields[0] if not seconds.isdigit() or not minutes.isdigit() or not hours.isdigit( ): return 'error', 'Fields of ' + item + ' are not positive integers ' + show_ref, item if int(minutes) > 59: return 'error', 'Minutes of ' + item + ' is out of range ' + show_ref, item if int(seconds) > 59: return 'error', 'Seconds of ' + item + ' is out of range ' + show_ref, item if int(hours) > 23: return 'error', 'Hours of ' + item + ' is out of range ' + show_ref, item return 'normal', '', item def parse_date(self, item, show_ref): fields = item.split('-') if len(fields) == 0: return 'error', 'Date field is empty ' + item + ' ' + show_ref if len(fields) != 3: return 'error', 'Too many or few fields in date ' + item + ' ' + show_ref year = fields[0] month = fields[1] day = fields[2] if not year.isdigit() or not month.isdigit() or not day.isdigit(): return 'error', 'Fields of ' + item + ' are not positive integers ' + show_ref if int(year) < 2018: return 'error', 'Year of ' + item + ' is out of range ' + show_ref + year if int(month) > 12: return 'error', 'Month of ' + item + ' is out of range ' + show_ref if int(day) > 31: return 'error', 'Day of ' + item + ' is out of range ' + show_ref return 'normal', '' # ********************* # print for debug # ********************* def pretty_todays_schedule(self): op = 'Schedule For ' + TimeOfDay.now.ctime() + '\n' for key in self.todays_schedule: op += ' ' + key + '\n' for show in self.todays_schedule[key]: op += ' ' + show[0] + ': ' + str(show[1]) + '\n' op += '\n' return op def pretty_events_lists(self): op = ' Task list for today' for key in self.events: op += '\n' + key for show in self.events[key]: op += '\n ' + show[0] + ' ' + str(show[1].isoformat()) return op def print_todays_schedule(self): print '\nSchedule For ' + TimeOfDay.now.ctime() for key in self.todays_schedule: print ' ' + key for show in self.todays_schedule[key]: print ' ' + show[0] + ': ' + show[1] print def print_events_lists(self): print '\nTask list for today' for key in self.events: print '\n', key for show in self.events[key]: print show[0], show[1].isoformat() print
class ShowManager(object): """ ShowManager manages PiPresents' concurrent shows. It does not manage sub-shows or child-shows but has a bit of common code to initilise them concurrent shows are always top level (level 0) shows: They can be opened/closed by the start show(open only) or by 'open/close myshow' in the Show Control field of players, by time of day sceduler or by OSC Two shows with the same show reference cannot be run concurrently as there is no way to reference an individual instance. However a workaround is to make the secong instance a subshow of a mediashow with a different reference. """ # Declare class variables shows = [] canvas = None #canvas for all shows shutdown_required = False SHOW_TEMPLATE = ['', None] SHOW_REF = 0 # show-reference - name of the show as in editor SHOW_OBJ = 1 # the python object showlist = [] # Initialise class variables, first time through only in pipresents.py def init(self, canvas, all_shows_ended_callback, command_callback, showlist): ShowManager.all_shows_ended_callback = all_shows_ended_callback ShowManager.shows = [] ShowManager.shutdown_required = False ShowManager.canvas = canvas ShowManager.command_callback = command_callback ShowManager.showlist = showlist # ************************************** # functions to manipulate show register # ************************************** def register_shows(self): for show in ShowManager.showlist.shows(): if show['show-ref'] != 'start': reason, message = self.register_show(show['show-ref']) if reason == 'error': return reason, message return 'normal', 'shows regiistered' def register_show(self, ref): registered = self.show_registered(ref) if registered == -1: ShowManager.shows.append(copy.deepcopy(ShowManager.SHOW_TEMPLATE)) index = len(ShowManager.shows) - 1 ShowManager.shows[index][ShowManager.SHOW_REF] = ref ShowManager.shows[index][ShowManager.SHOW_OBJ] = None self.mon.trace( self, ' - register show: show_ref = ' + ref + ' index = ' + str(index)) return 'normal', 'show registered' else: # self.mon.err(self, ' more than one show in showlist with show-ref: ' + ref ) return 'error', ' more than one show in showlist with show-ref: ' + ref # is the show registered? # can be used to return the index to the show def show_registered(self, show_ref): index = 0 for show in ShowManager.shows: if show[ShowManager.SHOW_REF] == show_ref: return index index += 1 return -1 # needs calling program to check that the show is not already running def set_running(self, index, show_obj): ShowManager.shows[index][ShowManager.SHOW_OBJ] = show_obj self.mon.trace( self, 'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] + ' show_id= ' + str(index)) # is the show running? def show_running(self, index): if ShowManager.shows[index][ShowManager.SHOW_OBJ] is not None: return ShowManager.shows[index][ShowManager.SHOW_OBJ] else: return None def set_exited(self, index): ShowManager.shows[index][ShowManager.SHOW_OBJ] = None self.mon.trace( self, 'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] + ' show_id= ' + str(index)) # are all shows exited? def all_shows_exited(self): all_exited = True for show in ShowManager.shows: if show[ShowManager.SHOW_OBJ] is not None: all_exited = False return all_exited # fromat for printing def pretty_shows(self): shows = '\n' for show in ShowManager.shows: shows += show[ShowManager.SHOW_REF] + '\n' return shows # ********************************* # show control # ********************************* # show manager can be initialised by a player, shower or by pipresents.py # if by pipresents.py then show_id=-1 def __init__(self, show_id, showlist, show_params, root, canvas, pp_dir, pp_profile, pp_home): self.show_id = show_id self.showlist = showlist self.show_params = show_params self.root = root self.show_canvas = canvas self.pp_dir = pp_dir self.pp_profile = pp_profile self.pp_home = pp_home self.mon = Monitor() def control_a_show(self, show_ref, show_command): if show_command == 'open': return self.start_show(show_ref) elif show_command == 'close': return self.exit_show(show_ref) elif show_command == 'closeall': return self.exit_all_shows() elif show_command == 'openexclusive': return self.open_exclusive(show_ref) else: return 'error', 'command not recognised ' + show_command def open_exclusive(self, show_ref): self.exit_all_shows() self.exclusive_show = show_ref self.wait_for_openexclusive() return 'normal', 'opened exclusive' def wait_for_openexclusive(self): if self.all_shows_exited() is False: ShowManager.canvas.after(1, self.wait_for_openexclusive) return self.start_show(self.exclusive_show) def exit_all_shows(self): for show in ShowManager.shows: self.exit_show(show[ShowManager.SHOW_REF]) return 'normal', 'exited all shows' # kick off the exit sequence of a show by calling the shows exit method. # it will result in all the shows in a stack being closed and end_play_show being called def exit_show(self, show_ref): index = self.show_registered(show_ref) self.mon.log( self, "De-registering show " + show_ref + ' show index:' + str(index)) show_obj = self.show_running(index) if show_obj is not None: self.mon.log( self, "Exiting show " + show_ref + ' show index:' + str(index)) show_obj.exit() return 'normal', 'exited a concurrent show' def start_show(self, show_ref): index = self.show_registered(show_ref) if index < 0: return 'error', "Show not found in showlist: " + show_ref show_index = self.showlist.index_of_show(show_ref) show = self.showlist.show(show_index) reason, message, show_canvas = self.compute_show_canvas(show) if reason == 'error': return reason, message # print 'STARTING TOP LEVEL SHOW',show_canvas self.mon.sched( self, TimeOfDay.now, 'Starting Show: ' + show_ref + ' from show: ' + self.show_params['show-ref']) self.mon.log( self, 'Starting Show: ' + show_ref + ' from: ' + self.show_params['show-ref']) if self.show_running(index): self.mon.sched( self, TimeOfDay.now, "show already running so ignoring command: " + show_ref) self.mon.warn( self, "show already running so ignoring command: " + show_ref) return 'normal', 'this concurrent show already running' show_obj = self.init_show(index, show, show_canvas) if show_obj is None: return 'error', "unknown show type in start concurrent show - " + show[ 'type'] else: self.set_running(index, show_obj) # params - end_callback, show_ready_callback, parent_kickback_signal, level show_obj.play(self._end_play_show, None, False, 0, []) return 'normal', 'concurrent show started' # used by shows to create subshows or child shows def init_subshow(self, show_id, show, show_canvas): return self.init_show(show_id, show, show_canvas) def _end_play_show(self, index, reason, message): show_ref_to_exit = ShowManager.shows[index][ShowManager.SHOW_REF] show_to_exit = ShowManager.shows[index][ShowManager.SHOW_OBJ] self.mon.sched(self, TimeOfDay.now, 'Closed show: ' + show_ref_to_exit) self.mon.log( self, 'Exited from show: ' + show_ref_to_exit + ' ' + str(index)) self.mon.log(self, 'Exited with Reason = ' + reason) self.mon.trace( self, ' Show is: ' + show_ref_to_exit + ' show index ' + str(index)) # closes the video/audio from last track then closes the track # print 'show to exit ',show_to_exit, show_to_exit.current_player,show_to_exit.previous_player self.set_exited(index) if self.all_shows_exited() is True: ShowManager.all_shows_ended_callback(reason, message) return reason, message # common function to initilaise the show by type def init_show( self, show_id, selected_show, show_canvas, ): if selected_show['type'] == "mediashow": return MediaShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "liveshow": return LiveShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "radiobuttonshow": return RadioButtonShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "hyperlinkshow": return HyperlinkShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "menu": return MenuShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "artmediashow": return ArtMediaShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "artliveshow": return ArtLiveShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) else: return None def compute_show_canvas(self, show_params): canvas = {} canvas['canvas-obj'] = ShowManager.canvas status, message, self.show_canvas_x1, self.show_canvas_y1, self.show_canvas_x2, self.show_canvas_y2 = self.parse_show_canvas( show_params['show-canvas']) if status == 'error': # self.mon.err(self,'show canvas error: ' + message + ' in ' + show_params['show-canvas']) return 'error', 'show canvas error: ' + message + ' in ' + show_params[ 'show-canvas'], canvas else: self.show_canvas_width = self.show_canvas_x2 - self.show_canvas_x1 self.show_canvas_height = self.show_canvas_y2 - self.show_canvas_y1 self.show_canvas_centre_x = self.show_canvas_width / 2 self.show_canvas_centre_y = self.show_canvas_height / 2 canvas['show-canvas-x1'] = self.show_canvas_x1 canvas['show-canvas-y1'] = self.show_canvas_y1 canvas['show-canvas-x2'] = self.show_canvas_x2 canvas['show-canvas-y2'] = self.show_canvas_y2 canvas['show-canvas-width'] = self.show_canvas_width canvas['show-canvas-height'] = self.show_canvas_height canvas['show-canvas-centre-x'] = self.show_canvas_centre_x canvas['show-canvas-centre-y'] = self.show_canvas_centre_y return 'normal', '', canvas def parse_show_canvas(self, text): fields = text.split() # blank so show canvas is the whole screen if len(fields) < 1: return 'normal', '', 0, 0, int(self.canvas['width']), int( self.canvas['height']) elif len(fields) in (1, 4): # window is specified status, message, x1, y1, x2, y2 = parse_rectangle(text) if status == 'error': return 'error', message, 0, 0, 0, 0 else: return 'normal', '', x1, y1, x2, y2 else: # error return 'error', 'Wrong number of fields in Show canvas: ' + text, 0, 0, 0, 0
class OSCDriver(object): # executed by main program def init(self, pp_profile, manager_unit, preferred_interface, my_ip, show_command_callback, input_event_callback, animate_callback): self.pp_profile = pp_profile self.show_command_callback = show_command_callback self.input_event_callback = input_event_callback self.animate_callback = animate_callback self.mon = Monitor() config_file = self.pp_profile + os.sep + 'pp_io_config' + os.sep + 'osc.cfg' if not os.path.exists(config_file): self.mon.err(self, 'OSC Configuration file not found: ' + config_file) return 'error', 'OSC Configuration file nof found: ' + config_file self.mon.log(self, 'OSC Configuration file found at: ' + config_file) self.osc_config = OSCConfig() # reads config data if self.osc_config.read(config_file) == False: return 'error', 'failed to read osc.cfg' # unpack config data and initialise if self.osc_config.this_unit_name == '': return 'error', 'OSC Config - This Unit has no name' if len(self.osc_config.this_unit_name.split()) > 1: return 'error', 'OSC config - This Unit Name not a single word: ' + self.osc_config.this_unit_name self.this_unit_name = self.osc_config.this_unit_name if self.osc_config.this_unit_ip == '': self.this_unit_ip = my_ip else: self.this_unit_ip = self.osc_config.this_unit_ip if self.osc_config.slave_enabled == 'yes': if not self.osc_config.listen_port.isdigit(): return 'error', 'OSC Config - Listen port is not a positve number: ' + self.osc_config.listen_port self.listen_port = self.osc_config.listen_port if self.osc_config.master_enabled == 'yes': if not self.osc_config.reply_listen_port.isdigit(): return 'error', 'OSC Config - Reply Listen port is not a positve number: ' + self.osc_config.reply_listen_port self.reply_listen_port = self.osc_config.reply_listen_port # prepare the list of slaves status, message = self.parse_slaves() if status == 'error': return status, message self.prefix = '/pipresents' self.this_unit = '/' + self.this_unit_name self.input_server = None self.input_reply_client = None self.input_st = None self.output_client = None self.output_reply_server = None self.output_reply_st = None if self.osc_config.slave_enabled == 'yes' and self.osc_config.master_enabled == 'yes' and self.listen_port == self.reply_listen_port: # The two listen ports are the same so use one server for input and output #start the client that sends commands to the slaves self.output_client = OSC.OSCClient() self.mon.log( self, 'sending commands to slaves and replies to master on: ' + self.reply_listen_port) #start the input+output reply server self.mon.log( self, 'listen to commands and replies from slave units using: ' + self.this_unit_ip + ':' + self.reply_listen_port) self.output_reply_server = myOSCServer( (self.this_unit_ip, int(self.reply_listen_port)), self.output_client) self.add_default_handler(self.output_reply_server) self.add_input_handlers(self.output_reply_server) self.add_output_reply_handlers(self.output_reply_server) self.input_server = self.output_reply_server else: if self.osc_config.slave_enabled == 'yes': # we want this to be a slave to something else # start the client that sends replies to controlling unit self.input_reply_client = OSC.OSCClient() #start the input server self.mon.log( self, 'listening to commands on: ' + self.this_unit_ip + ':' + self.listen_port) self.input_server = myOSCServer( (self.this_unit_ip, int(self.listen_port)), self.input_reply_client) self.add_default_handler(self.input_server) self.add_input_handlers(self.input_server) # print self.pretty_list(self.input_server.getOSCAddressSpace(),'\n') if self.osc_config.master_enabled == 'yes': #we want to control other units #start the client that sends commands to the slaves self.output_client = OSC.OSCClient() self.mon.log( self, 'sending commands to slaves on port: ' + self.reply_listen_port) #start the output reply server self.mon.log( self, 'listen to replies from slave units using: ' + self.this_unit_ip + ':' + self.reply_listen_port) self.output_reply_server = myOSCServer( (self.this_unit_ip, int(self.reply_listen_port)), self.output_client) self.add_default_handler(self.output_reply_server) self.add_output_reply_handlers(self.output_reply_server) return 'normal', 'osc.cfg read' def terminate(self): if self.input_server != None: self.input_server.close() if self.output_reply_server != None: self.output_reply_server.close() self.mon.log(self, 'Waiting for Server threads to finish') if self.input_st != None: self.input_st.join() ##!!! if self.output_reply_st != None: self.output_reply_st.join() ##!!! self.mon.log(self, 'server threads closed') if self.input_reply_client != None: self.input_reply_client.close() if self.output_client != None: self.output_client.close() def start_server(self): # Start input Server self.mon.log(self, 'Starting input OSCServer') if self.input_server != None: self.input_st = threading.Thread( target=self.input_server.serve_forever) self.input_st.start() # Start output_reply server self.mon.log(self, 'Starting output reply OSCServer') if self.output_reply_server != None: self.output_reply_st = threading.Thread( target=self.output_reply_server.serve_forever) self.output_reply_st.start() def parse_slaves(self): name_list = self.osc_config.slave_units_name.split() ip_list = self.osc_config.slave_units_ip.split() if len(name_list) == 0: return 'error', 'OSC Config - List of slaves name is empty' if len(name_list) != len(ip_list): return 'error', 'OSC Config - Lengths of list of slaves name and slaves IP is different' self.slave_name_list = [] self.slave_ip_list = [] for i, name in enumerate(name_list): self.slave_name_list.append(name) self.slave_ip_list.append(ip_list[i]) return 'normal', 'slaves parsed' def parse_osc_command(self, fields): # send message to slave unit - INTERFACE WITH pipresents if len(fields) < 2: return 'error', 'too few fields in OSC command ' + ' '.join(fields) to_unit_name = fields[0] show_command = fields[1] # print 'FIELDS ',fields # send an arbitary osc message if show_command == 'send': if len(fields) > 2: osc_address = fields[2] arg_list = [] if len(fields) > 3: arg_list = fields[3:] else: return 'error', 'OSC - wrong nmber of fields in ' + ' '.join( fields) elif show_command in ('open', 'close', 'openexclusive'): if len(fields) == 3: osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command arg_list = [fields[2]] else: return 'error', 'OSC - wrong number of fields in ' + ' '.join( fields) elif show_command == 'monitor': if fields[2] in ('on', 'off'): osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command arg_list = [fields[2]] else: self.mon.err( self, 'OSC - illegal state in ' + show_command + ' ' + fields[2]) elif show_command == 'event': if len(fields) == 3: osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command arg_list = [fields[2]] elif show_command == 'animate': if len(fields) > 2: osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command arg_list = fields[2:] else: return 'error', 'OSC - wrong nmber of fields in ' + ' '.join( fields) elif show_command in ('closeall', 'exitpipresents', 'shutdownnow', 'reboot'): if len(fields) == 2: osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command arg_list = [] else: return 'error', 'OSC - wrong nmber of fields in ' + ' '.join( fields) elif show_command in ('loopback', 'server-info'): if len(fields) == 2: osc_address = self.prefix + '/' + to_unit_name + '/system/' + show_command arg_list = [] else: return 'error', 'OSC - wrong nmber of fields in ' + ' '.join( fields) else: return 'error', 'OSC - unkown command in ' + ' '.join(fields) ip = self.find_ip(to_unit_name, self.slave_name_list, self.slave_ip_list) if ip == '': return 'warn', 'OSC Unit Name not in the list of slaves: ' + to_unit_name self.sendto(ip, osc_address, arg_list) return 'normal', 'osc command sent' def find_ip(self, name, name_list, ip_list): i = 0 for j in name_list: if j == name: break i = i + 1 if i == len(name_list): return '' else: return ip_list[i] def sendto(self, ip, osc_address, arg_list): # print ip,osc_address,arg_list if self.output_client is None: self.mon.warn(self, 'Master not enabled, ignoring OSC command') return msg = OSC.OSCMessage() # print address msg.setAddress(osc_address) for arg in arg_list: # print arg msg.append(arg) try: self.output_client.sendto(msg, (ip, int(self.reply_listen_port))) self.mon.log( self, 'Sent OSC command: ' + osc_address + ' ' + ' '.join(arg_list) + ' to ' + ip + ':' + self.reply_listen_port) except Exception as e: self.mon.warn( self, 'error in client when sending OSC command: ' + str(e)) # ************************************** # Handlers for fallback # ************************************** def add_default_handler(self, server): server.addMsgHandler('default', self.no_match_handler) def no_match_handler(self, addr, tags, stuff, source): text = "No handler for message from %s" % OSC.getUrlStr(source) + '\n' text += " %s" % addr + self.pretty_list(stuff, '') self.mon.warn(self, text) return None # ************************************** # Handlers for Slave (input) # ************************************** def add_input_handlers(self, server): server.addMsgHandler( self.prefix + self.this_unit + "/system/server-info", self.server_info_handler) server.addMsgHandler(self.prefix + self.this_unit + "/system/loopback", self.loopback_handler) server.addMsgHandler(self.prefix + self.this_unit + '/core/open', self.open_show_handler) server.addMsgHandler(self.prefix + self.this_unit + '/core/close', self.close_show_handler) server.addMsgHandler( self.prefix + self.this_unit + '/core/openexclusive', self.openexclusive_handler) server.addMsgHandler(self.prefix + self.this_unit + '/core/closeall', self.closeall_handler) server.addMsgHandler( self.prefix + self.this_unit + '/core/exitpipresents', self.exitpipresents_handler) server.addMsgHandler( self.prefix + self.this_unit + '/core/shutdownnow', self.shutdownnow_handler) server.addMsgHandler(self.prefix + self.this_unit + '/core/reboot', self.reboot_handler) server.addMsgHandler(self.prefix + self.this_unit + '/core/event', self.input_event_handler) server.addMsgHandler(self.prefix + self.this_unit + '/core/animate', self.animate_handler) server.addMsgHandler(self.prefix + self.this_unit + '/core/monitor', self.monitor_handler) # reply to master unit with name of this unit and commands def server_info_handler(self, addr, tags, stuff, source): msg = OSC.OSCMessage(self.prefix + '/system/server-info-reply') msg.append(self.this_unit_name) msg.append(self.input_server.getOSCAddressSpace()) self.mon.log(self, 'Sent Server Info reply to %s:' % OSC.getUrlStr(source)) return msg # reply to master unit with a loopback message def loopback_handler(self, addr, tags, stuff, source): msg = OSC.OSCMessage(self.prefix + '/system/loopback-reply') self.mon.log(self, 'Sent loopback reply to %s:' % OSC.getUrlStr(source)) return msg def open_show_handler(self, address, tags, args, source): self.prepare_show_command_callback('open ', args, 1) def openexclusive_handler(self, address, tags, args, source): self.prepare_show_command_callback('openexclusive ', args, 1) def close_show_handler(self, address, tags, args, source): self.prepare_show_command_callback('close ', args, 1) def closeall_handler(self, address, tags, args, source): self.prepare_show_command_callback('closeall', args, 0) def monitor_handler(self, address, tags, args, source): self.prepare_show_command_callback('monitor ', args, 1) def exitpipresents_handler(self, address, tags, args, source): self.prepare_show_command_callback('exitpipresents', args, 0) def reboot_handler(self, address, tags, args, source): self.prepare_show_command_callback('reboot', args, 0) def shutdownnow_handler(self, address, tags, args, source): self.prepare_show_command_callback('shutdownnow', args, 0) def prepare_show_command_callback(self, command, args, limit): if len(args) == limit: if limit != 0: self.mon.sched(self, TimeOfDay.now, 'Received from OSC: ' + command + ' ' + args[0]) self.show_command_callback(command + args[0]) else: self.mon.sched(self, TimeOfDay.now, 'Received from OSC: ' + command) self.show_command_callback(command) else: self.mon.warn( self, 'OSC show command does not have ' + limit + ' argument - ignoring') def input_event_handler(self, address, tags, args, source): if len(args) == 1: self.input_event_callback(args[0], 'OSC') else: self.mon.warn( self, 'OSC input event does not have 1 argument - ignoring') def animate_handler(self, address, tags, args, source): if len(args) != 0: # delay symbol,param_type,param_values,req_time as a string text = '0 ' for arg in args: text = text + arg + ' ' text = text + '0' # print text self.animate_callback(text) else: self.mon.warn(self, 'OSC output event has no arguments - ignoring') # ************************************** # Handlers for Master- replies from slaves (output) # ************************************** # reply handlers do not have the destinatuion unit in the address as they are always sent to the originator def add_output_reply_handlers(self, server): server.addMsgHandler(self.prefix + "/system/server-info-reply", self.server_info_reply_handler) server.addMsgHandler(self.prefix + "/system/loopback-reply", self.loopback_reply_handler) # print result of info request from slave unit def server_info_reply_handler(self, addr, tags, stuff, source): self.mon.log( self, 'server info reply from slave ' + OSC.getUrlStr(source) + self.pretty_list(stuff, '\n')) print 'Received reply to Server-Info command from slave: ', OSC.getUrlStr( source), self.pretty_list(stuff, '\n') return None #print result of info request from slave unit def loopback_reply_handler(self, addr, tags, stuff, source): self.mon.log( self, 'server info reply from slave ' + OSC.getUrlStr(source) + self.pretty_list(stuff, '\n')) print 'Received reply to Loopback command from slave: ' + OSC.getUrlStr( source) + ' ' + self.pretty_list(stuff, '\n') return None def pretty_list(self, fields, separator): text = ' ' for field in fields: text += str(field) + separator return text + '\n'
class TimeOfDay(object): # CLASS VARIABLES # change this for another language DAYS_OF_WEEK = [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday' ] """ TimeOfDay.events is a dictionary the keys being show-refs. Each dictionary entry is a list of time_elements sorted by descending time Each time element is a list with the fields: 0 - command 1 - time, seconds since midnight """ events = { } # list of times of day used to generate callbacks, earliest first # executed by main program and by each object using tod def __init__(self): self.mon = Monitor() TimeOfDay.now = None # executed once from main program only def init(self, pp_dir, pp_home, pp_profile, root, callback): # instantiate arguments TimeOfDay.root = root self.pp_dir = pp_dir self.pp_home = pp_home self.pp_profile = pp_profile self.callback = callback # init variables self.testing = False self.tod_tick = 500 self.tick_timer = None TimeOfDay.now = datetime.now().replace(microsecond=0) # read the schedule self.schedule = self.open_schedule() #create the initial events list if 'simulate-time' in self.schedule and self.schedule[ 'simulate-time'] == 'yes': year = int(self.schedule['sim-year']) month = int(self.schedule['sim-month']) day = int(self.schedule['sim-day']) hour = int(self.schedule['sim-hour']) minute = int(self.schedule['sim-minute']) second = int(self.schedule['sim-second']) TimeOfDay.now = datetime(day=day, month=month, year=year, hour=hour, minute=minute, second=second) self.testing = True # print '\nInitial SIMULATED time',TimeOfDay.now.ctime() self.mon.sched( self, TimeOfDay.now, 'Testing is ON, Initial SIMULATED time ' + str(TimeOfDay.now.ctime())) else: #get the current date/time only this once TimeOfDay.now = datetime.now().replace(microsecond=0) self.mon.sched( self, TimeOfDay.now, 'Testing is OFF, Initial REAL time ' + str(TimeOfDay.now.ctime())) # print '\nInitial REAL time',TimeOfDay.now.ctime() self.testing = False TimeOfDay.last_now = TimeOfDay.now - timedelta(seconds=1) self.build_schedule_for_today(self.schedule) self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule()) # if self.testing: # self.print_todays_schedule() self.build_events_lists() # self.print_events_lists() # and do exitpipresents or start any show that should be running at start up time self.do_catchup() def do_catchup(self): TimeOfDay.scheduler_time = TimeOfDay.now.time() # shutdown or exit if current time is later than time in event list for show_ref in TimeOfDay.events: if show_ref == 'pp_core': times = TimeOfDay.events[show_ref] for time_element in reversed(times): # print 'now', TimeOfDay.scheduler_time, 'time from events', time_element[1], time_element[0] # got past current time can give up and execute exitpipresents or closedown if TimeOfDay.scheduler_time >= time_element[1]: self.do_event(show_ref, time_element) return 'exiting' # do the catchup for each real show in turn for show_ref in TimeOfDay.events: if show_ref != 'pp_core': # print '\n*****',show_ref times = TimeOfDay.events[show_ref] # go through the event list for a show rembering show state until the first future event is found. # then if last command was to start the show send it show_running = False last_start_element = [] for time_element in reversed(times): # print 'now', now_seconds, 'time from events', time_element[1], time_element[0] # got past current time can give up catch up if time_element[1] >= TimeOfDay.scheduler_time: # print ' gone past time - break' break if time_element[0] == 'open': last_start_element = time_element # print 'open - show-running= true' show_running = True elif time_element[0] == 'close': # print 'close - show-running= false' show_running = False if show_running is True: #if self.testing: # print 'End of Catch Up Search', show_ref,last_start_element self.mon.sched( self, TimeOfDay.now, 'Catch up for show: ' + show_ref + ' requires ' + last_start_element[0] + ' ' + str(last_start_element[1])) self.do_event(show_ref, last_start_element) ## # print 'catchup time match', show_ref ## # now do the inital real time command if time now matches event time ## for time_element in reversed(times): ## if time_element[1] == TimeOfDay.scheduler_time: ## print ' do event - catchup', TimeOfDay.scheduler_time, 'time from events', time_element[1], time_element[0] ## self.do_event(show_ref,time_element) return 'not exiting' # called by main program only def poll(self): if self.testing: poll_time = TimeOfDay.now else: poll_time = datetime.now() # print 'poll time: ',poll_time.time(),'scheduler time: ',TimeOfDay.now.time() # if poll_time != TimeOfDay.now : print 'times different ',poll_time.time(),TimeOfDay.now.time() # is current time greater than last time the scheduler was run # run in a loop to catch up because root.after can get behind when images are being rendered etc. # poll time can be the same twice as poll is run at half second intervals. catchup_time = 0 while TimeOfDay.now <= poll_time: if TimeOfDay.now - TimeOfDay.last_now != timedelta(seconds=1): print 'POLL TIME FAILED', TimeOfDay.last_now, TimeOfDay.now #if catchup_time != 0: # print 'scheduler behind by: ',catchup_time, TimeOfDay.now.time(),poll_time.time() self.do_scheduler() TimeOfDay.last_now = TimeOfDay.now catchup_time += 1 TimeOfDay.now = TimeOfDay.now + timedelta(seconds=1) # and loop if self.testing: self.tick_timer = TimeOfDay.root.after(1000, self.poll) else: self.tick_timer = TimeOfDay.root.after(self.tod_tick, self.poll) # called by main program only def terminate(self): if self.tick_timer is not None: TimeOfDay.root.after_cancel(self.tick_timer) self.clear_events_lists() # execute events at the appropriate time. # called by main program only def do_scheduler(self): # if its midnight then build the events lists for the new day TimeOfDay.scheduler_time = TimeOfDay.now.time() if TimeOfDay.scheduler_time == time(hour=0, minute=0, second=0): # if self.testing: # print 'Its midnight, today is now', TimeOfDay.now.ctime() self.mon.sched( self, TimeOfDay.now, 'Its midnight, today is now ' + str(TimeOfDay.now.ctime())) self.build_schedule_for_today(self.schedule) self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule()) # if self.testing: # self.print_todays_schedule() self.build_events_lists() # self.mon.sched(self,TimeOfDay.now,self.pretty_events_lists()) # self.print_events_lists() # print TimeOfDay.scheduler_time for show_ref in TimeOfDay.events: # print 'scheduler time match', show_ref times = TimeOfDay.events[show_ref] # now send a command if time matches for time_element in reversed(times): # print time_element[1],TimeOfDay.scheduler_time if time_element[1] == TimeOfDay.scheduler_time: self.do_event(show_ref, time_element) # execute an event def do_event(self, show_ref, time_element): self.mon.log( self, 'Event : ' + time_element[0] + ' ' + show_ref + ' required at: ' + time_element[1].isoformat()) self.mon.sched( self, TimeOfDay.now, ' ToD Scheduler : ' + time_element[0] + ' ' + show_ref + ' required at: ' + time_element[1].isoformat()) # if self.testing: # print 'Event : ' + time_element[0] + ' ' + show_ref + ' required at: '+ time_element[1].isoformat() self.callback(time_element[0] + ' ' + show_ref) # # ************************************************ # The methods below can be called from many classes so need to operate on class variables # ************************************************ # clear events list def clear_events_lists(self): self.mon.log(self, 'clear time of day events list ') # empty event list TimeOfDay.events = {} # *********************************** # Preparing schedule and todays event list # ************************************ def open_schedule(self): # look for the schedule.json file # try inside profile filename = self.pp_profile + os.sep + "schedule.json" ifile = open(filename, 'rb') schedule = json.load(ifile) ifile.close() self.mon.log(self, "schedule.json read from " + filename) return schedule def build_schedule_for_today(self, schedule): # print this_day.year, this_day.month, this_day.day, TimeOfDay.DAYS_OF_WEEK[ this_day.weekday()] """ self.todays_schedule is a dictionary the keys being show-refs. Each dictionary entry is a list of time_elements Each time element is a list with the fields: 0 - command 1 - time hour:min[:sec] """ self.todays_schedule = {} for show in schedule['shows']: show_ref = show['show-ref'] if 'everyday' in show: day = show['everyday'] # print day['day'] times = day['times'] for time in times: self.todays_schedule[show['show-ref']] = copy.deepcopy( day['times']) # print '\nafter everyday' # self.print_todays_schedule() if 'weekday' in show: day = show['weekday'] # print day['day'] if TimeOfDay.DAYS_OF_WEEK[ TimeOfDay.now.weekday()] in day['day']: # print 'weekday matched', TimeOfDay.DAYS_OF_WEEK[ TimeOfDay.now.weekday()] times = day['times'] for time in times: self.todays_schedule[show['show-ref']] = copy.deepcopy( day['times']) # print '\nafter weekday' # self.print_todays_schedule() if 'monthday' in show: day = show['monthday'] # print day['day'] if TimeOfDay.now.day in map(int, day['day']): # print 'monthday matched', day['day'] times = day['times'] for time in times: self.todays_schedule[show['show-ref']] = copy.deepcopy( day['times']) # print '\nafter monthday' # self.print_todays_schedule() if 'specialday' in show: days = show['specialday'] # print days['day'] for day in days['day']: sdate = datetime.strptime(day, '%Y-%m-%d') if sdate.year == TimeOfDay.now.year and sdate.month == TimeOfDay.now.month and sdate.day == TimeOfDay.now.day: # print 'special matched', day times = days['times'] # for time in times: self.todays_schedule[show['show-ref']] = copy.deepcopy( days['times']) # print '\nafter specialday' # self.print_todays_schedule() def build_events_lists(self): # builds events dictionary from todays_schedule by # converting times in todays schedule from hour:min:sec to datetime # and sorts them earliest last TimeOfDay.events = {} for show_ref in self.todays_schedule: # print show_ref times = self.todays_schedule[show_ref] for time_element in times: time_element[1] = self.parse_event_time(time_element[1]) sorted_times = sorted(times, key=lambda time_element: time_element[1], reverse=True) TimeOfDay.events[show_ref] = sorted_times # print times def parse_event_time(self, time_text): fields = time_text.split(':') if len(fields) > 2: secs = int(fields[2]) else: secs = 0 hours = int(fields[0]) mins = int(fields[1]) return time(hour=hours, minute=mins, second=secs) # ********************* # print for debug # ********************* def pretty_todays_schedule(self): op = 'Schedule For ' + TimeOfDay.now.ctime() + '\n' for key in self.todays_schedule: op += ' ' + key + '\n' for show in self.todays_schedule[key]: op += ' ' + show[0] + ': ' + str(show[1]) + '\n' op += '\n' return op def pretty_events_lists(self): op = ' Events list for today' for key in self.events: op += '\n' + key for show in self.events[key]: op += '\n ' + show[0] + ' ' + str(show[1].isoformat()) return op def print_todays_schedule(self): print '\nSchedule For ' + TimeOfDay.now.ctime() for key in self.todays_schedule: print ' ' + key for show in self.todays_schedule[key]: print ' ' + show[0] + ': ' + show[1] print def print_events_lists(self): print '\nevents list for today' for key in self.events: print '\n', key for show in self.events[key]: print show[0], show[1].isoformat() def save_schedule(self, filename): """ save a schedule """ if filename == "": return False if os.name == 'nt': filename = string.replace(filename, '/', '\\') else: filename = string.replace(filename, '\\', '/') ofile = open(filename, "wb") json.dump(self.schedule, ofile, sort_keys=False, indent=1) ofile.close() return
class OSCDriver(object): # executed by main program def init(self,pp_profile,manager_unit,preferred_interface,my_ip,show_command_callback,input_event_callback,animate_callback): self.pp_profile=pp_profile self.show_command_callback=show_command_callback self.input_event_callback=input_event_callback self.animate_callback=animate_callback self.mon=Monitor() config_file=self.pp_profile + os.sep +'pp_io_config'+os.sep+ 'osc.cfg' if not os.path.exists(config_file): self.mon.err(self, 'OSC Configuration file not found: '+config_file) return'error','OSC Configuration file nof found: '+config_file self.mon.log(self, 'OSC Configuration file found at: '+config_file) self.osc_config=OSCConfig() # reads config data if self.osc_config.read(config_file) ==False: return 'error','failed to read osc.cfg' # unpack config data and initialise if self.osc_config.this_unit_name =='': return 'error','OSC Config - This Unit has no name' if len(self.osc_config.this_unit_name.split())>1: return 'error','OSC config - This Unit Name not a single word: '+self.osc_config.this_unit_name self.this_unit_name=self.osc_config.this_unit_name if self.osc_config.this_unit_ip=='': self.this_unit_ip=my_ip else: self.this_unit_ip=self.osc_config.this_unit_ip if self.osc_config.slave_enabled == 'yes': if not self.osc_config.listen_port.isdigit(): return 'error','OSC Config - Listen port is not a positve number: '+ self.osc_config.listen_port self.listen_port= self.osc_config.listen_port if self.osc_config.master_enabled == 'yes': if not self.osc_config.reply_listen_port.isdigit(): return 'error','OSC Config - Reply Listen port is not a positve number: '+ self.osc_config.reply_listen_port self.reply_listen_port= self.osc_config.reply_listen_port # prepare the list of slaves status,message=self.parse_slaves() if status=='error': return status,message self.prefix='/pipresents' self.this_unit='/' + self.this_unit_name self.input_server=None self.input_reply_client=None self.input_st=None self.output_client=None self.output_reply_server=None self.output_reply_st=None if self.osc_config.slave_enabled == 'yes' and self.osc_config.master_enabled == 'yes' and self.listen_port == self.reply_listen_port: # The two listen ports are the same so use one server for input and output #start the client that sends commands to the slaves self.output_client=OSC.OSCClient() self.mon.log(self, 'sending commands to slaves and replies to master on: '+self.reply_listen_port) #start the input+output reply server self.mon.log(self, 'listen to commands and replies from slave units using: ' + self.this_unit_ip+':'+self.reply_listen_port) self.output_reply_server=myOSCServer((self.this_unit_ip,int(self.reply_listen_port)),self.output_client) self.add_default_handler(self.output_reply_server) self.add_input_handlers(self.output_reply_server) self.add_output_reply_handlers(self.output_reply_server) self.input_server=self.output_reply_server else: if self.osc_config.slave_enabled == 'yes': # we want this to be a slave to something else # start the client that sends replies to controlling unit self.input_reply_client=OSC.OSCClient() #start the input server self.mon.log(self, 'listening to commands on: ' + self.this_unit_ip+':'+self.listen_port) self.input_server=myOSCServer((self.this_unit_ip,int(self.listen_port)),self.input_reply_client) self.add_default_handler(self.input_server) self.add_input_handlers(self.input_server) print self.pretty_list(self.input_server.getOSCAddressSpace(),'\n') if self.osc_config.master_enabled =='yes': #we want to control other units #start the client that sends commands to the slaves self.output_client=OSC.OSCClient() self.mon.log(self, 'sending commands to slaves on port: '+self.reply_listen_port) #start the output reply server self.mon.log(self, 'listen to replies from slave units using: ' + self.this_unit_ip+':'+self.reply_listen_port) self.output_reply_server=myOSCServer((self.this_unit_ip,int(self.reply_listen_port)),self.output_client) self.add_default_handler(self.output_reply_server) self.add_output_reply_handlers(self.output_reply_server) return 'normal','osc.cfg read' def terminate(self): if self.input_server != None: self.input_server.close() if self.output_reply_server != None: self.output_reply_server.close() self.mon.log(self, 'Waiting for Server threads to finish') if self.input_st != None: self.input_st.join() ##!!! if self.output_reply_st != None: self.output_reply_st.join() ##!!! self.mon.log(self,'server threads closed') if self.input_reply_client !=None: self.input_reply_client.close() if self.output_client !=None: self.output_client.close() def start_server(self): # Start input Server self.mon.log(self,'Starting input OSCServer') if self.input_server != None: self.input_st = threading.Thread( target = self.input_server.serve_forever ) self.input_st.start() # Start output_reply server self.mon.log(self,'Starting output reply OSCServer') if self.output_reply_server != None: self.output_reply_st = threading.Thread( target = self.output_reply_server.serve_forever ) self.output_reply_st.start() def parse_slaves(self): name_list=self.osc_config.slave_units_name.split() ip_list=self.osc_config.slave_units_ip.split() if len(name_list)==0: return 'error','OSC Config - List of slaves name is empty' if len(name_list) != len(ip_list): return 'error','OSC Config - Lengths of list of slaves name and slaves IP is different' self.slave_name_list=[] self.slave_ip_list=[] for i, name in enumerate(name_list): self.slave_name_list.append(name) self.slave_ip_list.append(ip_list[i]) return 'normal','slaves parsed' def parse_osc_command(self,fields): # send message to slave unit - INTERFACE WITH pipresents if len(fields) <2: return 'error','too few fields in OSC command '+' '.join(fields) to_unit_name=fields[0] show_command=fields[1] # print 'FIELDS ',fields # send an arbitary osc message if show_command == 'send': if len(fields)>2: osc_address= fields[2] arg_list=[] if len(fields)>3: arg_list=fields[3:] else: return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields) elif show_command in ('open','close','openexclusive'): if len(fields)==3: osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command arg_list= [fields[2]] else: return 'error','OSC - wrong number of fields in '+ ' '.join(fields) elif show_command =='monitor': if fields[2] in ('on','off'): osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command arg_list=[fields[2]] else: self.mon.err(self,'OSC - illegal state in '+ show_command + ' '+fields[2]) elif show_command =='event': if len(fields)==3: osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command arg_list= [fields[2]] elif show_command == 'animate': if len(fields)>2: osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command arg_list= fields[2:] else: return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields) elif show_command in ('closeall','exitpipresents','shutdownnow','reboot'): if len(fields)==2: osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command arg_list= [] else: return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields) elif show_command in ('loopback','server-info'): if len(fields)==2: osc_address=self.prefix+'/'+ to_unit_name + '/system/'+ show_command arg_list= [] else: return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields) else: return 'error','OSC - unkown command in '+ ' '.join(fields) ip=self.find_ip(to_unit_name,self.slave_name_list,self.slave_ip_list) if ip=='': return 'warn','OSC Unit Name not in the list of slaves: '+ to_unit_name self.sendto(ip,osc_address,arg_list) return 'normal','osc command sent' def find_ip(self,name,name_list,ip_list): i=0 for j in name_list: if j == name: break i=i+1 if i==len(name_list): return '' else: return ip_list[i] def sendto(self,ip,osc_address,arg_list): # print ip,osc_address,arg_list if self.output_client is None: self.mon.warn(self,'Master not enabled, ignoring OSC command') return msg = OSC.OSCMessage() # print address msg.setAddress(osc_address) for arg in arg_list: # print arg msg.append(arg) try: self.output_client.sendto(msg,(ip,int(self.reply_listen_port))) self.mon.log(self,'Sent OSC command: '+osc_address+' '+' '.join(arg_list) + ' to '+ ip +':'+self.reply_listen_port) except Exception as e: self.mon.warn(self,'error in client when sending OSC command: '+ str(e)) # ************************************** # Handlers for fallback # ************************************** def add_default_handler(self,server): server.addMsgHandler('default', self.no_match_handler) def no_match_handler(self,addr, tags, stuff, source): text= "No handler for message from %s" % OSC.getUrlStr(source)+'\n' text+= " %s" % addr+ self.pretty_list(stuff,'') self.mon.warn(self,text) return None # ************************************** # Handlers for Slave (input) # ************************************** def add_input_handlers(self,server): server.addMsgHandler(self.prefix + self.this_unit+"/system/server-info", self.server_info_handler) server.addMsgHandler(self.prefix + self.this_unit+"/system/loopback", self.loopback_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/open', self.open_show_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/close', self.close_show_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/openexclusive', self.openexclusive_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/closeall', self.closeall_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/exitpipresents', self.exitpipresents_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/shutdownnow', self.shutdownnow_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/reboot', self.reboot_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/event', self.input_event_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/animate', self.animate_handler) server.addMsgHandler(self.prefix+ self.this_unit+'/core/monitor', self.monitor_handler) # reply to master unit with name of this unit and commands def server_info_handler(self,addr, tags, stuff, source): msg = OSC.OSCMessage(self.prefix+'/system/server-info-reply') msg.append(self.this_unit_name) msg.append(self.input_server.getOSCAddressSpace()) self.mon.log(self,'Sent Server Info reply to %s:' % OSC.getUrlStr(source)) return msg # reply to master unit with a loopback message def loopback_handler(self,addr, tags, stuff, source): msg = OSC.OSCMessage(self.prefix+'/system/loopback-reply') self.mon.log(self,'Sent loopback reply to %s:' % OSC.getUrlStr(source)) return msg def open_show_handler(self,address, tags, args, source): self.prepare_show_command_callback('open ',args,1) def openexclusive_handler(self,address, tags, args, source): self.prepare_show_command_callback('openexclusive ',args,1) def close_show_handler(self,address, tags, args, source): self.prepare_show_command_callback('close ', args,1) def closeall_handler(self,address, tags, args, source): self.prepare_show_command_callback('closeall',args,0) def monitor_handler(self,address, tags, args, source): self.prepare_show_command_callback('monitor ', args,1) def exitpipresents_handler(self,address, tags, args, source): self.prepare_show_command_callback('exitpipresents',args,0) def reboot_handler(self,address, tags, args, source): self.prepare_show_command_callback('reboot',args,0) def shutdownnow_handler(self,address, tags, args, source): self.prepare_show_command_callback('shutdownnow',args,0) def prepare_show_command_callback(self,command,args,limit): if len(args) == limit: if limit !=0: self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command + ' ' +args[0]) self.show_command_callback(command+args[0]) else: self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command) self.show_command_callback(command) else: self.mon.warn(self,'OSC show command does not have '+limit +' argument - ignoring') def input_event_handler(self,address, tags, args, source): if len(args) == 1: self.input_event_callback(args[0],'OSC') else: self.mon.warn(self,'OSC input event does not have 1 argument - ignoring') def animate_handler(self,address, tags, args, source): if len(args) !=0: # delay symbol,param_type,param_values,req_time as a string text='0 ' for arg in args: text= text+ arg + ' ' text = text + '0' print text self.animate_callback(text) else: self.mon.warn(self,'OSC output event has no arguments - ignoring') # ************************************** # Handlers for Master- replies from slaves (output) # ************************************** # reply handlers do not have the destinatuion unit in the address as they are always sent to the originator def add_output_reply_handlers(self,server): server.addMsgHandler(self.prefix+"/system/server-info-reply", self.server_info_reply_handler) server.addMsgHandler(self.prefix+"/system/loopback-reply", self.loopback_reply_handler) # print result of info request from slave unit def server_info_reply_handler(self,addr, tags, stuff, source): self.mon.log(self,'server info reply from slave '+OSC.getUrlStr(source)+ self.pretty_list(stuff,'\n')) print 'Received reply to Server-Info command from slave: ',OSC.getUrlStr(source), self.pretty_list(stuff,'\n') return None #print result of info request from slave unit def loopback_reply_handler(self,addr, tags, stuff, source): self.mon.log(self,'server info reply from slave '+OSC.getUrlStr(source)+ self.pretty_list(stuff,'\n')) print 'Received reply to Loopback command from slave: ' + OSC.getUrlStr(source)+ ' '+ self.pretty_list(stuff,'\n') return None def pretty_list(self,fields, separator): text=' ' for field in fields: text += str(field) + separator return text+'\n'
class OSCDriver(object): # executed by main program def init(self,pp_profile,show_command_callback,input_event_callback,output_event_callback): self.pp_profile=pp_profile self.show_command_callback=show_command_callback self.input_event_callback=input_event_callback self.output_event_callback=output_event_callback self.mon=Monitor() config_file=self.pp_profile + os.sep +'pp_io_config'+os.sep+ 'osc.cfg' if not os.path.exists(config_file): self.mon.err(self, 'OSC Configuration file nof found: '+config_file) return'error','OSC Configuration file nof found: '+config_file self.mon.log(self, 'OSC Configuration file found at: '+config_file) self.options=OSCConfig() # only reads the data for required unit_type if self.options.read(config_file) ==False: return 'error','failed to read osc.cfg' self.prefix='/pipresents' self.this_unit='/' + self.options.this_unit_name self.this_unit_type = self.options.this_unit_type self.reply_client=None self.command_client=None self.client=None self.server=None if self.this_unit_type not in ('master','slave','master+slave'): return 'error','this unit type not known: '+self.this_unit_type if self.this_unit_type in('slave','master+slave'): #start the client that sends replies to controlling unit self.reply_client=OSC.OSCClient() self.mon.log(self, 'sending replies to controller at: '+self.options.controlled_by_ip+':'+self.options.controlled_by_port) self.reply_client.connect((self.options.controlled_by_ip,int(self.options.controlled_by_port))) self.mon.log(self,'sending repiles to: '+ str(self.reply_client)) self.client=self.reply_client if self.this_unit_type in ('master','master+slave'): #start the client that sends commands to the controlled unit self.command_client=OSC.OSCClient() self.command_client.connect((self.options.controlled_unit_1_ip,int(self.options.controlled_unit_1_port))) self.mon.log(self, 'sending commands to controled unit at: '+self.options.controlled_unit_1_ip+':'+self.options.controlled_unit_1_port) self.mon.log(self,'sending commands to: '+str(self.command_client)) self.client=self.command_client #start the listener's server self.mon.log(self, 'listen to commands from controlled by unit and replies from controlled units on: ' + self.options.this_unit_ip+':'+self.options.this_unit_port) self.server=myOSCServer((self.options.this_unit_ip,int(self.options.this_unit_port)),self.client) # return_port=int(self.options.controlled_by_port) self.mon.log(self,'listening on: '+str(self.server)) self.add_initial_handlers() return 'normal','osc.cfg read' def terminate(self): if self.server != None: self.server.close() self.mon.log(self, 'Waiting for Server-thread to finish') if self.st != None: self.st.join() ##!!! self.mon.log(self,'server thread closed') self.client.close() def start_server(self): # Start OSCServer self.mon.log(self,'Starting OSCServer') self.st = threading.Thread( target = self.server.serve_forever ) self.st.start() def add_initial_handlers(self): self.server.addMsgHandler('default', self.no_match_handler) self.server.addMsgHandler(self.prefix+self.this_unit+"/system/server-info", self.server_info_handler) self.server.addMsgHandler(self.prefix+self.this_unit+"/system/loopback", self.loopback_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/open', self.open_show_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/close', self.close_show_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/exitpipresents', self.exitpipresents_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/shutdownnow', self.shutdownnow_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/event', self.input_event_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/output', self.output_event_handler) def no_match_handler(self,addr, tags, stuff, source): self.mon.warn(self,"no match for osc msg with addr : %s" % addr) return None def server_info_handler(self,addr, tags, stuff, source): msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/server-info-reply') msg.append('Unit: '+ self.options.this_unit_name) return msg def loopback_handler(self,addr, tags, stuff, source): # send a reply to the client. msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/loopback-reply') return msg def open_show_handler(self,address, tags, args, source): self.prepare_show_command_callback('open ',args,1) def close_show_handler(self,address, tags, args, source): self.prepare_show_command_callback('close ', args,1) def exitpipresents_handler(self,address, tags, args, source): self.prepare_show_command_callback('exitpipresents',args,0) def shutdownnow_handler(self,address, tags, args, source): self.prepare_show_command_callback('shutdownnow',args,0) def prepare_show_command_callback(self,command,args,limit): if len(args) == limit: if limit !=0: self.mon.sched(self,'Received from OSC: '+ command + ' ' +args[0]) self.show_command_callback(command+args[0]) else: self.mon.sched(self,'Received from OSC: '+ command) self.show_command_callback(command) else: self.mon.warn(self,'OSC show command does not have '+limit +' argument - ignoring') def input_event_handler(self,address, tags, args, source): if len(args) == 1: self.input_event_callback(args[0],'OSC') else: self.mon.warn(self,'OSC input event does not have 1 argument - ignoring') def output_event_handler(self,address, tags, args, source): if len(args) !=0: # delay symbol,param_type,param_values,req_time as a string text='0 ' for arg in args: text= text+ arg + ' ' text = text + '0' self.output_event_callback(text) else: self.mon.warn(self,'OSC output event has no arguments - ignoring') #send messages to controlled units # parses the message string into fields and sends - NO error checking def send_command(self,text): self.mon.log(self,'send OSC Command: ' + text ) if self.this_unit_type not in ('master','remote','master+slave'): self.mon.warn(self,'Unit is not an OSC Master, ignoring command') return fields=text.split() address = fields[0] # print 'ADDRESS'+address address_fields=address[1:].split('/') if address_fields[0] != 'pipresents': self.mon.warn(self,'prefix is not pipresents: '+address_fields[0]) if address_fields[1] != self.options.controlled_unit_1_name: self.mon.warn(self,'not sending OSC to the controlled unit: ' +self.options.controlled_unit_1_name + ' is '+ address_fields[1]) arg_list=fields[1:] self.send(address,arg_list) def send(self,address,arg_list): # print self.command_client msg = OSC.OSCMessage() # print address msg.setAddress(address) for arg in arg_list: # print arg msg.append(arg) self.command_client.send(msg)
class TimeOfDay(object): # CLASS VARIABLES # change this for another language DAYS_OF_WEEK=['monday','tuesday','wednesday','thursday','friday','saturday','sunday'] """ TimeOfDay.events is a dictionary the keys being show-refs. Each dictionary entry is a list of time_elements sorted by descending time Each time element is a list with the fields: 0 - command 1 - time, seconds since midnight """ events={} # list of times of day used to generate callbacks, earliest first # executed by main program and by each object using tod def __init__(self): self.mon=Monitor() # executed once from main program only def init(self,pp_dir,pp_home,pp_profile,root,callback): # instantiate arguments TimeOfDay.root=root self.pp_dir=pp_dir self.pp_home=pp_home self.pp_profile=pp_profile self.callback=callback # init variables self.testing=False self.tod_tick=500 self.tick_timer=None TimeOfDay.now = datetime.now().replace(microsecond=0) # read the schedule self.schedule=self.open_schedule() #create the initial events list if 'simulate-time' in self.schedule and self.schedule['simulate-time']=='yes': year= int(self.schedule['sim-year']) month= int(self.schedule['sim-month']) day = int(self.schedule['sim-day']) hour= int(self.schedule['sim-hour']) minute= int(self.schedule['sim-minute']) second= int(self.schedule['sim-second']) TimeOfDay.now = datetime(day = day, month =month, year=year,hour=hour, minute=minute, second=second) self.testing=True # print '\nInitial SIMULATED time',TimeOfDay.now.ctime() self.mon.sched(self,'Testing is ON, Initial SIMULATED time ' + str(TimeOfDay.now.ctime())) else: #get the current date/time only this once TimeOfDay.now = datetime.now().replace(microsecond=0) # print '\nInitial REAL time',TimeOfDay.now.ctime() self.testing=False TimeOfDay.last_now = TimeOfDay.now - timedelta(seconds=1) self.build_schedule_for_today(self.schedule) self.mon.sched(self,self.pretty_todays_schedule()) # if self.testing: # self.print_todays_schedule() self.build_events_lists() # self.print_events_lists() # and do exitpipresents or start any show that should be running at start up time self.do_catchup() def do_catchup(self): TimeOfDay.scheduler_time=TimeOfDay.now.time() # shutdown or exit if current time is later than time in event list for show_ref in TimeOfDay.events: if show_ref == 'pp_core': times = TimeOfDay.events[show_ref] for time_element in reversed(times): # print 'now', TimeOfDay.scheduler_time, 'time from events', time_element[1], time_element[0] # got past current time can give up and execute exitpipresents or closedown if TimeOfDay.scheduler_time >= time_element[1]: self.do_event(show_ref,time_element) return 'exiting' # do the catchup for each real show in turn for show_ref in TimeOfDay.events: if show_ref != 'pp_core': # print '\n*****',show_ref times = TimeOfDay.events[show_ref] # go through the event list for a show rembering show state until the first future event is found. # then if last command was to start the show send it show_running=False last_start_element=[] for time_element in reversed(times): # print 'now', now_seconds, 'time from events', time_element[1], time_element[0] # got past current time can give up catch up if time_element[1] >= TimeOfDay.scheduler_time: # print ' gone past time - break' break if time_element[0] == 'open': last_start_element=time_element # print 'open - show-running= true' show_running=True elif time_element[0]=='close': # print 'close - show-running= false' show_running=False if show_running is True: #if self.testing: # print 'End of Catch Up Search', show_ref,last_start_element self.mon.sched(self,'Catch up for show: ' + show_ref +' requires '+ last_start_element[0] + ' ' +str(last_start_element[1])) self.do_event(show_ref,last_start_element) ## # print 'catchup time match', show_ref ## # now do the inital real time command if time now matches event time ## for time_element in reversed(times): ## if time_element[1] == TimeOfDay.scheduler_time: ## print ' do event - catchup', TimeOfDay.scheduler_time, 'time from events', time_element[1], time_element[0] ## self.do_event(show_ref,time_element) return 'not exiting' # called by main program only def poll(self): if self.testing: poll_time=TimeOfDay.now else: poll_time=datetime.now() # print 'poll time: ',poll_time.time(),'scheduler time: ',TimeOfDay.now.time() # if poll_time != TimeOfDay.now : print 'times different ',poll_time.time(),TimeOfDay.now.time() # is current time greater than last time the scheduler was run # run in a loop to catch up because root.after can get behind when images are being rendered etc. # poll time can be the same twice as poll is run at half second intervals. catchup_time=0 while TimeOfDay.now<=poll_time: if TimeOfDay.now-TimeOfDay.last_now != timedelta(seconds=1): print 'POLL TIME FAILED', TimeOfDay.last_now, TimeOfDay.now #if catchup_time != 0: # print 'scheduler behind by: ',catchup_time, TimeOfDay.now.time(),poll_time.time() self.do_scheduler() TimeOfDay.last_now=TimeOfDay.now catchup_time+=1 TimeOfDay.now = TimeOfDay.now + timedelta(seconds=1) # and loop if self.testing: self.tick_timer=TimeOfDay.root.after(1000,self.poll) else: self.tick_timer=TimeOfDay.root.after(self.tod_tick,self.poll) # called by main program only def terminate(self): if self.tick_timer is not None: TimeOfDay.root.after_cancel(self.tick_timer) self.clear_events_lists() # execute events at the appropriate time. # called by main program only def do_scheduler(self): # if its midnight then build the events lists for the new day TimeOfDay.scheduler_time=TimeOfDay.now.time() if TimeOfDay.scheduler_time == time(hour=0,minute=0,second=0): # if self.testing: # print 'Its midnight, today is now', TimeOfDay.now.ctime() self.mon.sched(self,'Its midnight, today is now ' + str(TimeOfDay.now.ctime())) self.build_schedule_for_today(self.schedule) self.mon.sched(self,self.pretty_todays_schedule()) # if self.testing: # self.print_todays_schedule() self.build_events_lists() # self.mon.sched(self,self.pretty_events_lists()) # self.print_events_lists() # print TimeOfDay.scheduler_time for show_ref in TimeOfDay.events: # print 'scheduler time match', show_ref times = TimeOfDay.events[show_ref] # now send a command if time matches for time_element in reversed(times): # print time_element[1],TimeOfDay.scheduler_time if time_element[1] == TimeOfDay.scheduler_time: self.do_event(show_ref,time_element) # execute an event def do_event(self,show_ref,time_element): self.mon.log (self,'Event : ' + time_element[0] + ' ' + show_ref + ' required at: ' + time_element[1].isoformat()) self.mon.sched (self,' ToD Scheduler : ' + time_element[0] + ' ' + show_ref + ' required at: ' + time_element[1].isoformat()) # if self.testing: # print 'Event : ' + time_element[0] + ' ' + show_ref + ' required at: '+ time_element[1].isoformat() self.callback(time_element[0] + ' ' + show_ref) # # ************************************************ # The methods below can be called from many classes so need to operate on class variables # ************************************************ # clear events list def clear_events_lists(self): self.mon.log(self,'clear time of day events list ') # empty event list TimeOfDay.events={} # *********************************** # Preparing schedule and todays event list # ************************************ def open_schedule(self): # look for the schedule.json file # try inside profile filename=self.pp_profile+os.sep+"schedule.json" ifile = open(filename, 'rb') schedule= json.load(ifile) ifile.close() self.mon.log(self,"schedule.json read from "+ filename) return schedule def build_schedule_for_today(self,schedule): # print this_day.year, this_day.month, this_day.day, TimeOfDay.DAYS_OF_WEEK[ this_day.weekday()] """ self.todays_schedule is a dictionary the keys being show-refs. Each dictionary entry is a list of time_elements Each time element is a list with the fields: 0 - command 1 - time hour:min[:sec] """ self.todays_schedule={} for show in schedule['shows']: show_ref=show['show-ref'] if 'everyday' in show: day=show['everyday'] # print day['day'] times=day['times'] for time in times: self.todays_schedule[show['show-ref']]=copy.deepcopy(day['times']) # print '\nafter everyday' # self.print_todays_schedule() if 'weekday' in show: day=show['weekday'] # print day['day'] if TimeOfDay.DAYS_OF_WEEK[ TimeOfDay.now.weekday()] in day['day'] : # print 'weekday matched', TimeOfDay.DAYS_OF_WEEK[ TimeOfDay.now.weekday()] times=day['times'] for time in times: self.todays_schedule[show['show-ref']]=copy.deepcopy(day['times']) # print '\nafter weekday' # self.print_todays_schedule() if 'monthday' in show: day=show['monthday'] # print day['day'] if TimeOfDay.now.day in map(int,day['day']): # print 'monthday matched', day['day'] times=day['times'] for time in times: self.todays_schedule[show['show-ref']]=copy.deepcopy(day['times']) # print '\nafter monthday' # self.print_todays_schedule() if 'specialday' in show: days=show['specialday'] # print days['day'] for day in days['day']: sdate=datetime.strptime(day,'%Y-%m-%d') if sdate.year == TimeOfDay.now.year and sdate.month==TimeOfDay.now.month and sdate.day == TimeOfDay.now.day: # print 'special matched', day times=days['times'] # for time in times: self.todays_schedule[show['show-ref']]=copy.deepcopy(days['times']) # print '\nafter specialday' # self.print_todays_schedule() def build_events_lists(self): # builds events dictionary from todays_schedule by # converting times in todays schedule from hour:min:sec to datetime # and sorts them earliest last TimeOfDay.events={} for show_ref in self.todays_schedule: # print show_ref times = self.todays_schedule[show_ref] for time_element in times: time_element[1]=self.parse_event_time(time_element[1]) sorted_times=sorted(times,key = lambda time_element: time_element[1],reverse=True) TimeOfDay.events[show_ref]=sorted_times # print times def parse_event_time(self,time_text): fields=time_text.split(':') if len(fields)>2: secs=int(fields[2]) else: secs=0 hours=int(fields[0]) mins=int(fields[1]) return time(hour=hours,minute=mins,second=secs) # ********************* # print for debug # ********************* def pretty_todays_schedule(self): op='Schedule For ' + TimeOfDay.now.ctime() + '\n' for key in self.todays_schedule: op += ' '+ key + '\n' for show in self.todays_schedule[key]: op += ' '+show[0]+ ': '+ str(show[1])+ '\n' op +='\n' return op def pretty_events_lists(self): op = ' Events list for today' for key in self.events: op += '\n' + key for show in self.events[key]: op += '\n ' + show[0] + ' ' + str(show[1].isoformat()) return op def print_todays_schedule(self): print '\nSchedule For '+ TimeOfDay.now.ctime() for key in self.todays_schedule: print ' '+key for show in self.todays_schedule[key]: print ' '+show[0]+ ': '+show[1] print def print_events_lists(self): print '\nevents list for today' for key in self.events: print '\n',key for show in self.events[key]: print show[0],show[1].isoformat() def save_schedule(self,filename): """ save a schedule """ if filename=="": return False if os.name=='nt': filename = string.replace(filename,'/','\\') else: filename = string.replace(filename,'\\','/') ofile = open(filename, "wb") json.dump(self.schedule,ofile,sort_keys= False,indent=1) ofile.close() return
class OSCDriver(object): # executed by main program def init(self,pp_profile,show_command_callback,input_event_callback,output_event_callback): self.pp_profile=pp_profile self.show_command_callback=show_command_callback self.input_event_callback=input_event_callback self.output_event_callback=output_event_callback self.mon=Monitor() config_file=self.pp_profile + os.sep +'pp_io_config'+os.sep+ 'osc.cfg' if not os.path.exists(config_file): self.mon.err(self, 'OSC Configuration file not found: '+config_file) return'error','OSC Configuration file nof found: '+config_file self.mon.log(self, 'OSC Configuration file found at: '+config_file) self.options=OSCConfig() # only reads the data for required unit_type if self.options.read(config_file) ==False: return 'error','failed to read osc.cfg' self.prefix='/pipresents' self.this_unit='/' + self.options.this_unit_name self.this_unit_type = self.options.this_unit_type self.reply_client=None self.command_client=None self.client=None self.server=None if self.this_unit_type not in ('master','slave','master+slave'): return 'error','OSC config, this unit type not known: '+self.this_unit_type if self.this_unit_type in('slave','master+slave'): #start the client that sends replies to controlling unit self.reply_client=OSC.OSCClient() self.mon.log(self, 'sending replies to controller at: '+self.options.controlled_by_ip+':'+self.options.controlled_by_port) self.reply_client.connect((self.options.controlled_by_ip,int(self.options.controlled_by_port))) self.mon.log(self,'sending repiles to: '+ str(self.reply_client)) self.client=self.reply_client if self.this_unit_type in ('master','master+slave'): #start the client that sends commands to the controlled unit self.command_client=OSC.OSCClient() self.command_client.connect((self.options.controlled_unit_1_ip,int(self.options.controlled_unit_1_port))) self.mon.log(self, 'sending commands to controled unit at: '+self.options.controlled_unit_1_ip+':'+self.options.controlled_unit_1_port) self.mon.log(self,'sending commands to: '+str(self.command_client)) self.client=self.command_client #start the listener's server self.mon.log(self, 'listen to commands from controlled by unit and replies from controlled units on: ' + self.options.this_unit_ip+':'+self.options.this_unit_port) self.server=myOSCServer((self.options.this_unit_ip,int(self.options.this_unit_port)),self.client) # return_port=int(self.options.controlled_by_port) self.mon.log(self,'listening on: '+str(self.server)) self.add_initial_handlers() return 'normal','osc.cfg read' def terminate(self): if self.server != None: self.server.close() self.mon.log(self, 'Waiting for Server-thread to finish') if self.st != None: self.st.join() ##!!! self.mon.log(self,'server thread closed') self.client.close() def start_server(self): # Start OSCServer self.mon.log(self,'Starting OSCServer') self.st = threading.Thread( target = self.server.serve_forever ) self.st.start() def add_initial_handlers(self): self.server.addMsgHandler('default', self.no_match_handler) self.server.addMsgHandler(self.prefix+self.this_unit+"/system/server-info", self.server_info_handler) self.server.addMsgHandler(self.prefix+self.this_unit+"/system/loopback", self.loopback_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/open', self.open_show_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/close', self.close_show_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/exitpipresents', self.exitpipresents_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/shutdownnow', self.shutdownnow_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/event', self.input_event_handler) self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/output', self.output_event_handler) def no_match_handler(self,addr, tags, stuff, source): self.mon.warn(self,"no match for osc msg with addr : %s" % addr) return None def server_info_handler(self,addr, tags, stuff, source): msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/server-info-reply') msg.append('Unit: '+ self.options.this_unit_name) return msg def loopback_handler(self,addr, tags, stuff, source): # send a reply to the client. msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/loopback-reply') return msg def open_show_handler(self,address, tags, args, source): self.prepare_show_command_callback('open ',args,1) def close_show_handler(self,address, tags, args, source): self.prepare_show_command_callback('close ', args,1) def exitpipresents_handler(self,address, tags, args, source): self.prepare_show_command_callback('exitpipresents',args,0) def shutdownnow_handler(self,address, tags, args, source): self.prepare_show_command_callback('shutdownnow',args,0) def prepare_show_command_callback(self,command,args,limit): if len(args) == limit: if limit !=0: self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command + ' ' +args[0]) self.show_command_callback(command+args[0]) else: self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command) self.show_command_callback(command) else: self.mon.warn(self,'OSC show command does not have '+limit +' argument - ignoring') def input_event_handler(self,address, tags, args, source): if len(args) == 1: self.input_event_callback(args[0],'OSC') else: self.mon.warn(self,'OSC input event does not have 1 argument - ignoring') def output_event_handler(self,address, tags, args, source): if len(args) !=0: # delay symbol,param_type,param_values,req_time as a string text='0 ' for arg in args: text= text+ arg + ' ' text = text + '0' self.output_event_callback(text) else: self.mon.warn(self,'OSC output event has no arguments - ignoring') #send messages to controlled units # parses the message string into fields and sends - NO error checking def send_command(self,text): self.mon.log(self,'send OSC Command: ' + text ) if self.this_unit_type not in ('master','remote','master+slave'): self.mon.warn(self,'Unit is not an OSC Master, ignoring command') return fields=text.split() address = fields[0] # print 'ADDRESS'+address address_fields=address[1:].split('/') if address_fields[0] != 'pipresents': self.mon.warn(self,'prefix is not pipresents: '+address_fields[0]) if address_fields[1] != self.options.controlled_unit_1_name: self.mon.warn(self,'not sending OSC to the controlled unit: ' +self.options.controlled_unit_1_name + ' is '+ address_fields[1]) arg_list=fields[1:] self.send(address,arg_list) def send(self,address,arg_list): # print self.command_client msg = OSC.OSCMessage() # print address msg.setAddress(address) for arg in arg_list: # print arg msg.append(arg) self.command_client.send(msg)
class ShowManager(object): """ ShowManager manages PiPresents' concurrent shows. It does not manage sub-shows or child-shows but has a bit of common code to initilise them concurrent shows are always top level (level 0) shows: They can be opened/closed by the start show(open only) or by 'open/close myshow' in the Show Control field of players, by time of day sceduler or by OSC Two shows with the same show reference cannot be run concurrently as there is no way to reference an individual instance. However a workaround is to make the secong instance a subshow of a mediashow with a different reference. """ # Declare class variables shows=[] canvas=None #canvas for all shows shutdown_required=False SHOW_TEMPLATE=['',None] SHOW_REF= 0 # show-reference - name of the show as in editor SHOW_OBJ = 1 # the python object showlist=[] # Initialise class variables, first time through only in pipresents.py def init(self,canvas,all_shows_ended_callback,command_callback,showlist): ShowManager.all_shows_ended_callback=all_shows_ended_callback ShowManager.shows=[] ShowManager.shutdown_required=False ShowManager.canvas=canvas ShowManager.command_callback = command_callback ShowManager.showlist = showlist # ************************************** # functions to manipulate show register # ************************************** def register_shows(self): for show in ShowManager.showlist.shows(): if show['show-ref'] != 'start': reason,message=self.register_show(show['show-ref']) if reason =='error': return reason,message return 'normal','shows regiistered' def register_show(self,ref): registered=self.show_registered(ref) if registered == -1: ShowManager.shows.append(copy.deepcopy(ShowManager.SHOW_TEMPLATE)) index=len(ShowManager.shows)-1 ShowManager.shows[index][ShowManager.SHOW_REF]=ref ShowManager.shows[index][ShowManager.SHOW_OBJ]=None self.mon.trace(self,' - register show: show_ref = ' + ref + ' index = ' + str(index)) return'normal','show registered' else: # self.mon.err(self, ' more than one show in showlist with show-ref: ' + ref ) return 'error', ' more than one show in showlist with show-ref: ' + ref # is the show registered? # can be used to return the index to the show def show_registered(self,show_ref): index=0 for show in ShowManager.shows: if show[ShowManager.SHOW_REF] == show_ref: return index index+=1 return -1 # needs calling program to check that the show is not already running def set_running(self,index,show_obj): ShowManager.shows[index][ShowManager.SHOW_OBJ]=show_obj self.mon.trace(self, 'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] + ' show_id= ' + str(index)) # is the show running? def show_running(self,index): if ShowManager.shows[index][ShowManager.SHOW_OBJ] is not None: return ShowManager.shows[index][ShowManager.SHOW_OBJ] else: return None def set_exited(self,index): ShowManager.shows[index][ShowManager.SHOW_OBJ]=None self.mon.trace(self,'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] + ' show_id= ' + str(index)) # are all shows exited? def all_shows_exited(self): all_exited=True for show in ShowManager.shows: if show[ShowManager.SHOW_OBJ] is not None: all_exited=False return all_exited # fromat for printing def pretty_shows(self): shows='\n' for show in ShowManager.shows: shows += show[ShowManager.SHOW_REF] +'\n' return shows # ********************************* # show control # ********************************* # show manager can be initialised by a player, shower or by pipresents.py # if by pipresents.py then show_id=-1 def __init__(self,show_id,showlist,show_params,root,canvas,pp_dir,pp_profile,pp_home): self.show_id=show_id self.showlist=showlist self.show_params=show_params self.root=root self.show_canvas=canvas self.pp_dir=pp_dir self.pp_profile=pp_profile self.pp_home=pp_home self.mon=Monitor() def control_a_show(self,show_ref,show_command): if show_command == 'open': return self.start_show(show_ref) elif show_command == 'close': return self.exit_show(show_ref) elif show_command == 'closeall': return self.exit_all_shows() elif show_command == 'openexclusive': return self.open_exclusive(show_ref) else: return 'error','command not recognised '+ show_command def open_exclusive(self,show_ref): self.exit_all_shows() self.exclusive_show=show_ref self.wait_for_openexclusive() return 'normal','opened exclusive' def wait_for_openexclusive(self): if self.all_shows_exited() is False: ShowManager.canvas.after(1,self.wait_for_openexclusive) return self.start_show(self.exclusive_show) def exit_all_shows(self): for show in ShowManager.shows: self.exit_show(show[ShowManager.SHOW_REF]) return 'normal','exited all shows' # kick off the exit sequence of a show by calling the shows exit method. # it will result in all the shows in a stack being closed and end_play_show being called def exit_show(self,show_ref): index=self.show_registered(show_ref) self.mon.log(self,"De-registering show "+ show_ref + ' show index:' + str(index)) show_obj=self.show_running(index) if show_obj is not None: self.mon.log(self,"Exiting show "+ show_ref + ' show index:' + str(index)) show_obj.exit() return 'normal','exited a concurrent show' def start_show(self,show_ref): index=self.show_registered(show_ref) if index <0: return 'error',"Show not found in showlist: "+ show_ref show_index = self.showlist.index_of_show(show_ref) show=self.showlist.show(show_index) reason,message,show_canvas=self.compute_show_canvas(show) if reason == 'error': return reason,message # print 'STARTING TOP LEVEL SHOW',show_canvas self.mon.sched(self,TimeOfDay.now,'Starting Show: ' + show_ref + ' from show: ' + self.show_params['show-ref']) self.mon.log(self,'Starting Show: ' + show_ref + ' from: ' + self.show_params['show-ref']) if self.show_running(index): self.mon.sched(self,TimeOfDay.now,"show already running so ignoring command: "+show_ref) self.mon.warn(self,"show already running so ignoring command: "+show_ref) return 'normal','this concurrent show already running' show_obj = self.init_show(index,show,show_canvas) if show_obj is None: return 'error',"unknown show type in start concurrent show - "+ show['type'] else: self.set_running(index,show_obj) # params - end_callback, show_ready_callback, parent_kickback_signal, level show_obj.play(self._end_play_show,None,False,0,[]) return 'normal','concurrent show started' # used by shows to create subshows or child shows def init_subshow(self,show_id,show,show_canvas): return self.init_show(show_id,show,show_canvas) def _end_play_show(self,index,reason,message): show_ref_to_exit =ShowManager.shows[index][ShowManager.SHOW_REF] show_to_exit =ShowManager.shows[index][ShowManager.SHOW_OBJ] self.mon.sched(self,TimeOfDay.now,'Closed show: ' + show_ref_to_exit) self.mon.log(self,'Exited from show: ' + show_ref_to_exit + ' ' + str(index) ) self.mon.log(self,'Exited with Reason = ' + reason) self.mon.trace(self,' Show is: ' + show_ref_to_exit + ' show index ' + str(index)) # closes the video/audio from last track then closes the track # print 'show to exit ',show_to_exit, show_to_exit.current_player,show_to_exit.previous_player self.set_exited(index) if self.all_shows_exited() is True: ShowManager.all_shows_ended_callback(reason,message) return reason,message # common function to initilaise the show by type def init_show(self,show_id,selected_show,show_canvas,): if selected_show['type'] == "mediashow": return MediaShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "liveshow": return LiveShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "radiobuttonshow": return RadioButtonShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "hyperlinkshow": return HyperlinkShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "menu": return MenuShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "artmediashow": return ArtMediaShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) elif selected_show['type'] == "artliveshow": return ArtLiveShow(show_id, selected_show, self.root, show_canvas, self.showlist, self.pp_dir, self.pp_home, self.pp_profile, ShowManager.command_callback) else: return None def compute_show_canvas(self,show_params): canvas={} canvas['canvas-obj']= ShowManager.canvas status,message,self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2,self.show_canvas_y2= self.parse_show_canvas(show_params['show-canvas']) if status == 'error': # self.mon.err(self,'show canvas error: ' + message + ' in ' + show_params['show-canvas']) return 'error','show canvas error: ' + message + ' in ' + show_params['show-canvas'],canvas else: self.show_canvas_width = self.show_canvas_x2 - self.show_canvas_x1 self.show_canvas_height=self.show_canvas_y2 - self.show_canvas_y1 self.show_canvas_centre_x= self.show_canvas_width/2 self.show_canvas_centre_y= self.show_canvas_height/2 canvas['show-canvas-x1'] = self.show_canvas_x1 canvas['show-canvas-y1'] = self.show_canvas_y1 canvas['show-canvas-x2'] = self.show_canvas_x2 canvas['show-canvas-y2'] = self.show_canvas_y2 canvas['show-canvas-width'] = self.show_canvas_width canvas['show-canvas-height'] = self.show_canvas_height canvas['show-canvas-centre-x'] = self.show_canvas_centre_x canvas['show-canvas-centre-y'] = self.show_canvas_centre_y return 'normal','',canvas def parse_show_canvas(self,text): fields = text.split() # blank so show canvas is the whole screen if len(fields) < 1: return 'normal','',0,0,int(self.canvas['width']),int(self.canvas['height']) elif len(fields) in (1,4): # window is specified status,message,x1,y1,x2,y2=parse_rectangle(text) if status=='error': return 'error',message,0,0,0,0 else: return 'normal','',x1,y1,x2,y2 else: # error return 'error','Wrong number of fields in Show canvas: '+ text,0,0,0,0
class PiPresents(object): def pipresents_version(self): vitems=self.pipresents_issue.split('.') if len(vitems)==2: # cope with 2 digit version numbers before 1.3.2 return 1000*int(vitems[0])+100*int(vitems[1]) else: return 1000*int(vitems[0])+100*int(vitems[1])+int(vitems[2]) def __init__(self): # gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL) gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_SAVEALL) self.pipresents_issue="1.3.5" self.pipresents_minorissue = '1.3.5d' # position and size of window without -f command line option self.nonfull_window_width = 0.45 # proportion of width self.nonfull_window_height= 0.7 # proportion of height self.nonfull_window_x = 0 # position of top left corner self.nonfull_window_y=0 # position of top left corner StopWatch.global_enable=False # set up the handler for SIGTERM signal.signal(signal.SIGTERM,self.handle_sigterm) # **************************************** # Initialisation # *************************************** # get command line options self.options=command_options() # get Pi Presents code directory pp_dir=sys.path[0] self.pp_dir=pp_dir if not os.path.exists(pp_dir+"/pipresents.py"): if self.options['manager'] is False: tkMessageBox.showwarning("Pi Presents","Bad Application Directory") exit(102) # Initialise logging and tracing Monitor.log_path=pp_dir self.mon=Monitor() # Init in PiPresents only self.mon.init() # uncomment to enable control of logging from within a class # Monitor.enable_in_code = True # enables control of log level in the code for a class - self.mon.set_log_level() # make a shorter list to log/trace only some classes without using enable_in_code. Monitor.classes = ['PiPresents', 'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow', 'GapShow','Show','ArtShow', 'AudioPlayer','BrowserPlayer','ImagePlayer','MenuPlayer','MessagePlayer','VideoPlayer','Player', 'MediaList','LiveList','ShowList', 'PathManager','ControlsManager','ShowManager','PluginManager','IOPluginManager', 'MplayerDriver','OMXDriver','UZBLDriver', 'TimeOfDay','ScreenDriver','Animate','OSCDriver','CounterManager', 'Network','Mailer' ] # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver'] # Monitor.classes=['OSCDriver'] # get global log level from command line Monitor.log_level = int(self.options['debug']) Monitor.manager = self.options['manager'] # print self.options['manager'] self.mon.newline(3) self.mon.sched (self,None, "Pi Presents is starting, Version:"+self.pipresents_minorissue + ' at '+time.strftime("%Y-%m-%d %H:%M.%S")) self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue+ ' at '+time.strftime("%Y-%m-%d %H:%M.%S")) # self.mon.log (self," OS and separator:" + os.name +' ' + os.sep) self.mon.log(self,"sys.path[0] - location of code: "+sys.path[0]) # log versions of Raspbian and omxplayer, and GPU Memory with open("/boot/issue.txt") as ifile: self.mon.log(self,'\nRaspbian: '+ifile.read()) self.mon.log(self,'\n'+check_output(["omxplayer", "-v"])) self.mon.log(self,'\nGPU Memory: '+check_output(["vcgencmd", "get_mem", "gpu"])) if os.geteuid() == 0: print 'Do not run Pi Presents with sudo' self.mon.log(self,'Do not run Pi Presents with sudo') self.mon.finish() sys.exit(102) if "DESKTOP_SESSION" not in os.environ: print 'Pi Presents must be run from the Desktop' self.mon.log(self,'Pi Presents must be run from the Desktop') self.mon.finish() sys.exit(102) else: self.mon.log(self,'Desktop is '+ os.environ['DESKTOP_SESSION']) # optional other classes used self.root=None self.ppio=None self.tod=None self.animate=None self.ioplugin_manager=None self.oscdriver=None self.osc_enabled=False self.tod_enabled=False self.email_enabled=False user=os.getenv('USER') if user is None: tkMessageBox.showwarning("You must be logged in to run Pi Presents") exit(102) if user !='pi': self.mon.warn(self,"You must be logged as pi to use GPIO") self.mon.log(self,'User is: '+ user) # self.mon.log(self,"os.getenv('HOME') - user home directory (not used): " + os.getenv('HOME')) # does not work # self.mon.log(self,"os.path.expanduser('~') - user home directory: " + os.path.expanduser('~')) # does not work # check network is available self.network_connected=False self.network_details=False self.interface='' self.ip='' self.unit='' # sets self.network_connected and self.network_details self.init_network() # start the mailer and send email when PP starts self.email_enabled=False if self.network_connected is True: self.init_mailer() if self.email_enabled is True and self.mailer.email_at_start is True: subject= '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime("%Y-%m-%d %H:%M") message = time.strftime("%Y-%m-%d %H:%M") + '\nUnit: ' + self.unit + ' Profile: '+ self.options['profile']+ '\n ' + self.interface + '\n ' + self.ip self.send_email('start',subject,message) # get profile path from -p option if self.options['profile'] != '': self.pp_profile_path="/pp_profiles/"+self.options['profile'] else: self.mon.err(self,"Profile not specified in command ") self.end('error','Profile not specified with the commands -p option') # get directory containing pp_home from the command, if self.options['home'] == "": home = os.sep+ 'home' + os.sep + user + os.sep+"pp_home" else: home = self.options['home'] + os.sep+ "pp_home" self.mon.log(self,"pp_home directory is: " + home) # check if pp_home exists. # try for 10 seconds to allow usb stick to automount found=False for i in range (1, 10): self.mon.log(self,"Trying pp_home at: " + home + " (" + str(i)+')') if os.path.exists(home): found=True self.pp_home=home break time.sleep (1) if found is True: self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home) else: self.mon.err(self,"Failed to find pp_home directory at " + home) self.end('error',"Failed to find pp_home directory at " + home) # check profile exists self.pp_profile=self.pp_home+self.pp_profile_path if os.path.exists(self.pp_profile): self.mon.sched(self,None,"Running profile: " + self.pp_profile_path) self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile) else: self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile) self.end('error',"Failed to find requested profile: "+ self.pp_profile) self.mon.start_stats(self.options['profile']) if self.options['verify'] is True: self.mon.err(self,"Validation option not supported - use the editor") self.end('error','Validation option not supported - use the editor') # initialise and read the showlist in the profile self.showlist=ShowList() self.showlist_file= self.pp_profile+ "/pp_showlist.json" if os.path.exists(self.showlist_file): self.showlist.open_json(self.showlist_file) else: self.mon.err(self,"showlist not found at "+self.showlist_file) self.end('error',"showlist not found at "+self.showlist_file) # check profile and Pi Presents issues are compatible if self.showlist.profile_version() != self.pipresents_version(): self.mon.err(self,"Version of showlist " + self.showlist.profile_version_string + " is not same as Pi Presents") self.end('error',"Version of showlist " + self.showlist.profile_version_string + " is not same as Pi Presents") # get the 'start' show from the showlist index = self.showlist.index_of_start_show() if index >=0: self.showlist.select(index) self.starter_show=self.showlist.selected_show() else: self.mon.err(self,"Show [start] not found in showlist") self.end('error',"Show [start] not found in showlist") # ******************** # SET UP THE GUI # ******************** # turn off the screenblanking and saver if self.options['noblank'] is True: call(["xset","s", "off"]) call(["xset","s", "-dpms"]) self.root=Tk() self.title='Pi Presents - '+ self.pp_profile self.icon_text= 'Pi Presents' self.root.title(self.title) self.root.iconname(self.icon_text) self.root.config(bg=self.starter_show['background-colour']) self.mon.log(self, 'monitor screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixels') if self.options['screensize'] =='': self.screen_width = self.root.winfo_screenwidth() self.screen_height = self.root.winfo_screenheight() else: reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize']) if reason =='error': self.mon.err(self,message) self.end('error',message) self.mon.log(self, 'forced screen dimensions (--screensize) are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixels') # set window dimensions and decorations if self.options['fullscreen'] is False: self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width) self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height) self.window_x=self.nonfull_window_x self.window_y=self.nonfull_window_y self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y)) else: self.window_width=self.screen_width self.window_height=self.screen_height self.root.attributes('-fullscreen', True) os.system('unclutter &') self.window_x=0 self.window_y=0 self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y)) self.root.attributes('-zoomed','1') # canvas cover the whole screen whatever the size of the window. self.canvas_height=self.screen_height self.canvas_width=self.screen_width # make sure focus is set. self.root.focus_set() # define response to main window closing. self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort) # setup a canvas onto which will be drawn the images or text self.canvas = Canvas(self.root, bg=self.starter_show['background-colour']) if self.options['fullscreen'] is True: self.canvas.config(height=self.canvas_height, width=self.canvas_width, highlightthickness=0) else: self.canvas.config(height=self.canvas_height, width=self.canvas_width, highlightthickness=1, highlightcolor='yellow') self.canvas.place(x=0,y=0) # self.canvas.config(bg='black') self.canvas.focus_set() # **************************************** # INITIALISE THE TOUCHSCREEN DRIVER # **************************************** # each driver takes a set of inputs, binds them to symboic names # and sets up a callback which returns the symbolic name when an input event occurs self.sr=ScreenDriver() # read the screen click area config file reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile) if reason == 'error': self.end('error','cannot find, or error in screen.cfg') # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes # click areas are made on the Pi Presents canvas not the show canvases. reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event) if reason == 'error': self.mon.err(self,message) self.end('error',message) # **************************************** # INITIALISE THE APPLICATION AND START # **************************************** self.shutdown_required=False self.reboot_required=False self.terminate_required=False self.exitpipresents_required=False # initialise the I/O plugins by importing their drivers self.ioplugin_manager=IOPluginManager() reason,message=self.ioplugin_manager.init(self.pp_dir,self.pp_profile,self.root,self.handle_input_event) if reason == 'error': # self.mon.err(self,message) self.end('error',message) # kick off animation sequencer self.animate = Animate() self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event) self.animate.poll() #create a showmanager ready for time of day scheduler and osc server show_id=-1 self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home) # first time through set callback to terminate Pi Presents if all shows have ended. self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist) # Register all the shows in the showlist reason,message=self.show_manager.register_shows() if reason == 'error': self.mon.err(self,message) self.end('error',message) # Init OSCDriver, read config and start OSC server self.osc_enabled=False if self.network_connected is True: if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'): self.oscdriver=OSCDriver() reason,message=self.oscdriver.init(self.pp_profile, self.unit,self.interface,self.ip, self.handle_command,self.handle_input_event,self.e_osc_handle_animate) if reason == 'error': self.mon.err(self,message) self.end('error',message) else: self.osc_enabled=True self.root.after(1000,self.oscdriver.start_server()) # initialise ToD scheduler calculating schedule for today self.tod=TimeOfDay() reason,message,self.tod_enabled = self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.showlist,self.root,self.handle_command) if reason == 'error': self.mon.err(self,message) self.end('error',message) # warn if the network not available when ToD required if self.tod_enabled is True and self.network_connected is False: self.mon.warn(self,'Network not connected so Time of Day scheduler may be using the internal clock') # init the counter manager self.counter_manager=CounterManager() self.counter_manager.init() # warn about start shows and scheduler if self.starter_show['start-show']=='' and self.tod_enabled is False: self.mon.sched(self,None,"No Start Shows in Start Show and no shows scheduled") self.mon.warn(self,"No Start Shows in Start Show and no shows scheduled") if self.starter_show['start-show'] !='' and self.tod_enabled is True: self.mon.sched(self,None,"Start Shows in Start Show and shows scheduled - conflict?") self.mon.warn(self,"Start Shows in Start Show and shows scheduled - conflict?") # run the start shows self.run_start_shows() # kick off the time of day scheduler which may run additional shows if self.tod_enabled is True: self.tod.poll() # start the I/O plugins input event generation self.ioplugin_manager.start() # start Tkinters event loop self.root.mainloop( ) def parse_screen(self,size_text): fields=size_text.split('*') if len(fields)!=2: return 'error','do not understand --screensize comand option',0,0 elif fields[0].isdigit() is False or fields[1].isdigit() is False: return 'error','dimensions are not positive integers in --screensize',0,0 else: return 'normal','',int(fields[0]),int(fields[1]) # ********************* # RUN START SHOWS # ******************** def run_start_shows(self): self.mon.trace(self,'run start shows') # parse the start shows field and start the initial shows show_refs=self.starter_show['start-show'].split() for show_ref in show_refs: reason,message=self.show_manager.control_a_show(show_ref,'open') if reason == 'error': self.mon.err(self,message) # ********************* # User inputs # ******************** def e_osc_handle_animate(self,line): #jump out of server thread self.root.after(1, lambda arg=line: self.osc_handle_animate(arg)) def osc_handle_animate(self,line): self.mon.log(self,"animate command received: "+ line) #osc sends output events as a string reason,message,delay,name,param_type,param_values=self.animate.parse_animate_fields(line) if reason == 'error': self.mon.err(self,message) self.end(reason,message) self.handle_output_event(name,param_type,param_values,0) # output events are animate commands def handle_output_event(self,symbol,param_type,param_values,req_time): reason,message=self.ioplugin_manager.handle_output_event(symbol,param_type,param_values,req_time) if reason =='error': self.mon.err(self,message) self.end(reason,message) # all input events call this callback providing a symbolic name. # handle events that affect PP overall, otherwise pass to all active shows def handle_input_event(self,symbol,source): self.mon.log(self,"event received: "+symbol + ' from '+ source) if symbol == 'pp-terminate': self.handle_user_abort() elif symbol == 'pp-shutdown': self.mon.err(self,'pp-shutdown removed in version 1.3.3a, see Release Notes') self.end('error','pp-shutdown removed in version 1.3.3a, see Release Notes') elif symbol == 'pp-shutdownnow': # need root.after to grt out of st thread self.root.after(1,self.shutdownnow_pressed) return elif symbol == 'pp-exitpipresents': self.exitpipresents_required=True if self.show_manager.all_shows_exited() is True: # need root.after to grt out of st thread self.root.after(1,self.e_all_shows_ended_callback) return reason,message= self.show_manager.exit_all_shows() else: # pass the input event to all registered shows for show in self.show_manager.shows: show_obj=show[ShowManager.SHOW_OBJ] if show_obj is not None: show_obj.handle_input_event(symbol) # commands are generaed by tracks and shows # they can open or close shows, generate input events and do special tasks # commands also generate osc outputs to other computers # handles one command provided as a line of text def handle_command(self,command_text,source='',show=''): # print 'PIPRESENTS ',command_text,'\n Source',source,'from',show self.mon.log(self,"command received: " + command_text) if command_text.strip()=="": return fields= command_text.split() if fields[0] in ('osc','OSC'): if self.osc_enabled is True: status,message=self.oscdriver.parse_osc_command(fields[1:]) if status=='warn': self.mon.warn(self,message) if status=='error': self.mon.err(self,message) self.end('error',message) return if fields[0] =='counter': status,message=self.counter_manager.parse_counter_command(fields[1:]) if status=='error': self.mon.err(self,message) self.end('error',message) return show_command=fields[0] if len(fields)>1: show_ref=fields[1] else: show_ref='' if show_command in ('open','close','closeall','openexclusive'): self.mon.sched(self, TimeOfDay.now,command_text + ' received from show:'+show) if self.shutdown_required is False and self.terminate_required is False: reason,message=self.show_manager.control_a_show(show_ref,show_command) else: return elif show_command =='monitor': self.handle_monitor_command(show_ref) return elif show_command =='cec': self.handle_cec_command(show_ref) return elif show_command == 'event': self.handle_input_event(show_ref,'Show Control') return elif show_command == 'exitpipresents': self.exitpipresents_required=True if self.show_manager.all_shows_exited() is True: # need root.after to get out of st thread self.root.after(1,self.e_all_shows_ended_callback) return else: reason,message= self.show_manager.exit_all_shows() elif show_command == 'shutdownnow': # need root.after to get out of st thread self.root.after(1,self.shutdownnow_pressed) return elif show_command == 'reboot': # need root.after to get out of st thread self.root.after(1,self.reboot_pressed) return else: reason='error' message = 'command not recognised: '+ show_command if reason=='error': self.mon.err(self,message) return def handle_monitor_command(self,command): if command == 'on': os.system('vcgencmd display_power 1 >/dev/null') elif command == 'off': os.system('vcgencmd display_power 0 >/dev/null') def handle_cec_command(self,command): if command == 'on': os.system('echo "on 0" | cec-client -s') elif command == 'standby': os.system('echo "standby 0" | cec-client -s') elif command == 'scan': os.system('echo scan | cec-client -s -d 1') # deal with differnt commands/input events def shutdownnow_pressed(self): self.shutdown_required=True if self.show_manager.all_shows_exited() is True: self.all_shows_ended_callback('normal','no shows running') else: # calls exit method of all shows, results in all_shows_closed_callback self.show_manager.exit_all_shows() def reboot_pressed(self): self.reboot_required=True if self.show_manager.all_shows_exited() is True: self.all_shows_ended_callback('normal','no shows running') else: # calls exit method of all shows, results in all_shows_closed_callback self.show_manager.exit_all_shows() def handle_sigterm(self,signum,fframe): self.mon.log(self,'SIGTERM received - '+ str(signum)) self.terminate() def handle_user_abort(self): self.mon.log(self,'User abort received') self.terminate() def terminate(self): self.mon.log(self, "terminate received") self.terminate_required=True needs_termination=False for show in self.show_manager.shows: # print show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF] if show[ShowManager.SHOW_OBJ] is not None: needs_termination=True self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF]) # call shows terminate method # eventually the show will exit and after all shows have exited all_shows_callback will be executed. show[ShowManager.SHOW_OBJ].terminate() if needs_termination is False: self.end('killed','killed - no termination of shows required') # ****************************** # Ending Pi Presents after all the showers and players are closed # ************************** def e_all_shows_ended_callback(self): self.all_shows_ended_callback('normal','no shows running') # callback from ShowManager when all shows have ended def all_shows_ended_callback(self,reason,message): self.canvas.config(bg=self.starter_show['background-colour']) if reason in ('killed','error') or self.shutdown_required is True or self.exitpipresents_required is True or self.reboot_required is True: self.end(reason,message) def end(self,reason,message): self.mon.log(self,"Pi Presents ending with reason: " + reason) if self.root is not None: self.root.destroy() self.tidy_up() if reason == 'killed': if self.email_enabled is True and self.mailer.email_on_terminate is True: subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated' message = time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip self.send_email(reason,subject,message) self.mon.sched(self, None,"Pi Presents Terminated, au revoir\n") self.mon.log(self, "Pi Presents Terminated, au revoir") # close logging files self.mon.finish() print 'Uncollectable Garbage',gc.collect() # objgraph.show_backrefs(objgraph.by_type('Monitor')) sys.exit(101) elif reason == 'error': if self.email_enabled is True and self.mailer.email_on_error is True: subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error' message_text = 'Error message: '+ message + '\n'+ time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip self.send_email(reason,subject,message_text) self.mon.sched(self,None, "Pi Presents closing because of error, sorry\n") self.mon.log(self, "Pi Presents closing because of error, sorry") # close logging files self.mon.finish() print 'uncollectable garbage',gc.collect() sys.exit(102) else: self.mon.sched(self,None,"Pi Presents exiting normally, bye\n") self.mon.log(self,"Pi Presents exiting normally, bye") # close logging files self.mon.finish() if self.reboot_required is True: # print 'REBOOT' call (['sudo','reboot']) if self.shutdown_required is True: # print 'SHUTDOWN' call (['sudo','shutdown','now','SHUTTING DOWN']) print 'uncollectable garbage',gc.collect() sys.exit(100) # tidy up all the peripheral bits of Pi Presents def tidy_up(self): self.handle_monitor_command('on') self.mon.log(self, "Tidying Up") # turn screen blanking back on if self.options['noblank'] is True: call(["xset","s", "on"]) call(["xset","s", "+dpms"]) # tidy up animation if self.animate is not None: self.animate.terminate() # tidy up i/o plugins if self.ioplugin_manager != None: self.ioplugin_manager.terminate() if self.osc_enabled is True: self.oscdriver.terminate() # tidy up time of day scheduler if self.tod_enabled is True: self.tod.terminate() # ******************************* # Connecting to network and email # ******************************* def init_network(self): timeout=int(self.options['nonetwork']) if timeout== 0: self.network_connected=False self.unit='' self.ip='' self.interface='' return self.network=Network() self.network_connected=False # try to connect to network self.mon.log (self, 'Waiting up to '+ str(timeout) + ' seconds for network') success=self.network.wait_for_network(timeout) if success is False: self.mon.warn(self,'Failed to connect to network after ' + str(timeout) + ' seconds') # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock") return self.network_connected=True self.mon.sched (self, None,'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S")) self.mon.log (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S")) # Get web configuration self.network_details=False network_options_file_path=self.pp_dir+os.sep+'pp_config'+os.sep+'pp_web.cfg' if not os.path.exists(network_options_file_path): self.mon.warn(self,"pp_web.cfg not found at "+network_options_file_path) return self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path) self.network.read_config(network_options_file_path) self.unit=self.network.unit # get interface and IP details of preferred interface self.interface,self.ip = self.network.get_preferred_ip() if self.interface == '': self.network_connected=False return self.network_details=True self.mon.log (self, 'Network details ' + self.unit + ' ' + self.interface + ' ' +self.ip) def init_mailer(self): self.email_enabled=False email_file_path = self.pp_dir+os.sep+'pp_config'+os.sep+'pp_email.cfg' if not os.path.exists(email_file_path): self.mon.log(self,'pp_email.cfg not found at ' + email_file_path) return self.mon.log(self,'Found pp_email.cfg at ' + email_file_path) self.mailer=Mailer() self.mailer.read_config(email_file_path) # all Ok so can enable email if config file allows it. if self.mailer.email_allowed is True: self.email_enabled=True self.mon.log (self,'Email Enabled') def try_connect(self): tries=1 while True: success, error = self.mailer.connect() if success is True: return True else: self.mon.log(self,'Failed to connect to email SMTP server ' + str(tries) + '\n ' +str(error)) tries +=1 if tries >5: self.mon.log(self,'Failed to connect to email SMTP server after ' + str(tries)) return False def send_email(self,reason,subject,message): if self.try_connect() is False: return False else: success,error = self.mailer.send(subject,message) if success is False: self.mon.log(self, 'Failed to send email: ' + str(error)) success,error=self.mailer.disconnect() if success is False: self.mon.log(self,'Failed disconnect after send:' + str(error)) return False else: self.mon.log(self,'Sent email for ' + reason) success,error=self.mailer.disconnect() if success is False: self.mon.log(self,'Failed disconnect from email server ' + str(error)) return True