class OSCRemote(object): def __init__(self): self.editor_issue = "1.3" # get command options self.command_options = remote_options() # get directory holding the code self.pp_dir = sys.path[0] if not os.path.exists(self.pp_dir + os.sep + "pp_oscremote.py"): tkMessageBox.showwarning("Pi Presents", "Bad Application Directory") exit() # Initialise logging Monitor.log_path = self.pp_dir self.mon = Monitor() self.mon.init() Monitor.classes = ['OSCRemote', 'OSCConfig', 'OSCEditor'] Monitor.log_level = int(self.command_options['debug']) self.mon.log(self, "Pi Presents Remote is starting") self.mon.log(self, " OS and separator " + os.name + ' ' + os.sep) self.mon.log(self, "sys.path[0] - location of code: code " + sys.path[0]) self.setup_gui() # OSC config class self.osc_config = OSCConfig() self.init() #and start the system self.root.after(1000, self.run_app) self.root.mainloop() def init(self): self.osc_config_file = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_oscremote.cfg' self.read_create_osc() self.pp_home_dir = self.osc_config.pp_home_dir self.pp_profiles_offset = self.osc_config.pp_profiles_offset self.mon.log(self, "Data Home from options is " + self.pp_home_dir) self.mon.log( self, "Current Profiles Offset from options is " + self.pp_profiles_offset) self.pp_profile_dir = '' self.current_showlist = None self.current_show = None self.current_show_ref = '' self.shows_display.delete(0, END) self.results.set('') def add_status(self, text): self.status_display.insert(END, text + '\n') self.status_display.see(END) def run_app(self): self.client = None self.server = None self.st = None # initialise OSC variables self.prefix = '/pipresents' self.this_unit = '/' + self.osc_config.this_unit_name self.add_status('this unit is: ' + self.this_unit) self.controlled_unit = '/' + self.osc_config.controlled_unit_1_name self.add_status('controlled unit is: ' + self.controlled_unit) #connect client then start server to listen for replies self.init_client() self.add_status('connecting to controlled unit: ' + self.osc_config.controlled_unit_1_ip + ':' + self.osc_config.controlled_unit_1_port + ' ' + self.osc_config.controlled_unit_1_name) self.connect_client(self.osc_config.controlled_unit_1_ip, self.osc_config.controlled_unit_1_port) self.add_status('listening for replies on:' + self.osc_config.this_unit_ip + ':' + self.osc_config.this_unit_port) self.init_server(self.osc_config.this_unit_ip, self.osc_config.this_unit_port, self.client) self.add_initial_handlers() self.start_server() # *************************************** # RESPOND TO BUTTONS # *************************************** def open_show(self): self.msg_path = '/core/open ' self.msg_arg_text = self.current_show_ref self.display_msg_text() def close_show(self): self.msg_path = '/core/close ' self.msg_arg_text = self.current_show_ref self.display_msg_text() def exit_pipresents(self): self.msg_path = '/core/exitpipresents' self.msg_arg_text = '' self.display_msg_text() def play_event(self): self.msg_path = '/core/event' self.msg_arg_text = 'pp-play' self.display_msg_text() def pause_event(self): self.msg_path = '/core/event' self.msg_arg_text = 'pp-pause' self.display_msg_text() pass def stop_event(self): self.msg_path = '/core/event' self.msg_arg_text = 'pp-stop' self.display_msg_text() pass def up_event(self): self.msg_path = '/core/event' self.msg_arg_text = 'pp-up' self.display_msg_text() def down_event(self): self.msg_path = '/core/event' self.msg_arg_text = 'pp-down' self.display_msg_text() def output(self): self.msg_path = '/core/output' self.msg_arg_text = '' self.display_msg_text() def loopback(self): self.msg_path = '/system/loopback' self.msg_arg_text = '' self.display_msg_text() def server_info(self): self.msg_path = '/system/server-info' self.msg_arg_text = '' self.display_msg_text() # and put the created text in the results box in the gui def display_msg_text(self): self.results.set(self.prefix + self.controlled_unit + self.msg_path + ' ' + self.msg_arg_text) #calback from the Send button # parses the message string into fields and sends - NO error checking def send_message(self): msg_text = self.results.get() self.add_status('Send message:' + msg_text) self.mon.log(self, 'send message: ' + msg_text) fields = msg_text.split() address = fields[0] arg_list = fields[1:] self.send(address, arg_list) # *************************************** # OSC CLIENT TO SEND MESSAGES # *************************************** def init_client(self): self.client = OSC.OSCClient() def connect_client(self, ip, port): self.mon.log(self, 'connect to: ' + ip + ':' + str(port)) self.client.connect((ip, int(port))) def send(self, address, arg_list): msg = OSC.OSCMessage() msg.setAddress(address) for arg in arg_list: msg.append(arg) self.client.send(msg) def disconnect_client(self): self.client.close() return # *************************************** # OSC SERVER TO LISTEN TO REPLIES # *************************************** def init_server(self, ip, port_text, client): self.mon.log(self, 'Start Server: ' + ip + ':' + port_text) self.server = OSC.OSCServer((ip, int(port_text)), client) def start_server(self): self.st = threading.Thread(target=self.server.serve_forever) self.st.start() def close_server(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') def add_initial_handlers(self): self.server.addMsgHandler('default', self.no_match_handler) self.server.addMsgHandler( self.prefix + self.this_unit + "/system/loopback-reply", self.loopback_reply_handler) self.server.addMsgHandler( self.prefix + self.this_unit + "/system/server-info-reply", self.server_info_reply_handler) def no_match_handler(self, addr, tags, stuff, source): text = '' text += "no match for new osc msg from %s" % OSC.getUrlStr( source) + '\n' text += "with addr : %s" % addr + '\n' text += "typetags %s" % tags + '\n' text += "data %s" % stuff + '\n' self.add_status(text + '\n') def loopback_reply_handler(self, addr, tags, stuff, source): self.add_status('Loopback reply received from: ' + self.pretty_list(source)) def server_info_reply_handler(self, addr, tags, stuff, source): self.add_status('Server Information from: ' + self.pretty_list(source) + '\n ' + self.pretty_list(stuff)) def pretty_list(self, fields): text = ' ' for field in fields: text += str(field) + ' ' return text # *************************************** # INIT EXIT MISC # *************************************** def e_edit_osc(self): self.disconnect_client() self.close_server() self.edit_osc() self.init() self.add_status('\n\n\nRESTART') self.run_app() def app_exit(self): self.disconnect_client() self.close_server() if self.root is not None: self.root.destroy() self.mon.finish() sys.exit() def show_help(self): tkMessageBox.showinfo("Help", "Read 'manual.pdf'") def about(self): tkMessageBox.showinfo( "About", "Simple Remote Control for Pi Presents\n" + "Author: Ken Thompson" + "\nWebsite: http://pipresents.wordpress.com/") def setup_gui(self): # set up the gui # root is the Tkinter root widget self.root = Tk() self.root.title("Remote Control for Pi Presents") # self.root.configure(background='grey') self.root.resizable(False, False) # define response to main window closing self.root.protocol("WM_DELETE_WINDOW", self.app_exit) # bind some display fields self.filename = StringVar() self.display_show = StringVar() self.results = StringVar() self.status = StringVar() # define menu menubar = Menu(self.root) profilemenu = Menu(menubar, tearoff=0, bg="grey", fg="black") profilemenu.add_command(label='Select', command=self.open_existing_profile) menubar.add_cascade(label='Profile', menu=profilemenu) toolsmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Tools', menu=toolsmenu) osc_configmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='OSC', menu=osc_configmenu) osc_configmenu.add_command(label='Edit', command=self.e_edit_osc) helpmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Help', menu=helpmenu) helpmenu.add_command(label='Help', command=self.show_help) helpmenu.add_command(label='About', command=self.about) self.root.config(menu=menubar) #top frame top_frame = Frame(self.root, padx=5, pady=5) top_frame.pack(side=TOP) results_label = Label(top_frame, text="Message to Send", font="arial 12 bold") results_label.pack(side=LEFT) results_display = Entry(top_frame, textvariable=self.results, width=70) results_display.pack(side=LEFT, fill=BOTH, expand=1) send_button = Button(top_frame, width=5, height=1, text='Send', fg='black', command=self.send_message, bg="light grey") send_button.pack(side=RIGHT) #bottom frame bottom_frame = Frame(self.root, padx=5, pady=5) bottom_frame.pack(side=TOP, fill=BOTH, expand=1) left_frame = Frame(bottom_frame, padx=5) left_frame.pack(side=LEFT) right_frame = Frame(bottom_frame, padx=5, pady=5) right_frame.pack(side=LEFT) suplabel_frame = Frame(right_frame, pady=5) suplabel_frame.pack(side=TOP) commands_label = Label(suplabel_frame, text="Show Control", font="arial 12 bold") commands_label.pack() supervisor_frame = Frame(right_frame, pady=5) supervisor_frame.pack(side=TOP) # supervisor buttons add_button = Button(supervisor_frame, width=5, height=1, text='Open\nShow', fg='black', command=self.open_show, bg="light grey") add_button.pack(side=LEFT) add_button = Button(supervisor_frame, width=5, height=1, text='Close\nShow', fg='black', command=self.close_show, bg="light grey") add_button.pack(side=LEFT) add_button = Button(supervisor_frame, width=10, height=1, text='Exit\nPi Presents', fg='black', command=self.exit_pipresents, bg="light grey") add_button.pack(side=LEFT) # events buttons oplabel_frame = Frame(right_frame, pady=5) oplabel_frame.pack(side=TOP) operations_label = Label(oplabel_frame, text="Input Events", font="arial 12 bold") operations_label.pack() operations_frame = Frame(right_frame, pady=5) operations_frame.pack(side=TOP) add_button = Button(operations_frame, width=5, height=1, text='Play', fg='black', command=self.play_event, bg="light grey") add_button.pack(side=LEFT) add_button = Button(operations_frame, width=5, height=1, text='Pause', fg='black', command=self.pause_event, bg="light grey") add_button.pack(side=LEFT) add_button = Button(operations_frame, width=5, height=1, text='Stop', fg='black', command=self.stop_event, bg="light grey") add_button.pack(side=LEFT) add_button = Button(operations_frame, width=5, height=1, text='Up', fg='black', command=self.up_event, bg="light grey") add_button.pack(side=LEFT) add_button = Button(operations_frame, width=5, height=1, text='Down', fg='black', command=self.down_event, bg="light grey") add_button.pack(side=LEFT) # animate buttons animate_frame = Frame(right_frame, pady=5) animate_frame.pack(side=TOP) animate_label = Label(animate_frame, text="Control Outputs", font="arial 12 bold") animate_label.pack() animate_frame = Frame(right_frame, pady=5) animate_frame.pack(side=TOP) add_button = Button(animate_frame, width=5, height=1, text='Output', fg='black', command=self.output, bg="light grey") add_button.pack(side=LEFT) # system buttons systemlabel_frame = Frame(right_frame, pady=5) systemlabel_frame.pack(side=TOP) system_label = Label(systemlabel_frame, text="System", font="arial 12 bold") system_label.pack() system_frame = Frame(right_frame, pady=5) system_frame.pack(side=TOP) add_button = Button(system_frame, width=5, height=1, text='Loopback', fg='black', command=self.loopback, bg="light grey") add_button.pack(side=LEFT) add_button = Button(system_frame, width=10, height=1, text='Server Info', fg='black', command=self.server_info, bg="light grey") add_button.pack(side=LEFT) # define display of showlist shows_title_frame = Frame(left_frame) shows_title_frame.pack(side=TOP) shows_label = Label(shows_title_frame, text="Shows") shows_label.pack() shows_frame = Frame(left_frame) shows_frame.pack(side=TOP) scrollbar = Scrollbar(shows_frame, orient=VERTICAL) self.shows_display = Listbox(shows_frame, selectmode=SINGLE, height=12, width=40, bg="white", activestyle=NONE, fg="black", yscrollcommand=scrollbar.set) scrollbar.config(command=self.shows_display.yview) scrollbar.pack(side=RIGHT, fill=Y) self.shows_display.pack(side=LEFT, fill=BOTH, expand=1) self.shows_display.bind("<ButtonRelease-1>", self.e_select_show) # status_frame status_frame = Frame(self.root, padx=5, pady=5) status_frame.pack(side=TOP, fill=BOTH, expand=1) status_label = Label(status_frame, text="Status", font="arial 12 bold") status_label.pack(side=LEFT) scrollbar = Scrollbar(status_frame, orient=VERTICAL) self.status_display = Text(status_frame, height=10, yscrollcommand=scrollbar.set) scrollbar.config(command=self.status_display.yview) scrollbar.pack(side=RIGHT, fill=Y) self.status_display.pack(side=LEFT, fill=BOTH, expand=1) # *************************************** # SHOWLIST # *************************************** def open_existing_profile(self): initial_dir = self.pp_home_dir + os.sep + "pp_profiles" + self.pp_profiles_offset if os.path.exists(initial_dir) is False: self.mon.err( self, "Profiles directory not found: " + initial_dir + "\n\nHint: Data Home option must end in pp_home") return dir_path = tkFileDialog.askdirectory(initialdir=initial_dir) # dir_path="C:\Users\Ken\pp_home\pp_profiles\\ttt" if len(dir_path) > 0: self.open_profile(dir_path) def open_profile(self, dir_path): showlist_file = dir_path + os.sep + "pp_showlist.json" if os.path.exists(showlist_file) is False: self.mon.err( self, "Not a Profile: " + dir_path + "\n\nHint: Have you opened the profile directory?") return self.pp_profile_dir = dir_path self.root.title("Remote for Pi Presents - " + self.pp_profile_dir) self.open_showlist(self.pp_profile_dir) def open_showlist(self, profile_dir): showlist_file = profile_dir + os.sep + "pp_showlist.json" if os.path.exists(showlist_file) is False: self.mon.err( self, "showlist file not found at " + profile_dir + "\n\nHint: Have you opened the profile directory?") self.app_exit() self.current_showlist = ShowList() self.current_showlist.open_json(showlist_file) if float(self.current_showlist.sissue()) != float(self.editor_issue): self.mon.err( self, "Version of profile does not match Remote: " + self.editor_issue) self.app_exit() self.refresh_shows_display() def refresh_shows_display(self): self.shows_display.delete(0, self.shows_display.size()) for index in range(self.current_showlist.length()): self.shows_display.insert( END, self.current_showlist.show(index)['title'] + " [" + self.current_showlist.show(index)['show-ref'] + "]") if self.current_showlist.show_is_selected(): self.shows_display.itemconfig( self.current_showlist.selected_show_index(), fg='red') self.shows_display.see(self.current_showlist.selected_show_index()) def e_select_show(self, event): print 'select show', self.current_showlist.length() if self.current_showlist is not None and self.current_showlist.length( ) > 0: mouse_item_index = int(event.widget.curselection()[0]) self.current_showlist.select(mouse_item_index) self.current_show_ref = self.current_showlist.selected_show( )['show-ref'] self.refresh_shows_display() else: self.current_show_ref = '' # *************************************** # OSC CONFIGURATION # *************************************** def read_create_osc(self): if self.osc_config.read(self.osc_config_file) is False: self.osc_config.create(self.osc_config_file) eosc = OSCEditor(self.root, self.osc_config_file, 'remote', 'Create OSC Remote Configuration') self.osc_config.read(self.osc_config_file) def edit_osc(self): if self.osc_config.read(self.osc_config_file) is False: self.osc_config.create(self.osc_config_file) eosc = OSCEditor(self.root, self.osc_config_file, 'remote', 'Edit OSC Reomote Configuration')
class OSCMonitor(object): def __init__(self): # get command options self.command_options = remote_options() # get directory holding the code self.pp_dir = sys.path[0] if not os.path.exists(self.pp_dir + os.sep + "pipresents.py"): tkinter.messagebox.showwarning("Pi Presents", "Bad Application Directory") exit() # Initialise logging Monitor.log_path = self.pp_dir self.mon = Monitor() self.mon.init() Monitor.classes = ['OSCMonitor', 'OSCConfig', 'OSCEditor'] Monitor.log_level = int(self.command_options['debug']) self.mon.log(self, "Pi Presents Monitor is starting") self.mon.log(self, " OS and separator " + os.name + ' ' + os.sep) self.mon.log(self, "sys.path[0] - location of code: code " + sys.path[0]) self.root = Tk() # initialise OSC config class self.osc_config = OSCConfig() # read the options and allow their editing self.osc_config_file = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_oscmonitor.cfg' self.read_create_osc() if self.osc_config.slave_enabled != 'yes': self.mon.err(self, 'OSC Slave is not enabled in pp_oscmonitor.cfg') exit() #build gui self.setup_gui() # initialise self.init() #and start the system self.root.after(1000, self.run_app) self.root.mainloop() def init(self): self.desc.set('Listening for Commands from Master on: ' + self.osc_config.this_unit_ip + ':' + self.osc_config.listen_port) self.client = None self.server = None self.st = None def add_status(self, text): self.status_display.insert(END, text + '\n') self.status_display.see(END) def run_app(self): if self.osc_config.slave_enabled != 'yes': self.mon.err(self, 'Slave not enabled in oscmonitor.cfg') return if self.osc_config.this_unit_ip == '': self.mon.err(self, 'IP of own unit must be provided in oscmonitor.cfg') return if self.osc_config.listen_port == '': self.mon.err(self, 'Listen port must be provided in oscmonitor.cfg') return self.client = None self.server = None self.st = None # initialise OSC variables self.prefix = '/pipresents' self.this_unit = '/' + self.osc_config.this_unit_name self.add_status('this unit OSC address is: ' + self.this_unit) self.add_status('Listening for Commands from Master on: ' + self.osc_config.this_unit_ip + ':' + self.osc_config.listen_port) #connect client for replies then start server to listen for commands self.client = OSC.OSCClient() self.init_server(self.osc_config.this_unit_ip, self.osc_config.listen_port, self.client) self.add_initial_handlers() self.start_server() # *************************************** # OSC CLIENT TO SEND REPLIES # *************************************** def disconnect_client(self): if self.client != None: self.client.close() return # *************************************** # OSC SERVER TO LISTEN TO COMMANDS # *************************************** def init_server(self, ip, port_text, client): self.mon.log(self, 'Init Server: ' + ip + ':' + port_text) self.server = myOSCServer((ip, int(port_text)), client) def start_server(self): self.st = threading.Thread(target=self.server.serve_forever) self.st.start() def close_server(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') def add_initial_handlers(self): pass 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) def no_match_handler(self, addr, tags, stuff, source): text = "Message from %s" % OSC.getUrlStr(source) + '\n' text += " %s" % addr + self.pretty_list(stuff) self.add_status(text + '\n') def server_info_handler(self, addr, tags, stuff, source): # send a reply to the client. msg = OSC.OSCMessage(self.prefix + '/system/server-info-reply') msg.append(self.osc_config.this_unit_name) msg.append(self.server.getOSCAddressSpace()) text = "Message from %s" % OSC.getUrlStr(source) + '\n' text += " %s" % addr + self.pretty_list(stuff) self.add_status(text) self.add_status('Sent reply to Server Info request to %s:' % OSC.getUrlStr(source) + '\n') return msg def loopback_handler(self, addr, tags, stuff, source): # send a reply to the client. msg = OSC.OSCMessage(self.prefix + '/system/loopback-reply') text = "Message from %s" % OSC.getUrlStr(source) + '\n' text += " %s" % addr + self.pretty_list(stuff) self.add_status(text + '\n' + 'Sent reply to Loopback request to %s:' % OSC.getUrlStr(source) + '\n') return msg def pretty_list(self, fields): text = ' ' for field in fields: text += str(field) + ' ' return text # *************************************** # INIT EXIT MISC # *************************************** def e_edit_osc(self): self.disconnect_client() self.close_server() self.edit_osc() self.read_create_osc() self.init() self.add_status('\n\n\nRESTART') self.run_app() def app_exit(self): self.disconnect_client() self.close_server() if self.root is not None: self.root.destroy() self.mon.finish() sys.exit() def show_help(self): tkinter.messagebox.showinfo("Help", "Read 'manual.pdf'") def about(self): tkinter.messagebox.showinfo( "About", "Simple Remote Monitor for Pi Presents\n" + "Author: Ken Thompson" + "\nWebsite: http://pipresents.wordpress.com/") def setup_gui(self): # set up the gui # root is the Tkinter root widget self.root.title("Remote Monitor for Pi Presents") # self.root.configure(background='grey') self.root.resizable(False, False) # define response to main window closing self.root.protocol("WM_DELETE_WINDOW", self.app_exit) # bind some display fields self.desc = StringVar() self.filename = StringVar() self.display_show = StringVar() self.results = StringVar() self.status = StringVar() # define menu menubar = Menu(self.root) osc_configmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Options', menu=osc_configmenu) osc_configmenu.add_command(label='Edit', command=self.e_edit_osc) helpmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Help', menu=helpmenu) helpmenu.add_command(label='Help', command=self.show_help) helpmenu.add_command(label='About', command=self.about) self.root.config(menu=menubar) # info frame info_frame = Frame(self.root, padx=5, pady=5) info_frame.pack(side=TOP, fill=BOTH, expand=1) info_name = Label(info_frame, text="Slave Unit's Name: " + self.osc_config.this_unit_name, font="arial 12 bold") info_name.pack(side=TOP) info_this_address = Label(info_frame, textvariable=self.desc, font="arial 12 bold") info_this_address.pack(side=TOP) # status_frame status_frame = Frame(self.root, padx=5, pady=5) status_frame.pack(side=TOP, fill=BOTH, expand=1) status_label = Label(status_frame, text="Status:", font="arial 12 bold") status_label.pack(side=LEFT) scrollbar = Scrollbar(status_frame, orient=VERTICAL) self.status_display = Text(status_frame, height=20, yscrollcommand=scrollbar.set) scrollbar.config(command=self.status_display.yview) scrollbar.pack(side=RIGHT, fill=Y) self.status_display.pack(side=LEFT, fill=BOTH, expand=1) # *************************************** # OSC CONFIGURATION # *************************************** def read_create_osc(self): if self.osc_config.read(self.osc_config_file) is False: self.osc_config.create(self.osc_config_file, 'slave') eosc = OSCEditor(self.root, self.osc_config_file, 'slave', 'Create OSC Monitor Configuration') self.osc_config.read(self.osc_config_file) def edit_osc(self): if self.osc_config.read(self.osc_config_file) is False: self.osc_config.create(self.osc_config_file) eosc = OSCEditor(self.root, self.osc_config_file, 'slave', 'Edit OSC Monitor Configuration')
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: def __init__(self): self.pipresents_issue="1.2" self.nonfull_window_width = 0.6 # proportion of width self.nonfull_window_height= 0.6 # 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 #**************************************** # 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"): tkMessageBox.showwarning("Pi Presents","Bad Application Directory") exit() #Initialise logging Monitor.log_path=pp_dir self.mon=Monitor() self.mon.on() if self.options['debug']==True: Monitor.global_enable=True else: Monitor.global_enable=False self.mon.log (self, "Pi Presents is starting") self.mon.log (self," OS and separator:" + os.name +' ' + os.sep) self.mon.log(self,"sys.path[0] - location of code: "+sys.path[0]) # self.mon.log(self,"os.getenv('HOME') - user home directory (not used): " + os.getenv('HOME')) # self.mon.log(self,"os.path.expanduser('~') - user home directory: " + os.path.expanduser('~')) # optional other classes used self.ppio=None self.tod=None #get profile path from -p option if self.options['profile']<>"": self.pp_profile_path="/pp_profiles/"+self.options['profile'] else: self.pp_profile_path = "/pp_profiles/pp_profile" #get directory containing pp_home from the command, if self.options['home'] =="": home = os.path.expanduser('~')+ 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 # fall back to pipresents/pp_home self.pp_home=pp_dir+"/pp_home" 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==True: self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home) else: self.mon.log(self,"FAILED to find requested home directory, using default to display error message: " + self.pp_home) #check profile exists, if not default to error profile inside pipresents self.pp_profile=self.pp_home+self.pp_profile_path if os.path.exists(self.pp_profile): self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile) else: self.pp_profile=pp_dir+"/pp_home/pp_profiles/pp_profile" self.mon.log(self,"FAILED to find requested profile, using default to display error message: pp_profile") if self.options['verify']==True: val =Validator() if val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) == False: tkMessageBox.showwarning("Pi Presents","Validation Failed") exit() # open the resources self.rr=ResourceReader() # read the file, done once for all the other classes to use. if self.rr.read(pp_dir,self.pp_home,self.pp_profile)==False: self.end('error','cannot find resources.cfg') #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') # check profile and Pi Presents issues are compatible if float(self.showlist.sissue())<>float(self.pipresents_issue): self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not same as Pi Presents, must exit") self.end('error','wrong version of profile') # 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','start show not found') # ******************** # SET UP THE GUI # ******************** #turn off the screenblanking and saver if self.options['noblank']==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='black') # get size of the screen self.screen_width = self.root.winfo_screenwidth() self.screen_height = self.root.winfo_screenheight() # set window dimensions and decorations if self.options['fullscreen']==True: self.root.attributes('-fullscreen', True) os.system('unclutter &') self.window_width=self.screen_width self.window_height=self.screen_height 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') else: self.window_width=int(self.screen_width*self.nonfull_window_width) self.window_height=int(self.screen_height*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)) #canvas covers the whole 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.exit_pressed) #setup a canvas onto which will be drawn the images or text self.canvas = Canvas(self.root, bg='black') self.canvas.config(height=self.canvas_height, width=self.canvas_width, highlightthickness=0) # self.canvas.pack() self.canvas.place(x=0,y=0) self.canvas.focus_set() # **************************************** # INITIALISE THE INPUT DRIVERS # **************************************** # looks after bindings between symbolic names and internal operations controlsmanager=ControlsManager() if controlsmanager.read(pp_dir,self.pp_home,self.pp_profile)==False: self.end('error','cannot find or error in controls.cfg.cfg') else: controlsmanager.parse_defaults() # 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)==False: self.end('error','cannot find or error in keys.cfg') kbd.bind_keys(self.root,self.input_pressed) self.sr=ScreenDriver() # read the screen click area config file if self.sr.read(pp_dir,self.pp_home,self.pp_profile)==False: self.end('error','cannot find screen.cfg') # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes reason,message = self.sr.make_click_areas(self.canvas,self.input_pressed) if reason=='error': self.mon.err(self,message) self.end('error',message) # **************************************** # INITIALISE THE APPLICATION AND START # **************************************** self.shutdown_required=False #kick off GPIO if enabled by command line option if self.options['gpio']==True: from pp_gpio import PPIO # initialise the GPIO self.ppio=PPIO() # PPIO.gpio_enabled=False if self.ppio.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.gpio_pressed)==False: self.end('error','gpio error') # and start polling gpio self.ppio.poll() #kick off the time of day scheduler self.tod=TimeOfDay() self.tod.init(pp_dir,self.pp_home,self.canvas,500) self.tod.poll() # Create list of start shows initialise them and then run them self.run_start_shows() #start tkinter self.root.mainloop( ) # ********************* # RUN START SHOWS # ******************** def run_start_shows(self): #start show manager show_id=-1 #start show 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 so empty show register and set callback to terminate Pi Presents if all shows have ended. self.show_manager.init(self.all_shows_ended_callback) #parse the start shows field and start the initial shows start_shows_text=self.starter_show['start-show'] self.show_manager.start_initial_shows(start_shows_text) #callback from ShowManager when all shows have ended def all_shows_ended_callback(self,reason,message,force_shutdown): self.mon.log(self,"All shows ended, so terminate Pi Presents") if force_shutdown==True: self.shutdown_required=True self.mon.log(self,"shutdown forced by profile") self.terminate('killed') else: self.end(reason,message) # ********************* # User inputs # ******************** #gpio callback - symbol provided by gpio def gpio_pressed(self,index,symbol,edge): self.mon.log(self, "GPIO Pressed: "+ symbol) self.input_pressed(symbol,edge,'gpio') # all input events call this callback with a symbolic name. def input_pressed(self,symbol,edge,source): self.mon.log(self,"input received: "+symbol) if symbol=='pp-exit': self.exit_pressed() elif symbol=='pp-shutdown': self.shutdown_pressed('delay') elif symbol=='pp-shutdownnow': self.shutdown_pressed('now') else: for show in self.show_manager.shows: show_obj=show[ShowManager.SHOW_OBJ] if show_obj<>None: show_obj.input_pressed(symbol,edge,source) # ************************************** # respond to exit inputs by terminating # ************************************** def shutdown_pressed(self, when): if when=='delay': self.root.after(5000,self.on_shutdown_delay) else: self.shutdown_required=True self.exit_pressed() def on_shutdown_delay(self): if self.ppio.shutdown_pressed(): self.shutdown_required=True self.exit_pressed() def exit_pressed(self): self.mon.log(self, "kill received from user") #terminate any running shows and players self.mon.log(self,"kill sent to shows") self.terminate('killed') # kill or error def terminate(self,reason): needs_termination=False for show in self.show_manager.shows: if show[ShowManager.SHOW_OBJ]<>None: needs_termination=True self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF]) show[ShowManager.SHOW_OBJ].terminate(reason) if needs_termination==False: self.end(reason,'terminate - no termination of lower levels required') # ****************************** # Ending Pi Presents after all the showers and players are closed # ************************** def end(self,reason,message): self.mon.log(self,"Pi Presents ending with message: " + reason + ' ' + message) if reason=='error': self.tidy_up() self.mon.log(self, "exiting because of error") #close logging files self.mon.finish() exit() else: self.tidy_up() self.mon.log(self,"no error - exiting normally") #close logging files self.mon.finish() if self.shutdown_required==True: call(['sudo', 'shutdown', '-h', '-t 5','now']) exit() else: exit() # tidy up all the peripheral bits of Pi Presents def tidy_up(self): #turn screen blanking back on if self.options['noblank']==True: call(["xset","s", "on"]) call(["xset","s", "+dpms"]) # tidy up gpio if self.options['gpio']==True and self.ppio<>None: self.ppio.terminate() #tidy up time of day scheduler if self.tod<>None: self.tod.terminate() # ***************************** # utilitities # **************************** def resource(self,section,item): value=self.rr.get(section,item) if value==False: self.mon.err(self, "resource: "+section +': '+ item + " not found" ) self.terminate("error") else: return value
class PiPresents: def __init__(self): self.pipresents_issue="1.1" StopWatch.global_enable=False #**************************************** # INTERPRET COMMAND LINE # *************************************** self.options=command_options() pp_dir=sys.path[0] if not os.path.exists(pp_dir+"/pipresents.py"): tkMessageBox.showwarning("Pi Presents","Bad Application Directory") exit() #Initialise logging Monitor.log_path=pp_dir self.mon=Monitor() self.mon.on() if self.options['debug']==True: Monitor.global_enable=True else: Monitor.global_enable=False self.mon.log (self, "Pi Presents is starting") #self.show=None # create profile for pp_editor test files if already not there. if not os.path.exists(pp_dir+"/pp_home/pp_profiles/pp_editor"): self.mon.log(self,"Making pp_editor directory") os.makedirs(pp_dir+"/pp_home/pp_profiles/pp_editor") #profile path from -p option if self.options['profile']<>"": self.pp_profile_path="/pp_profiles/"+self.options['profile'] else: self.pp_profile_path = "/pp_profiles/pp_profile" #get directory containing pp_home from the command, if self.options['home'] =="": home = os.path.expanduser('~')+ os.sep+"pp_home" else: home = self.options['home'] + os.sep+ "pp_home" #check if pp_home exists. # try for 10 seconds to allow usb stick to automount # fall back to pipresents/pp_home self.pp_home=pp_dir+"/pp_home" for i in range (1, 10): self.mon.log(self,"Trying pp_home at: " + home + " " + str(i)) if os.path.exists(home): self.mon.log(self,"Using pp_home at: " + home) self.pp_home=home break time.sleep (1) #check profile exists, if not default to error profile inside pipresents self.pp_profile=self.pp_home+self.pp_profile_path if not os.path.exists(self.pp_profile): self.pp_profile=pp_dir+"/pp_home/pp_profiles/pp_profile" if self.options['verify']==True: val =Validator() if val.validate_profile(None,self.pp_home,self.pp_profile,self.pipresents_issue,False) == False: tkMessageBox.showwarning("Pi Presents","Validation Failed") exit() # open the resources self.rr=ResourceReader() # read the file, done once for all the other classes to use. self.rr.read(pp_dir,self.pp_home) #initialise the showlists and read the showlists 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') if float(self.showlist.sissue())<>float(self.pipresents_issue): self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not same as Pi Presents, must exit") self._end('error','wrong version of profile') # get the starter 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','start show not found') # ******************** # SET UP THE GUI # ******************** #turn off the screenblanking and saver if self.options['noblank']==True: call(["xset","s", "off"]) call(["xset","s", "-dpms"]) # control display of window decorations if self.options['fullscreen']<>"partial": self.root = Tk(className="fspipresents") os.system('unclutter &') else: self.root = Tk(className="pipresents") 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='black') # get size of the screen self.screen_width = self.root.winfo_screenwidth() self.screen_height = self.root.winfo_screenheight() # set window dimensions self.window_height=self.screen_height self.window_width=self.screen_width self.window_x=0 self.window_y=0 if self.options['fullscreen']<>"partial": bar=self.options['fullscreen'] # allow just 2 pixels for the hidden taskbar if bar in ('left','right'): self.window_width=self.screen_width-2 else: self.window_height=self.screen_height-2 if bar =="left": self.window_x=2 if bar =="top": self.window_y=2 self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y)) self.root.attributes('-zoomed','1') else: self.window_width=self.screen_width-200 self.window_height=self.screen_height-200 self.window_x=50 self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y)) #canvas covers the whole window self.canvas_height=self.window_height self.canvas_width=self.window_width # make sure focus is set. self.root.focus_set() #define response to main window closing. self.root.protocol ("WM_DELETE_WINDOW", self.on_break_key) # Always use CTRL-Break key to close the program as a get out of jail self.root.bind("<Break>",self.e_on_break_key) #pass all other keys along to 'shows' and hence to 'players' self.root.bind("<Escape>", self._escape_pressed) self.root.bind("<Up>", self._up_pressed) self.root.bind("<Down>", self._down_pressed) self.root.bind("<Return>", self._return_pressed) self.root.bind("<space>", self._pause_pressed) self.root.bind("p", self._pause_pressed) #setup a canvas onto which will be drawn the images or text self.canvas = Canvas(self.root, bg='black') self.canvas.config(height=self.canvas_height, width=self.canvas_width) #self.canvas.grid(row=1,columnspan=2) self.canvas.pack() # make sure focus is set on canvas. self.canvas.focus_set() # **************************************** # INITIALISE THE APPLICATION AND START # **************************************** self.shutdown_required=False #kick off GPIO if enabled by command line option if self.options['gpio']==True: from pp_buttons import Buttons # initialise the buttons connected to GPIO self.Buttons=Buttons self.buttons = Buttons(self.root,20,self.button_pressed) self.buttons.poll() # kick off the initial show self.show=None # get the start show from the showlist index = self.showlist.index_of_show(self.starter_show['start-show']) if index >=0: self.showlist.select(index) self.start_show=self.showlist.selected_show() else: self.mon.err(self,"Show not found in showlist: "+ self.starter_show['start-show']) self._end('error','show not found in showlist') if self.start_show['type']=="mediashow": self.show= MediaShow(self.start_show, self.canvas, self.showlist, self.pp_home, self.pp_profile) self.show.play(self._end_play_show,top=True,command='nil') self.root.mainloop( ) elif self.start_show['type']=="menu": self.show= MenuShow(self.start_show, self.canvas, self.showlist, self.pp_home, self.pp_profile) self.show.play(self._end_play_show,top=True,command='nil') self.root.mainloop( ) elif self.start_show['type']=="liveshow": self.show= LiveShow(self.start_show, self.canvas, self.showlist, self.pp_home, self.pp_profile) self.show.play(self._end_play_show,top=True,command='nil') self.root.mainloop( ) else: self.mon.err(self,"unknown mediashow type in start show - "+ self.start_show['type']) self._end('error','unknown mediashow type') def _end_play_show(self,reason,message): self.mon.log(self,"Returned to piresents with reason: " + reason +" and message " + message) self._end(reason,message) def _end(self,reason,message): self.mon.log(self,"Pi Presents ending with message: " + message) self.show=None if reason=='error': self.mon.log(self, "exiting because of error") self.tidy_up() exit() if reason=='killed': self.mon.log(self,"kill received - exiting") self.on_kill_callback() else: # should never be here or fatal error self.mon.log(self, "exiting because invalid end reasosn") self.tidy_up() exit() # ********************* # EXIT APP # ********************* # kill or error def terminate(self,reason): if self.shower<>None: self.mon.log(self,"sent terminate to shower") self.shower.terminate(reason) else: self._end(reason,message) def tidy_up(self): #turn screen blanking back on if self.options['noblank']==True: call(["xset","s", "on"]) call(["xset","s", "+dpms"]) if self.options['gpio']==True: self.buttons.kill() #close logging files self.mon.finish() def on_kill_callback(self): self.tidy_up() if self.shutdown_required==True: call(['sudo', 'shutdown', '-h', '-t 5','now']) else: exit() def resource(self,section,item): value=self.rr.get(section,item) if value==False: self.mon.err(self, "resource: "+section +': '+ item + " not found" ) self.terminate("error") else: return value # ********************* # Key and button presses # ******************** def shutdown_pressed(self): self.root.after(5000,self.on_shutdown_delay) def on_shutdown_delay(self): if self.buttons.is_pressed(self.Buttons.SHUTDOWN): self.shutdown_required=True self.on_break_key() def button_pressed(self,index,button,edge): self.mon.log(self, "Button Pressed: "+button) if button=="shutdown": self.shutdown_pressed() else: if self.show<>None: self.show.button_pressed(button,edge) # key presses def _escape_pressed(self,event): self._key_pressed("escape") def _up_pressed(self,event): self._key_pressed("up") def _down_pressed(self,event): self._key_pressed("down") def _return_pressed(self,event): self._key_pressed("return") def _pause_pressed(self,event): self._key_pressed("p") def _key_pressed(self,key_name): self.mon.log(self, "Key Pressed: "+ key_name) # if a show is running pass the key to it. if self.show<>None: self.show.key_pressed(key_name) def on_break_key(self): self.mon.log(self, "kill received from user") #terminate any running shows and players if self.show<>None: self.mon.log(self,"kill sent to show") self.show.terminate('killed') def e_on_break_key(self,event): self.on_break_key()
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 OSCMonitor(object): def __init__(self): self.editor_issue="1.3" # get command options self.command_options=remote_options() # get directory holding the code self.pp_dir=sys.path[0] if not os.path.exists(self.pp_dir+os.sep+"pipresents.py"): tkMessageBox.showwarning("Pi Presents","Bad Application Directory") exit() # Initialise logging Monitor.log_path=self.pp_dir self.mon=Monitor() self.mon.init() Monitor.classes = ['OSCMonitor','OSCConfig','OSCEditor'] Monitor.log_level = int(self.command_options['debug']) self.mon.log (self, "Pi Presents Monitor is starting") self.mon.log (self," OS and separator " + os.name +' ' + os.sep) self.mon.log(self,"sys.path[0] - location of code: code "+sys.path[0]) self.setup_gui() # initialise OSC config class self.osc_config=OSCConfig() self.init() #and start the system self.root.after(1000,self.run_app) self.root.mainloop() def init(self): # read the options and allow their editing self.osc_config_file = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_oscmonitor.cfg' self.read_create_osc() def add_status(self,text): self.status_display.insert(END,text+'\n') self.status_display.see(END) def run_app(self): self.client=None self.server=None self.st=None # initialise OSC variables self.prefix='/pipresents' self.this_unit='/' + self.osc_config.this_unit_name self.add_status('this unit is: '+self.this_unit) self.controlled_by_unit='/'+self.osc_config.controlled_by_name self.add_status('controlled by unit : '+self.controlled_by_unit) #connect client for replies then start server to listen for commands self.client = OSC.OSCClient() self.add_status('connecting to controlled by unit: '+self.osc_config.controlled_by_ip+':'+self.osc_config.controlled_by_port +' '+self.osc_config.controlled_by_name) self.client.connect((self.osc_config.controlled_by_ip,int(self.osc_config.controlled_by_port))) self.add_status('listening for commands on:'+self.osc_config.this_unit_ip+':'+self.osc_config.this_unit_port) self.init_server(self.osc_config.this_unit_ip,self.osc_config.this_unit_port,self.client) self.add_initial_handlers() self.start_server() # *************************************** # OSC CLIENT TO SEND REPLIES # *************************************** def disconnect_client(self): if self.client != None: self.client.close() return # *************************************** # OSC SERVER TO LISTEN TO COMMANDS # *************************************** def init_server(self,ip,port_text,client): self.add_status('Init Server: '+ip+':'+port_text) self.server = myOSCServer((ip,int(port_text)),client) def start_server(self): self.add_status('Start Server') self.st = threading.Thread( target = self.server.serve_forever ) self.st.start() def close_server(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') def add_initial_handlers(self): pass 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) def no_match_handler(self,addr, tags, stuff, source): text= "Message from %s" % OSC.getUrlStr(source)+'\n' text+= " %s" % addr+ self.pretty_list(stuff) self.add_status(text+'\n') def server_info_handler(self,addr, tags, stuff, source): msg = OSC.OSCMessage(self.prefix+self.controlled_by_unit+'/system/server-info-reply') msg.append('Unit: '+ self.osc_config.this_unit_name) self.add_status('Server Info Request from %s:' % OSC.getUrlStr(source)) return msg def loopback_handler(self,addr, tags, stuff, source): # send a reply to the client. msg = OSC.OSCMessage(self.prefix+self.controlled_by_unit+'/system/loopback-reply') self.add_status('Loopback Request from %s:' % OSC.getUrlStr(source)) return msg def pretty_list(self,fields): text=' ' for field in fields: text += str(field) + ' ' return text # *************************************** # INIT EXIT MISC # *************************************** def e_edit_osc(self): self.disconnect_client() self.close_server() self.edit_osc() self.init() self.add_status('\n\n\nRESTART') self.run_app() def app_exit(self): self.disconnect_client() self.close_server() if self.root is not None: self.root.destroy() self.mon.finish() sys.exit() def show_help (self): tkMessageBox.showinfo("Help","Read 'manual.pdf'") def about (self): tkMessageBox.showinfo("About","Simple Remote Monitor for Pi Presents\n" +"Author: Ken Thompson" +"\nWebsite: http://pipresents.wordpress.com/") def setup_gui(self): # set up the gui # root is the Tkinter root widget self.root = Tk() self.root.title("Remote Monitor for Pi Presents") # self.root.configure(background='grey') self.root.resizable(False,False) # define response to main window closing self.root.protocol ("WM_DELETE_WINDOW", self.app_exit) # bind some display fields self.filename = StringVar() self.display_show = StringVar() self.results = StringVar() self.status = StringVar() # define menu menubar = Menu(self.root) toolsmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Tools', menu = toolsmenu) osc_configmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Options', menu = osc_configmenu) osc_configmenu.add_command(label='Edit', command = self.e_edit_osc) helpmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Help', menu = helpmenu) helpmenu.add_command(label='Help', command = self.show_help) helpmenu.add_command(label='About', command = self.about) self.root.config(menu=menubar) # status_frame status_frame=Frame(self.root,padx=5,pady=5) status_frame.pack(side=TOP, fill=BOTH, expand=1) status_label = Label(status_frame, text="Status",font="arial 12 bold") status_label.pack(side=LEFT) scrollbar = Scrollbar(status_frame, orient=VERTICAL) self.status_display=Text(status_frame,height=10, yscrollcommand=scrollbar.set) scrollbar.config(command=self.status_display.yview) scrollbar.pack(side=RIGHT, fill=Y) self.status_display.pack(side=LEFT,fill=BOTH, expand=1) # *************************************** # OSC CONFIGURATION # *************************************** def read_create_osc(self): if self.osc_config.read(self.osc_config_file) is False: self.osc_config.create(self.osc_config_file) eosc = OSCEditor(self.root, self.osc_config_file,'slave','Create OSC Monitor Configuration') self.osc_config.read(self.osc_config_file) def edit_osc(self): if self.osc_config.read(self.osc_config_file) is False: self.osc_config.create(self.osc_config_file) eosc = OSCEditor(self.root, self.osc_config_file,'slave','Edit OSC Monitor Configuration')
class PiPresents: # Constants for list of start shows SHOW_TEMPLATE=['',None,-1] NAME = 0 # text name of the show SHOW = 1 # the show object ID = 2 # Numeic identity of the show object, sent to the instance and returned in callbacks def __init__(self): self.pipresents_issue="1.2" StopWatch.global_enable=False #**************************************** # INTERPRET COMMAND LINE # *************************************** self.options=command_options() pp_dir=sys.path[0] if not os.path.exists(pp_dir+"/pipresents.py"): tkMessageBox.showwarning("Pi Presents","Bad Application Directory") exit() #Initialise logging Monitor.log_path=pp_dir self.mon=Monitor() self.mon.on() if self.options['debug']==True: Monitor.global_enable=True else: Monitor.global_enable=False self.mon.log (self, "Pi Presents is starting") self.mon.log (self," OS and separator:" + os.name +' ' + os.sep) self.mon.log(self,"sys.path[0] - location of code: "+sys.path[0]) # self.mon.log(self,"os.getenv('HOME') - user home directory (not used): " + os.getenv('HOME')) # self.mon.log(self,"os.path.expanduser('~') - user home directory: " + os.path.expanduser('~')) self.ppio=None self.tod=None # create profile for pp_editor test files if already not there. if not os.path.exists(pp_dir+"/pp_home/pp_profiles/pp_editor"): self.mon.log(self,"Making pp_editor directory") os.makedirs(pp_dir+"/pp_home/pp_profiles/pp_editor") #profile path from -p option if self.options['profile']<>"": self.pp_profile_path="/pp_profiles/"+self.options['profile'] else: self.pp_profile_path = "/pp_profiles/pp_profile" #get directory containing pp_home from the command, if self.options['home'] =="": home = os.path.expanduser('~')+ 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 # fall back to pipresents/pp_home self.pp_home=pp_dir+"/pp_home" for i in range (1, 10): self.mon.log(self,"Trying pp_home at: " + home + " (" + str(i)+')') if os.path.exists(home): self.mon.log(self,"Using pp_home at: " + home) self.pp_home=home break time.sleep (1) #check profile exists, if not default to error profile inside pipresents self.pp_profile=self.pp_home+self.pp_profile_path if not os.path.exists(self.pp_profile): self.pp_profile=pp_dir+"/pp_home/pp_profiles/pp_profile" if self.options['verify']==True: val =Validator() if val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) == False: tkMessageBox.showwarning("Pi Presents","Validation Failed") exit() # open the resources self.rr=ResourceReader() # read the file, done once for all the other classes to use. if self.rr.read(pp_dir,self.pp_home)==False: #self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not same as Pi Presents, must exit") self._end('error','cannot find resources.cfg') #initialise the showlists and read the showlists 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') if float(self.showlist.sissue())<>float(self.pipresents_issue): self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not same as Pi Presents, must exit") self._end('error','wrong version of profile') # 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','start show not found') # ******************** # SET UP THE GUI # ******************** #turn off the screenblanking and saver if self.options['noblank']==True: call(["xset","s", "off"]) call(["xset","s", "-dpms"]) self.root=Tk() # control display of window decorations if self.options['fullscreen']==True: self.root.attributes('-fullscreen', True) #self.root = Tk(className="fspipresents") os.system('unclutter &') else: #self.root = Tk(className="pipresents") pass 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='black') # get size of the screen self.screen_width = self.root.winfo_screenwidth() self.screen_height = self.root.winfo_screenheight() # set window dimensions self.window_height=self.screen_height self.window_width=self.screen_width self.window_x=0 self.window_y=0 if self.options['fullscreen']==True: bar=self.options['fullscreen'] # allow just 2 pixels for the hidden taskbar - not any more if bar in ('left','right'): self.window_width=self.screen_width else: self.window_height=self.screen_height if bar =="left": self.window_x=0 if bar =="top": 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') else: self.window_width=self.screen_width-600 self.window_height=self.screen_height-200 self.window_x=50 self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y)) #canvas covers the whole window self.canvas_height=self.window_height self.canvas_width=self.window_width # make sure focus is set. self.root.focus_set() #define response to main window closing. self.root.protocol ("WM_DELETE_WINDOW", self.on_break_key) # Always use CTRL-Break key to close the program as a get out of jail self.root.bind("<Break>",self.e_on_break_key) #pass all other keys along to start shows and hence to 'players' self.root.bind("<Escape>", self._escape_pressed) self.root.bind("<Up>", self._up_pressed) self.root.bind("<Down>", self._down_pressed) self.root.bind("<Return>", self._return_pressed) self.root.bind("<space>", self._pause_pressed) self.root.bind("p", self._pause_pressed) #setup a canvas onto which will be drawn the images or text self.canvas = Canvas(self.root, bg='black') self.canvas.config(height=self.canvas_height, width=self.canvas_width) self.canvas.pack() # make sure focus is set on canvas. self.canvas.focus_set() # **************************************** # INITIALISE THE APPLICATION AND START # **************************************** self.shutdown_required=False #kick off GPIO if enabled by command line option if self.options['gpio']==True: from pp_gpio import PPIO # initialise the GPIO self.ppio=PPIO() # PPIO.gpio_enabled=False if self.ppio.init(pp_dir,self.pp_profile,self.canvas,50,self.button_pressed)==False: self._end('error','gpio error') # and start polling gpio self.ppio.poll() #kick off the time of day scheduler self.tod=TimeOfDay() self.tod.init(pp_dir,self.pp_home,self.canvas,500) self.tod.poll() # Create list of start shows initialise them and then run them self.start_shows = self.create_start_show_list() self.init_shows() self.run_shows() self.root.mainloop( ) # ********************* # EXIT APP # ********************* # kill or error def terminate(self,reason): needs_termination=False for start_show in self.start_shows: if start_show[PiPresents.SHOW]<>None: needs_termination=True self.mon.log(self,"Sent terminate to show "+ start_show[PiPresents.NAME]) start_show[PiPresents.SHOW].terminate(reason) if needs_termination==False: self._end(reason) def tidy_up(self): #turn screen blanking back on if self.options['noblank']==True: call(["xset","s", "on"]) call(["xset","s", "+dpms"]) # tidy up gpio if self.options['gpio']==True and self.ppio<>None: self.ppio.terminate() #tidy up time of day scheduler if self.tod<>None: self.tod.terminate() #close logging files self.mon.finish() def on_kill_callback(self): self.tidy_up() if self.shutdown_required==True: call(['sudo', 'shutdown', '-h', '-t 5','now']) else: exit() def resource(self,section,item): value=self.rr.get(section,item) if value==False: self.mon.err(self, "resource: "+section +': '+ item + " not found" ) self.terminate("error") else: return value # ********************* # Key and button presses # ******************** def shutdown_pressed(self): self.root.after(5000,self.on_shutdown_delay) def on_shutdown_delay(self): if self.ppio.is_pressed('shutdown'): self.shutdown_required=True self.on_break_key() def button_pressed(self,index,button,edge): self.mon.log(self, "Button Pressed: "+button) if button=="shutdown": self.shutdown_pressed() else: for start_show in self.start_shows: print "sending to show" , start_show[PiPresents.NAME] start_show[PiPresents.SHOW].button_pressed(button,edge) # key presses - convert from events to call to _key_pressed def _escape_pressed(self,event): self._key_pressed("escape") def _up_pressed(self,event): self._key_pressed("up") def _down_pressed(self,event): self._key_pressed("down") def _return_pressed(self,event): self._key_pressed("return") def _pause_pressed(self,event): self._key_pressed("p") def _key_pressed(self,key_name): # key pressses are sent only to the controlled show. self.mon.log(self, "Key Pressed: "+ key_name) for start_show in self.start_shows: start_show[PiPresents.SHOW].key_pressed(key_name) def on_break_key(self): self.mon.log(self, "kill received from user") #terminate any running shows and players self.mon.log(self,"kill sent to shows") self.terminate('killed') def e_on_break_key(self,event): self.on_break_key() # ********************* # Start show creation and running and return from # ******************** # Extract shows from start show def create_start_show_list(self): start_shows_text=self.starter_show['start-show'] shows=[] index=0 fields= start_shows_text.split() for field in fields: show = PiPresents.SHOW_TEMPLATE show[PiPresents.NAME]=field show[PiPresents.ID]=index shows.append(copy.deepcopy(show)) index+=1 return shows def init_shows(self): # build list of shows to run by instantiating their classes. for start_show in self.start_shows: index = self.showlist.index_of_show(start_show[PiPresents.NAME]) if index >=0: self.showlist.select(index) show=self.showlist.selected_show() else: self.mon.err(self,"Show not found in showlist: "+ start_show[PiPresents.NAME]) self._end('error','show not found in showlist') if show['type']=="mediashow": show_obj = MediaShow(show, self.canvas, self.showlist, self.pp_home, self.pp_profile) start_show[PiPresents.SHOW]=show_obj elif show['type']=="menu": show_obj = MenuShow(show, self.canvas, self.showlist, self.pp_home, self.pp_profile) start_show[PiPresents.SHOW]=show_obj elif show['type']=="liveshow": show_obj= LiveShow(show, self.canvas, self.showlist, self.pp_home, self.pp_profile) start_show[PiPresents.SHOW]=show_obj else: self.mon.err(self,"unknown mediashow type in start show - "+ show['type']) self._end('error','unknown mediashow type') # run each of the shows in the list def run_shows(self): for start_show in self.start_shows: show_obj = start_show[PiPresents.SHOW] show_obj.play(start_show[PiPresents.ID],self._end_play_show,top=True,command='nil') def _end_play_show(self,show_id,reason,message): self.mon.log(self,"Show " + str(show_id) + " returned to Pipresents with reason: " + reason ) self.start_shows[show_id][PiPresents.SHOW]=None # if all the shows have ended then end Pi Presents all_terminated=True for start_show in self.start_shows: if start_show[PiPresents.SHOW]<>None: all_terminated=False if all_terminated==True: self._end(reason,message) def _end(self,reason,message): self.mon.log(self,"Pi Presents ending with message: " + message) if reason=='error': self.mon.log(self, "exiting because of error") self.tidy_up() exit() if reason=='killed': self.mon.log(self,"kill received - exiting") self.on_kill_callback() else: # should never be here or fatal error self.mon.log(self, "exiting because invalid end reasosn") self.tidy_up() exit()
class PiPresents(object): def __init__(self): gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL) self.pipresents_issue="1.3" self.pipresents_minorissue = '1.3.1g' # 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 self.pp_background='black' 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:\n{0}".format(pp_dir)) exit(103) # 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', 'pp_paths', 'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow', 'PathManager','ControlsManager','ShowManager','PluginManager', 'MplayerDriver','OMXDriver','UZBLDriver', 'KbdDriver','GPIODriver','TimeOfDay','ScreenDriver','Animate','OSCDriver' ] # Monitor.classes=['PiPresents','ArtMediaShow','VideoPlayer','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.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue) # self.mon.log (self," OS and separator:" + os.name +' ' + os.sep) self.mon.log(self,"sys.path[0] - location of code: "+sys.path[0]) if os.geteuid() !=0: user=os.getenv('USER') else: user = os.getenv('SUDO_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 # 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 # get home path from -o option self.pp_home = pp_paths.get_home(self.options['home']) if self.pp_home is None: self.end('error','Failed to find pp_home') # get profile path from -p option # pp_profile is the full path to the directory that contains # pp_showlist.json and other files for the profile self.pp_profile = pp_paths.get_profile_dir(self.pp_home, self.options['profile']) if self.pp_profile is None: self.end('error','Failed to find profile') # check profile exists if os.path.exists(self.pp_profile): 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 profile') self.mon.start_stats(self.options['profile']) # check 'verify' option 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') # check profile and Pi Presents issues are compatible if float(self.showlist.sissue()) != float(self.pipresents_issue): self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not same as Pi Presents, must exit") self.end('error','wrong version of profile') # 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','start show not found') if self.starter_show['start-show']=='': self.mon.warn(self,"No Start Shows in Start Show") # ******************** # 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.pp_background) self.mon.log(self, 'native screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixcels') 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, 'commanded screen dimensions are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixcels') # 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 1>&- 2>&- &') # Suppress 'someone created a subwindow' complaints from 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') # canvs 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.pp_background) 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 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.exitpipresents_required=False # 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 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.end('error',message) else: self.osc_enabled=True self.root.after(1000,self.oscdriver.start_server()) # and run the start shows self.run_start_shows() # set up the time of day scheduler including catchup self.tod_enabled=False if os.path.exists(self.pp_profile + os.sep + 'schedule.json'): # kick off the time of day scheduler which may run additional shows self.tod=TimeOfDay() self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.root,self.handle_command) self.tod_enabled = True # then start the time of day scheduler if self.tod_enabled is True: 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 --fullscreen comand option',0,0 elif fields[0].isdigit() is False or fields[1].isdigit() is False: return 'error','dimensions are not positive integers in ---fullscreen',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): 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'): if self.shutdown_required is False: reason,message=self.show_manager.control_a_show(show_ref,show_command) else: 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 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") 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.pp_background) 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': self.mon.log(self, "Pi Presents Aborted, au revoir") # close logging files self.mon.finish() sys.exit(101) elif reason == 'error': self.mon.log(self, "Pi Presents closing because of error, sorry") # close logging files self.mon.finish() sys.exit(102) else: 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', '-h', '-t 5','now']) sys.exit(100) else: sys.exit(100) # tidy up all the peripheral bits of Pi Presents def tidy_up(self): 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 PiPresents: def __init__(self): self.pipresents_issue = "1.2" self.pipresents_minorissue = '1.2.3f' self.nonfull_window_width = 0.5 # proportion of width self.nonfull_window_height = 0.6 # 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 #**************************************** # 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"): tkMessageBox.showwarning("Pi Presents", "Bad Application Directory") exit() #Initialise logging Monitor.log_path = pp_dir self.mon = Monitor() self.mon.on() # 0 - errors only # 1 - errors and warnings # 2 - everything if self.options['debug'] == True: Monitor.global_enable = 2 else: Monitor.global_enable = 0 # UNCOMMENT THIS TO LOG WARNINGS AND ERRORS ONLY # Monitor.global_enable=1 self.mon.log( self, "\n\n\n\n\n*****************\nPi Presents is starting, Version:" + self.pipresents_minorissue) self.mon.log(self, "Version: " + self.pipresents_minorissue) self.mon.log(self, " OS and separator:" + os.name + ' ' + os.sep) self.mon.log(self, "sys.path[0] - location of code: " + sys.path[0]) # self.mon.log(self,"os.getenv('HOME') - user home directory (not used): " + os.getenv('HOME')) # self.mon.log(self,"os.path.expanduser('~') - user home directory: " + os.path.expanduser('~')) # optional other classes used self.ppio = None self.tod = None #get profile path from -p option if self.options['profile'] <> "": self.pp_profile_path = "/pp_profiles/" + self.options['profile'] else: self.pp_profile_path = "/pp_profiles/pp_profile" #get directory containing pp_home from the command, if self.options['home'] == "": home = os.path.expanduser('~') + 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 # fall back to pipresents/pp_home self.pp_home = pp_dir + "/pp_home" 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 == True: self.mon.log( self, "Found Requested Home Directory, using pp_home at: " + home) else: self.mon.log( self, "FAILED to find requested home directory, using default to display error message: " + self.pp_home) #check profile exists, if not default to error profile inside pipresents self.pp_profile = self.pp_home + self.pp_profile_path if os.path.exists(self.pp_profile): self.mon.log( self, "Found Requested profile - pp_profile directory is: " + self.pp_profile) else: self.pp_profile = pp_dir + "/pp_home/pp_profiles/pp_profile" self.mon.log( self, "FAILED to find requested profile, using default to display error message: pp_profile" ) if self.options['verify'] == True: val = Validator() if val.validate_profile(None, pp_dir, self.pp_home, self.pp_profile, self.pipresents_issue, False) == False: tkMessageBox.showwarning("Pi Presents", "Validation Failed") exit() # open the resources self.rr = ResourceReader() # read the file, done once for all the other classes to use. if self.rr.read(pp_dir, self.pp_home, self.pp_profile) == False: self.end('error', 'cannot find resources.cfg') #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') # check profile and Pi Presents issues are compatible if float(self.showlist.sissue()) <> float(self.pipresents_issue): self.mon.err( self, "Version of profile " + self.showlist.sissue() + " is not same as Pi Presents, must exit") self.end('error', 'wrong version of profile') # 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', 'start show not found') # ******************** # SET UP THE GUI # ******************** #turn off the screenblanking and saver if self.options['noblank'] == 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='black') # get size of the screen self.screen_width = self.root.winfo_screenwidth() self.screen_height = self.root.winfo_screenheight() # set window dimensions and decorations if self.options['fullscreen'] == True: self.root.attributes('-fullscreen', True) os.system('unclutter &') self.window_width = self.screen_width self.window_height = self.screen_height 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') else: self.window_width = int(self.screen_width * self.nonfull_window_width) self.window_height = int(self.screen_height * 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)) #canvas covers the whole 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.exit_pressed) #setup a canvas onto which will be drawn the images or text self.canvas = Canvas(self.root, bg='black') self.canvas.config(height=self.canvas_height, width=self.canvas_width, highlightthickness=0) # self.canvas.pack() self.canvas.place(x=0, y=0) self.canvas.focus_set() # **************************************** # INITIALISE THE INPUT DRIVERS # **************************************** # looks after bindings between symbolic names and internal operations controlsmanager = ControlsManager() if controlsmanager.read(pp_dir, self.pp_home, self.pp_profile) == False: self.end('error', 'cannot find or error in controls.cfg.cfg') else: controlsmanager.parse_defaults() # 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) == False: self.end('error', 'cannot find or error in keys.cfg') kbd.bind_keys(self.root, self.input_pressed) self.sr = ScreenDriver() # read the screen click area config file if self.sr.read(pp_dir, self.pp_home, self.pp_profile) == False: self.end('error', 'cannot find screen.cfg') # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes reason, message = self.sr.make_click_areas(self.canvas, self.input_pressed) if reason == 'error': self.mon.err(self, message) self.end('error', message) # **************************************** # INITIALISE THE APPLICATION AND START # **************************************** self.shutdown_required = False #kick off GPIO if enabled by command line option if self.options['gpio'] == True: from pp_gpio import PPIO # initialise the GPIO self.ppio = PPIO() # PPIO.gpio_enabled=False if self.ppio.init(pp_dir, self.pp_home, self.pp_profile, self.canvas, 50, self.gpio_pressed) == False: self.end('error', 'gpio error') # and start polling gpio self.ppio.poll() #kick off the time of day scheduler self.tod = TimeOfDay() self.tod.init(pp_dir, self.pp_home, self.canvas, 500) self.tod.poll() # Create list of start shows initialise them and then run them self.run_start_shows() #start tkinter self.root.mainloop() # ********************* # RUN START SHOWS # ******************** def run_start_shows(self): #start show manager show_id = -1 #start show 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 so empty show register and set callback to terminate Pi Presents if all shows have ended. self.show_manager.init(self.all_shows_ended_callback) #parse the start shows field and start the initial shows start_shows_text = self.starter_show['start-show'] self.show_manager.start_initial_shows(start_shows_text) #callback from ShowManager when all shows have ended def all_shows_ended_callback(self, reason, message, force_shutdown): self.mon.log(self, "All shows ended, so terminate Pi Presents") if force_shutdown == True: self.shutdown_required = True self.mon.log(self, "shutdown forced by profile") self.terminate('killed') else: self.end(reason, message) # ********************* # User inputs # ******************** #gpio callback - symbol provided by gpio def gpio_pressed(self, index, symbol, edge): self.mon.log(self, "GPIO Pressed: " + symbol) self.input_pressed(symbol, edge, 'gpio') # all input events call this callback with a symbolic name. def input_pressed(self, symbol, edge, source): self.mon.log(self, "input received: " + symbol) if symbol == 'pp-exit': self.exit_pressed() elif symbol == 'pp-shutdown': self.shutdown_pressed('delay') elif symbol == 'pp-shutdownnow': self.shutdown_pressed('now') else: for show in self.show_manager.shows: show_obj = show[ShowManager.SHOW_OBJ] if show_obj <> None: show_obj.input_pressed(symbol, edge, source) # ************************************** # respond to exit inputs by terminating # ************************************** def shutdown_pressed(self, when): if when == 'delay': self.root.after(5000, self.on_shutdown_delay) else: self.shutdown_required = True self.exit_pressed() def on_shutdown_delay(self): if self.ppio.shutdown_pressed(): self.shutdown_required = True self.exit_pressed() def exit_pressed(self): self.mon.log(self, "kill received from user") #terminate any running shows and players self.mon.log(self, "kill sent to shows") self.terminate('killed') # kill or error def terminate(self, reason): needs_termination = False for show in self.show_manager.shows: if show[ShowManager.SHOW_OBJ] <> None: needs_termination = True self.mon.log( self, "Sent terminate to show " + show[ShowManager.SHOW_REF]) show[ShowManager.SHOW_OBJ].terminate(reason) if needs_termination == False: self.end(reason, 'terminate - no termination of lower levels required') # ****************************** # Ending Pi Presents after all the showers and players are closed # ************************** def end(self, reason, message): self.mon.log( self, "Pi Presents ending with message: " + reason + ' ' + message) if reason == 'error': self.tidy_up() self.mon.log(self, "exiting because of error") #close logging files self.mon.finish() exit() else: self.tidy_up() self.mon.log(self, "no error - exiting normally") #close logging files self.mon.finish() if self.shutdown_required == True: # call(['sudo', 'shutdown', '-h', '-t 5','now']) call(['sudo', 'shutdown', '-h', 'now']) exit() else: exit() # tidy up all the peripheral bits of Pi Presents def tidy_up(self): #turn screen blanking back on if self.options['noblank'] == True: call(["xset", "s", "on"]) call(["xset", "s", "+dpms"]) # tidy up gpio if self.options['gpio'] == True and self.ppio <> None: self.ppio.terminate() #tidy up time of day scheduler if self.tod <> None: self.tod.terminate() # ***************************** # utilitities # **************************** def resource(self, section, item): value = self.rr.get(section, item) if value == False: self.mon.err(self, "resource: " + section + ': ' + item + " not found") self.terminate("error") else: return value
class PiPresents(object): def __init__(self): gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_INSTANCES | gc.DEBUG_OBJECTS | gc.DEBUG_SAVEALL) self.pipresents_issue = "1.3" self.pipresents_minorissue = '1.3.1g' # 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 self.pp_background = 'black' 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:\n{0}".format(pp_dir)) exit(103) # 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', 'pp_paths', 'HyperlinkShow', 'RadioButtonShow', 'ArtLiveShow', 'ArtMediaShow', 'MediaShow', 'LiveShow', 'MenuShow', 'PathManager', 'ControlsManager', 'ShowManager', 'PluginManager', 'MplayerDriver', 'OMXDriver', 'UZBLDriver', 'KbdDriver', 'GPIODriver', 'TimeOfDay', 'ScreenDriver', 'Animate', 'OSCDriver' ] # Monitor.classes=['PiPresents','ArtMediaShow','VideoPlayer','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.log( self, "Pi Presents is starting, Version:" + self.pipresents_minorissue) # self.mon.log (self," OS and separator:" + os.name +' ' + os.sep) self.mon.log(self, "sys.path[0] - location of code: " + sys.path[0]) if os.geteuid() != 0: user = os.getenv('USER') else: user = os.getenv('SUDO_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 # 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 # get home path from -o option self.pp_home = pp_paths.get_home(self.options['home']) if self.pp_home is None: self.end('error', 'Failed to find pp_home') # get profile path from -p option # pp_profile is the full path to the directory that contains # pp_showlist.json and other files for the profile self.pp_profile = pp_paths.get_profile_dir(self.pp_home, self.options['profile']) if self.pp_profile is None: self.end('error', 'Failed to find profile') # check profile exists if os.path.exists(self.pp_profile): 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 profile') self.mon.start_stats(self.options['profile']) # check 'verify' option 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') # check profile and Pi Presents issues are compatible if float(self.showlist.sissue()) != float(self.pipresents_issue): self.mon.err( self, "Version of profile " + self.showlist.sissue() + " is not same as Pi Presents, must exit") self.end('error', 'wrong version of profile') # 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', 'start show not found') if self.starter_show['start-show'] == '': self.mon.warn(self, "No Start Shows in Start Show") # ******************** # 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.pp_background) self.mon.log( self, 'native screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixcels') 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, 'commanded screen dimensions are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixcels') # 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 1>&- 2>&- &' ) # Suppress 'someone created a subwindow' complaints from 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') # canvs 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.pp_background) 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 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.exitpipresents_required = False # 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 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.end('error', message) else: self.osc_enabled = True self.root.after(1000, self.oscdriver.start_server()) # and run the start shows self.run_start_shows() # set up the time of day scheduler including catchup self.tod_enabled = False if os.path.exists(self.pp_profile + os.sep + 'schedule.json'): # kick off the time of day scheduler which may run additional shows self.tod = TimeOfDay() self.tod.init(pp_dir, self.pp_home, self.pp_profile, self.root, self.handle_command) self.tod_enabled = True # then start the time of day scheduler if self.tod_enabled is True: 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 --fullscreen comand option', 0, 0 elif fields[0].isdigit() is False or fields[1].isdigit() is False: return 'error', 'dimensions are not positive integers in ---fullscreen', 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): 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'): if self.shutdown_required is False: reason, message = self.show_manager.control_a_show( show_ref, show_command) else: 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 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") 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.pp_background) 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': self.mon.log(self, "Pi Presents Aborted, au revoir") # close logging files self.mon.finish() sys.exit(101) elif reason == 'error': self.mon.log(self, "Pi Presents closing because of error, sorry") # close logging files self.mon.finish() sys.exit(102) else: 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', '-h', '-t 5', 'now']) sys.exit(100) else: sys.exit(100) # tidy up all the peripheral bits of Pi Presents def tidy_up(self): 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 OSCRemote(object): def __init__(self): self.editor_issue="1.3" # get command options self.command_options=remote_options() # get directory holding the code self.pp_dir=sys.path[0] if not os.path.exists(self.pp_dir+os.sep+"pp_oscremote.py"): tkMessageBox.showwarning("Pi Presents","Bad Application Directory") exit() # Initialise logging Monitor.log_path=self.pp_dir self.mon=Monitor() self.mon.init() Monitor.classes = ['OSCRemote','OSCConfig','OSCEditor'] Monitor.log_level = int(self.command_options['debug']) self.mon.log (self, "Pi Presents Remote is starting") self.mon.log (self," OS and separator " + os.name +' ' + os.sep) self.mon.log(self,"sys.path[0] - location of code: code "+sys.path[0]) self.setup_gui() # OSC config class self.osc_config=OSCConfig() self.init() #and start the system self.root.after(1000,self.run_app) self.root.mainloop() def init(self): self.osc_config_file = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_oscremote.cfg' self.read_create_osc() self.pp_home_dir = self.osc_config.pp_home_dir self.pp_profiles_offset = self.osc_config.pp_profiles_offset self.mon.log(self,"Data Home from options is "+self.pp_home_dir) self.mon.log(self,"Current Profiles Offset from options is "+self.pp_profiles_offset) self.pp_profile_dir='' self.current_showlist=None self.current_show=None self.current_show_ref='' self.shows_display.delete(0,END) self.results.set('') def add_status(self,text): self.status_display.insert(END,text+'\n') self.status_display.see(END) def run_app(self): self.client=None self.server=None self.st=None # initialise OSC variables self.prefix='/pipresents' self.this_unit='/' + self.osc_config.this_unit_name self.add_status('this unit is: '+self.this_unit) self.controlled_unit='/'+self.osc_config.controlled_unit_1_name self.add_status('controlled unit is: '+self.controlled_unit) #connect client then start server to listen for replies self.init_client() self.add_status('connecting to controlled unit: '+self.osc_config.controlled_unit_1_ip+':'+self.osc_config.controlled_unit_1_port +' '+self.osc_config.controlled_unit_1_name) self.connect_client(self.osc_config.controlled_unit_1_ip,self.osc_config.controlled_unit_1_port) self.add_status('listening for replies on:'+self.osc_config.this_unit_ip+':'+self.osc_config.this_unit_port) self.init_server(self.osc_config.this_unit_ip,self.osc_config.this_unit_port,self.client) self.add_initial_handlers() self.start_server() # *************************************** # RESPOND TO BUTTONS # *************************************** def open_show(self): self.msg_path= '/core/open ' self.msg_arg_text= self.current_show_ref self.display_msg_text() def close_show(self): self.msg_path= '/core/close ' self.msg_arg_text=self.current_show_ref self.display_msg_text() def exit_pipresents(self): self.msg_path= '/core/exitpipresents' self.msg_arg_text='' self.display_msg_text() def play_event(self): self.msg_path= '/core/event' self.msg_arg_text = 'pp-play' self.display_msg_text() def pause_event(self): self.msg_path= '/core/event' self.msg_arg_text='pp-pause' self.display_msg_text() pass def stop_event(self): self.msg_path= '/core/event' self.msg_arg_text='pp-stop' self.display_msg_text() pass def up_event(self): self.msg_path= '/core/event' self.msg_arg_text='pp-up' self.display_msg_text() def down_event(self): self.msg_path= '/core/event' self.msg_arg_text='pp-down' self.display_msg_text() def output(self): self.msg_path= '/core/output' self.msg_arg_text='' self.display_msg_text() def loopback(self): self.msg_path= '/system/loopback' self.msg_arg_text='' self.display_msg_text() def server_info(self): self.msg_path= '/system/server-info' self.msg_arg_text='' self.display_msg_text() # and put the created text in the results box in the gui def display_msg_text(self): self.results.set(self.prefix+self.controlled_unit+self.msg_path+' '+self.msg_arg_text) #calback from the Send button # parses the message string into fields and sends - NO error checking def send_message(self): msg_text=self.results.get() self.add_status('Send message:'+msg_text) self.mon.log(self,'send message: ' + msg_text ) fields=msg_text.split() address = fields[0] arg_list=fields[1:] self.send(address,arg_list) # *************************************** # OSC CLIENT TO SEND MESSAGES # *************************************** def init_client(self): self.client = OSC.OSCClient() def connect_client(self,ip,port): self.mon.log(self,'connect to: '+ip+':'+str(port)) self.client.connect( (ip, int(port)) ) def send(self,address,arg_list): msg = OSC.OSCMessage() msg.setAddress(address) for arg in arg_list: msg.append(arg) self.client.send(msg) def disconnect_client(self): self.client.close() return # *************************************** # OSC SERVER TO LISTEN TO REPLIES # *************************************** def init_server(self,ip,port_text,client): self.mon.log(self,'Start Server: '+ip+':'+port_text) self.server = OSC.OSCServer((ip,int(port_text)),client) def start_server(self): self.st = threading.Thread( target = self.server.serve_forever ) self.st.start() def close_server(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') def add_initial_handlers(self): self.server.addMsgHandler('default', self.no_match_handler) self.server.addMsgHandler(self.prefix+self.this_unit+"/system/loopback-reply", self.loopback_reply_handler) self.server.addMsgHandler(self.prefix+self.this_unit+"/system/server-info-reply", self.server_info_reply_handler) def no_match_handler(self,addr, tags, stuff, source): text='' text+= "no match for new osc msg from %s" % OSC.getUrlStr(source)+'\n' text+= "with addr : %s" % addr+'\n' text+= "typetags %s" % tags+'\n' text+= "data %s" % stuff+'\n' self.add_status(text+'\n') def loopback_reply_handler(self,addr, tags, stuff, source): self.add_status('Loopback reply received from: '+ self.pretty_list(source)) def server_info_reply_handler(self,addr, tags, stuff, source): self.add_status('Server Information from: '+ self.pretty_list(source) + '\n ' + self.pretty_list(stuff)) def pretty_list(self,fields): text=' ' for field in fields: text += str(field) + ' ' return text # *************************************** # INIT EXIT MISC # *************************************** def e_edit_osc(self): self.disconnect_client() self.close_server() self.edit_osc() self.init() self.add_status('\n\n\nRESTART') self.run_app() def app_exit(self): self.disconnect_client() self.close_server() if self.root is not None: self.root.destroy() self.mon.finish() sys.exit() def show_help (self): tkMessageBox.showinfo("Help","Read 'manual.pdf'") def about (self): tkMessageBox.showinfo("About","Simple Remote Control for Pi Presents\n" +"Author: Ken Thompson" +"\nWebsite: http://pipresents.wordpress.com/") def setup_gui(self): # set up the gui # root is the Tkinter root widget self.root = Tk() self.root.title("Remote Control for Pi Presents") # self.root.configure(background='grey') self.root.resizable(False,False) # define response to main window closing self.root.protocol ("WM_DELETE_WINDOW", self.app_exit) # bind some display fields self.filename = StringVar() self.display_show = StringVar() self.results = StringVar() self.status = StringVar() # define menu menubar = Menu(self.root) profilemenu = Menu(menubar, tearoff=0, bg="grey", fg="black") profilemenu.add_command(label='Select', command = self.open_existing_profile) menubar.add_cascade(label='Profile', menu = profilemenu) toolsmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Tools', menu = toolsmenu) osc_configmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='OSC', menu = osc_configmenu) osc_configmenu.add_command(label='Edit', command = self.e_edit_osc) helpmenu = Menu(menubar, tearoff=0, bg="grey", fg="black") menubar.add_cascade(label='Help', menu = helpmenu) helpmenu.add_command(label='Help', command = self.show_help) helpmenu.add_command(label='About', command = self.about) self.root.config(menu=menubar) #top frame top_frame=Frame(self.root,padx=5,pady=5) top_frame.pack(side=TOP) results_label = Label(top_frame, text="Message to Send",font="arial 12 bold") results_label.pack(side=LEFT) results_display=Entry(top_frame, textvariable=self.results, width=70) results_display.pack(side=LEFT,fill=BOTH, expand=1) send_button = Button(top_frame, width = 5, height = 1, text='Send', fg='black', command = self.send_message, bg="light grey") send_button.pack(side=RIGHT) #bottom frame bottom_frame=Frame(self.root,padx=5,pady=5) bottom_frame.pack(side=TOP, fill=BOTH, expand=1) left_frame=Frame(bottom_frame, padx=5) left_frame.pack(side=LEFT) right_frame=Frame(bottom_frame,padx=5,pady=5) right_frame.pack(side=LEFT) suplabel_frame=Frame(right_frame,pady=5) suplabel_frame.pack(side=TOP) commands_label = Label(suplabel_frame, text="Show Control",font="arial 12 bold") commands_label.pack() supervisor_frame=Frame(right_frame,pady=5) supervisor_frame.pack(side=TOP) # supervisor buttons add_button = Button(supervisor_frame, width = 5, height = 1, text='Open\nShow', fg='black', command = self.open_show, bg="light grey") add_button.pack(side=LEFT) add_button = Button(supervisor_frame, width = 5, height = 1, text='Close\nShow', fg='black', command = self.close_show, bg="light grey") add_button.pack(side=LEFT) add_button = Button(supervisor_frame, width = 10, height = 1, text='Exit\nPi Presents', fg='black', command = self.exit_pipresents, bg="light grey") add_button.pack(side=LEFT) # events buttons oplabel_frame=Frame(right_frame,pady=5) oplabel_frame.pack(side=TOP) operations_label = Label(oplabel_frame, text="Input Events", font="arial 12 bold") operations_label.pack() operations_frame=Frame(right_frame,pady=5) operations_frame.pack(side=TOP) add_button = Button(operations_frame, width = 5, height = 1, text='Play', fg='black', command = self.play_event, bg="light grey") add_button.pack(side=LEFT) add_button = Button(operations_frame, width = 5, height = 1, text='Pause', fg='black', command = self.pause_event, bg="light grey") add_button.pack(side=LEFT) add_button = Button(operations_frame, width = 5, height = 1, text='Stop', fg='black', command = self.stop_event, bg="light grey") add_button.pack(side=LEFT) add_button = Button(operations_frame, width = 5, height = 1, text='Up', fg='black', command = self.up_event, bg="light grey") add_button.pack(side=LEFT) add_button = Button(operations_frame, width = 5, height = 1, text='Down', fg='black', command = self.down_event, bg="light grey") add_button.pack(side=LEFT) # animate buttons animate_frame=Frame(right_frame,pady=5) animate_frame.pack(side=TOP) animate_label = Label(animate_frame, text="Control Outputs", font="arial 12 bold") animate_label.pack() animate_frame=Frame(right_frame,pady=5) animate_frame.pack(side=TOP) add_button = Button(animate_frame, width = 5, height = 1, text='Output', fg='black', command = self.output, bg="light grey") add_button.pack(side=LEFT) # system buttons systemlabel_frame=Frame(right_frame,pady=5) systemlabel_frame.pack(side=TOP) system_label = Label(systemlabel_frame, text="System", font="arial 12 bold") system_label.pack() system_frame=Frame(right_frame,pady=5) system_frame.pack(side=TOP) add_button = Button(system_frame, width = 5, height = 1, text='Loopback', fg='black', command = self.loopback, bg="light grey") add_button.pack(side=LEFT) add_button = Button(system_frame, width = 10, height = 1, text='Server Info', fg='black', command = self.server_info, bg="light grey") add_button.pack(side=LEFT) # define display of showlist shows_title_frame=Frame(left_frame) shows_title_frame.pack(side=TOP) shows_label = Label(shows_title_frame, text="Shows") shows_label.pack() shows_frame=Frame(left_frame) shows_frame.pack(side=TOP) scrollbar = Scrollbar(shows_frame, orient=VERTICAL) self.shows_display = Listbox(shows_frame, selectmode=SINGLE, height=12, width = 40, bg="white",activestyle=NONE, fg="black", yscrollcommand=scrollbar.set) scrollbar.config(command=self.shows_display.yview) scrollbar.pack(side=RIGHT, fill=Y) self.shows_display.pack(side=LEFT, fill=BOTH, expand=1) self.shows_display.bind("<ButtonRelease-1>", self.e_select_show) # status_frame status_frame=Frame(self.root,padx=5,pady=5) status_frame.pack(side=TOP, fill=BOTH, expand=1) status_label = Label(status_frame, text="Status",font="arial 12 bold") status_label.pack(side=LEFT) scrollbar = Scrollbar(status_frame, orient=VERTICAL) self.status_display=Text(status_frame,height=10, yscrollcommand=scrollbar.set) scrollbar.config(command=self.status_display.yview) scrollbar.pack(side=RIGHT, fill=Y) self.status_display.pack(side=LEFT,fill=BOTH, expand=1) # *************************************** # SHOWLIST # *************************************** def open_existing_profile(self): initial_dir=self.pp_home_dir+os.sep+"pp_profiles"+self.pp_profiles_offset if os.path.exists(initial_dir) is False: self.mon.err(self,"Profiles directory not found: " + initial_dir + "\n\nHint: Data Home option must end in pp_home") return dir_path=tkFileDialog.askdirectory(initialdir=initial_dir) # dir_path="C:\Users\Ken\pp_home\pp_profiles\\ttt" if len(dir_path)>0: self.open_profile(dir_path) def open_profile(self,dir_path): showlist_file = dir_path + os.sep + "pp_showlist.json" if os.path.exists(showlist_file) is False: self.mon.err(self,"Not a Profile: " + dir_path + "\n\nHint: Have you opened the profile directory?") return self.pp_profile_dir = dir_path self.root.title("Remote for Pi Presents - "+ self.pp_profile_dir) self.open_showlist(self.pp_profile_dir) def open_showlist(self,profile_dir): showlist_file = profile_dir + os.sep + "pp_showlist.json" if os.path.exists(showlist_file) is False: self.mon.err(self,"showlist file not found at " + profile_dir + "\n\nHint: Have you opened the profile directory?") self.app_exit() self.current_showlist=ShowList() self.current_showlist.open_json(showlist_file) if float(self.current_showlist.sissue()) != float(self.editor_issue): self.mon.err(self,"Version of profile does not match Remote: "+self.editor_issue) self.app_exit() self.refresh_shows_display() def refresh_shows_display(self): self.shows_display.delete(0,self.shows_display.size()) for index in range(self.current_showlist.length()): self.shows_display.insert(END, self.current_showlist.show(index)['title']+" ["+self.current_showlist.show(index)['show-ref']+"]") if self.current_showlist.show_is_selected(): self.shows_display.itemconfig(self.current_showlist.selected_show_index(),fg='red') self.shows_display.see(self.current_showlist.selected_show_index()) def e_select_show(self,event): print 'select show', self.current_showlist.length() if self.current_showlist is not None and self.current_showlist.length()>0: mouse_item_index=int(event.widget.curselection()[0]) self.current_showlist.select(mouse_item_index) self.current_show_ref=self.current_showlist.selected_show()['show-ref'] self.refresh_shows_display() else: self.current_show_ref='' # *************************************** # OSC CONFIGURATION # *************************************** def read_create_osc(self): if self.osc_config.read(self.osc_config_file) is False: self.osc_config.create(self.osc_config_file) eosc = OSCEditor(self.root, self.osc_config_file,'remote','Create OSC Remote Configuration') self.osc_config.read(self.osc_config_file) def edit_osc(self): if self.osc_config.read(self.osc_config_file) is False: self.osc_config.create(self.osc_config_file) eosc = OSCEditor(self.root, self.osc_config_file,'remote','Edit OSC Reomote Configuration')
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