Пример #1
0
class PiPresents(object):
    def pipresents_version(self):
        vitems = self.pipresents_issue.split('.')
        if len(vitems) == 2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000 * int(vitems[0]) + 100 * int(vitems[1])
        else:
            return 1000 * int(vitems[0]) + 100 * int(vitems[1]) + int(
                vitems[2])

    def __init__(self):
        # gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_SAVEALL)
        self.pipresents_issue = "1.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
Пример #2
0
class PiPresents(object):

    def pipresents_version(self):
        vitems=self.pipresents_issue.split('.')
        if len(vitems)==2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000*int(vitems[0])+100*int(vitems[1])
        else:
            return 1000*int(vitems[0])+100*int(vitems[1])+int(vitems[2])


    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        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()
Пример #3
0
class TimeOfDay(object):

    # CLASS VARIABLES

    # change this for another language
    DAYS_OF_WEEK = [
        'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday',
        'sunday'
    ]
    """
        TimeOfDay.events is a dictionary the keys being show-refs.
        Each dictionary entry is a list of time_elements sorted by descending time
        Each time element is a list with the fields:
        0 - command
        1 - time, seconds since midnight

    """
    events = {
    }  # list of times of day used to generate callbacks, earliest first

    # executed by main program and by each object using tod
    def __init__(self):
        self.mon = Monitor()

    # executed once from main program  only
    def init(self, pp_dir, pp_home, pp_profile, showlist, root, callback):

        # instantiate arguments
        TimeOfDay.root = root
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.showlist = showlist
        self.callback = callback

        # init variables
        self.testing = False
        self.tod_tick = 500
        self.tick_timer = None
        # set time of day for if no schedule
        TimeOfDay.now = datetime.now().replace(microsecond=0)

        #  read and error check the schedule
        reason, message, schedule_enabled = self.read_schedule()
        if reason == 'error':
            return 'error', message, False

        if schedule_enabled is False:
            return 'normal', '', False

        #create the initial events list
        if self.simulate_time is True:
            year = int(self.sim_year)
            month = int(self.sim_month)
            day = int(self.sim_day)
            hour = int(self.sim_hour)
            minute = int(self.sim_minute)
            second = int(self.sim_second)
            TimeOfDay.now = datetime(day=day,
                                     month=month,
                                     year=year,
                                     hour=hour,
                                     minute=minute,
                                     second=second)
            self.testing = True
            print '\nInitial SIMULATED time', TimeOfDay.now.ctime()
            self.mon.sched(
                self, TimeOfDay.now, 'Testing is ON, Initial SIMULATED time ' +
                str(TimeOfDay.now.ctime()))
        else:
            #get the current date/time only this once
            TimeOfDay.now = datetime.now().replace(microsecond=0)
            self.mon.sched(
                self, TimeOfDay.now, 'Testing is OFF, Initial REAL time ' +
                str(TimeOfDay.now.ctime()))
            # print '\nInitial REAL time',TimeOfDay.now.ctime()
            self.testing = False
        # print 'init',TimeOfDay.now
        TimeOfDay.last_now = TimeOfDay.now - timedelta(seconds=1)
        reason, message = self.build_schedule_for_today()
        if reason == 'error':
            return 'error', message, False
        self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule())
        if self.testing:
            self.print_todays_schedule()
        self.build_events_lists()
        if self.testing:
            self.print_events_lists()
        # and do exitpipresents or start any show that should be running at start up time
        self.do_catchup()
        return 'normal', '', True

    def do_catchup(self):
        TimeOfDay.scheduler_time = TimeOfDay.now.time()

        # do the catchup for each real show in turn
        # nothing required for start show, all previous events are just ignored.
        for show_ref in TimeOfDay.events:
            if show_ref != 'start' and self.enable_catchup[show_ref] == 'yes':
                if self.testing:
                    print 'Catch Up', show_ref
                times = TimeOfDay.events[show_ref]
                # go through the event list for a show rembering show state until the first future event is found.
                # then if last command was to start the show send it
                show_running = False
                last_start_element = []
                for time_element in reversed(times):
                    # print 'now', now_seconds, 'time from events', time_element[1], time_element[0]
                    # got past current time can give up catch up
                    if time_element[1] >= TimeOfDay.scheduler_time:
                        # print ' gone past time - break'
                        break
                    if time_element[0] == 'open':
                        last_start_element = time_element
                        # print 'open - show-running= true'
                        show_running = True
                    elif time_element[0] == 'close':
                        # print 'close - show-running= false'
                        show_running = False
                if show_running is True:
                    #if self.testing:
                    # print 'End of Catch Up Search', show_ref,last_start_element
                    self.mon.sched(
                        self, TimeOfDay.now, 'Catch up for show: ' + show_ref +
                        ' requires ' + last_start_element[0] + ' ' +
                        str(last_start_element[1]))
                    self.do_event(show_ref, last_start_element)

        return 'not exiting'

    # called by main program only
    def poll(self):
        if self.testing:
            poll_time = TimeOfDay.now
        else:
            poll_time = datetime.now()
        # print 'poll time: ',poll_time.time(),'scheduler time: ',TimeOfDay.now.time()
        # if poll_time != TimeOfDay.now : print 'times different ',poll_time.time(),TimeOfDay.now.time()
        # is current time greater than last time the scheduler was run
        # run in a loop to catch up because root.after can get behind when images are being rendered etc.
        # poll time can be the same twice as poll is run at half second intervals.
        catchup_time = 0
        while TimeOfDay.now <= poll_time:
            if TimeOfDay.now - TimeOfDay.last_now != timedelta(seconds=1):
                print 'POLL TIME FAILED', TimeOfDay.last_now, TimeOfDay.now
            #if catchup_time != 0:
            # print 'scheduler behind by: ',catchup_time, TimeOfDay.now.time(),poll_time.time()
            self.do_scheduler()
            TimeOfDay.last_now = TimeOfDay.now
            catchup_time += 1
            # print 'poll',TimeOfDay.now, timedelta(seconds=1)
            TimeOfDay.now = TimeOfDay.now + timedelta(seconds=1)
        # and loop
        if self.testing:
            self.tick_timer = TimeOfDay.root.after(1000, self.poll)
        else:
            self.tick_timer = TimeOfDay.root.after(self.tod_tick, self.poll)

    # called by main program only
    def terminate(self):
        if self.tick_timer is not None:
            TimeOfDay.root.after_cancel(self.tick_timer)
        self.clear_events_lists()

    # execute events at the appropriate time.
    # called by main program only
    def do_scheduler(self):
        # if its midnight then build the events lists for the new day
        TimeOfDay.scheduler_time = TimeOfDay.now.time()
        if TimeOfDay.scheduler_time == time(hour=0, minute=0, second=0):
            # if self.testing:
            # print 'Its midnight,  today is now', TimeOfDay.now.ctime()
            self.mon.sched(
                self, TimeOfDay.now,
                'Its midnight,  today is now ' + str(TimeOfDay.now.ctime()))
            reason, message = self.build_schedule_for_today()
            if reason == 'error':
                self.mon.err(self, 'system error- illegal time at midnight')
                return
            self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule())
            # if self.testing:
            # self.print_todays_schedule()
            self.build_events_lists()
            # self.mon.sched(self,TimeOfDay.now,self.pretty_events_lists())
            # self.print_events_lists()

        # print TimeOfDay.scheduler_time
        for show_ref in TimeOfDay.events:
            # print 'scheduler time match', show_ref
            times = TimeOfDay.events[show_ref]
            # now send a command if time matches
            for time_element in reversed(times):
                # print time_element[1],TimeOfDay.scheduler_time
                if time_element[1] == TimeOfDay.scheduler_time:
                    self.do_event(show_ref, time_element)

    # execute an event
    def do_event(self, show_ref, time_element):
        self.mon.log(
            self, 'Event : ' + time_element[0] + ' ' + show_ref +
            ' required at: ' + time_element[1].isoformat())
        self.mon.sched(
            self, TimeOfDay.now, ' ToD Scheduler : ' + time_element[0] + ' ' +
            show_ref + ' required at: ' + time_element[1].isoformat())
        if self.testing:
            print 'Event : ' + time_element[
                0] + ' ' + show_ref + ' required at: ' + time_element[
                    1].isoformat()
        if show_ref != 'start':
            self.callback(time_element[0] + ' ' + show_ref)
        else:
            self.callback(time_element[0])

#
# ************************************************
# The methods below can be called from many classes so need to operate on class variables
# ************************************************

# clear events list

    def clear_events_lists(self):
        self.mon.log(self, 'clear time of day  events list ')
        # empty event list
        TimeOfDay.events = {}

# ***********************************
# Preparing schedule and todays event list
# ************************************

    def read_schedule(self):
        # get schedule from showlist
        index = self.showlist.index_of_start_show()
        self.showlist.select(index)
        starter_show = self.showlist.selected_show()

        sched_enabled = starter_show['sched-enable']
        if sched_enabled != 'yes':
            return 'normal', '', False

        if starter_show['simulate-time'] == 'yes':
            self.simulate_time = True

            self.sim_second = starter_show['sim-second']
            if not self.sim_second.isdigit():
                return 'error', 'Simulate time -  second is not a positive integer ' + self.sim_second, False
            if int(self.sim_second) > 59:
                return 'error', 'Simulate time - second is out of range ' + self.sim_second, False

            self.sim_minute = starter_show['sim-minute']
            if not self.sim_minute.isdigit():
                return 'error', 'Simulate time - minute is not a positive integer ' + self.sim_minute, False
            if int(self.sim_minute) > 59:
                return 'error', 'Simulate time -  minute is out of range ' + self.sim_minute, False

            self.sim_hour = starter_show['sim-hour']
            if not self.sim_hour.isdigit():
                return 'error', 'Simulate time - hour is not a positive integer ' + self.sim_hour, False
            if int(self.sim_hour) > 23:
                return 'error', 'Simulate time -  hour is out of range ' + self.sim_hour, False

            self.sim_day = starter_show['sim-day']
            if not self.sim_day.isdigit():
                return 'error', 'Simulate time - day is not a positive integer ' + self.sim_day, False
            if int(self.sim_day) > 31:
                return 'error', 'Simulate time -  day is out of range ' + self.sim_day, False

            self.sim_month = starter_show['sim-month']
            if not self.sim_month.isdigit():
                return 'error', 'Simulate time - month is not a positive integer ' + self.sim_month, False
            if int(self.sim_month) > 12:
                return 'error', 'Simulate time -  month is out of range ' + self.sim_month, False

            self.sim_year = starter_show['sim-year']
            if not self.sim_year.isdigit():
                return 'error', 'Simulate time - year is not a positive integer ' + self.sim_year, False
            if int(self.sim_year) < 2019:
                return 'error', 'Simulate time -  year is out of range ' + self.sim_year, False
        else:
            self.simulate_time = False

        return 'normal', '', True

    def build_schedule_for_today(self):
        # print this_day.year, this_day.month, this_day.day, TimeOfDay.DAYS_OF_WEEK[ this_day.weekday()]
        """
        self.todays_schedule is a dictionary the keys being show-refs.
        Each dictionary entry is a list of time_elements
        Each time element is a list with the fields:
        0 - command
        1 - time hour:min[:sec]

        """
        self.enable_catchup = {}
        self.todays_schedule = {}
        for index in range(self.showlist.length()):
            show = self.showlist.show(index)
            show_type = show['type']
            show_ref = show['show-ref']
            if show['type'] != 'start':
                self.enable_catchup[show_ref] = show['enable-catchup']
            # print 'looping build ',show_type,show_ref,self.showlist.length()
            if 'sched-everyday' in show:
                text = show['sched-everyday']
                lines = text.splitlines()
                while len(lines) != 0:
                    status, message, day_lines, lines = self.get_one_day(
                        lines, show_ref)
                    if status == 'error':
                        return 'error', message
                    status, message, days_list, times_list = self.parse_day(
                        day_lines, 'everyday', show_ref, show_type)
                    if status == 'error':
                        return 'error', message
                    #print 'everyday ',status,message,days_list,times_list
                    self.todays_schedule[show['show-ref']] = copy.deepcopy(
                        times_list)

                # print '\nafter everyday'
                # self.print_todays_schedule()

            if 'sched-weekday' in show:
                text = show['sched-weekday']
                lines = text.splitlines()
                while len(lines) != 0:
                    status, message, day_lines, lines = self.get_one_day(
                        lines, show_ref)
                    if status == 'error':
                        return 'error', message
                    status, message, days_list, times_list = self.parse_day(
                        day_lines, 'weekday', show_ref, show_type)
                    if status == 'error':
                        return 'error', message
                    #print 'weekday ',status,message,days_list,times_list
                    # is current day of the week in list of days in schedule
                    if TimeOfDay.DAYS_OF_WEEK[
                            TimeOfDay.now.weekday()] in days_list:
                        self.todays_schedule[show['show-ref']] = copy.deepcopy(
                            times_list)

                #print '\nafter weekday'
                #self.print_todays_schedule()

            if 'sched-monthday' in show:
                text = show['sched-monthday']
                lines = text.splitlines()
                while len(lines) != 0:
                    status, message, day_lines, lines = self.get_one_day(
                        lines, show_ref)
                    # print 'in monthday',day_lines
                    if status == 'error':
                        return 'error', message
                    status, message, days_list, times_list = self.parse_day(
                        day_lines, 'monthday', show_ref, show_type)
                    if status == 'error':
                        return 'error', message
                    #print 'monthday ',status,message,days_list,times_list
                    if TimeOfDay.now.day in map(int, days_list):
                        self.todays_schedule[show['show-ref']] = copy.deepcopy(
                            times_list)

                #print '\nafter monthday'
                #self.print_todays_schedule()

            if 'sched-specialday' in show:

                text = show['sched-specialday']
                lines = text.splitlines()
                while len(lines) != 0:
                    status, message, day_lines, lines = self.get_one_day(
                        lines, show_ref)
                    if status == 'error':
                        return 'error', message

                    status, message, days_list, times_list = self.parse_day(
                        day_lines, 'specialday', show_ref, show_type)
                    if status == 'error':
                        return 'error', message
                    # print 'specialday ',status,message,days_list,times_list
                    for day in days_list:
                        sdate = datetime.strptime(day, '%Y-%m-%d')
                        if sdate.year == TimeOfDay.now.year and sdate.month == TimeOfDay.now.month and sdate.day == TimeOfDay.now.day:
                            self.todays_schedule[
                                show['show-ref']] = copy.deepcopy(times_list)

                #print '\nafter specialday'
                #self.print_todays_schedule()
        # print self.enable_catchup
        return 'normal', ''

    def get_one_day(self, lines, show_ref):
        this_day = []
        left_over = []
        #print 'get one day',lines
        # check first line is day and move tt output
        #print lines[0]
        if not lines[0].startswith('day'):
            return 'error', 'first line of section is not day ' + lines[
                0] + ' ' + show_ref, [], []
        this_day = [lines[0]]
        #print ' this day',this_day
        left_over = lines[1:]
        # print 'left over',left_over
        x_left_over = lines[1:]
        for line in x_left_over:
            #print 'in loop',line
            if line.startswith('day'):
                # print 'one day day',this_day,left_over
                return 'normal', '', this_day, left_over
            this_day.append(line)
            left_over = left_over[1:]
        # print 'one day end',this_day,left_over
        return 'normal', '', this_day, left_over

    def parse_day(self, lines, section, show_ref, show_type):
        # text
        # day monday
        # open 1:42
        # close 1:45
        # returns status,message,list of days,list of time lines
        if section == 'everyday':
            status, message, days_list = self.parse_everyday(
                lines[0], show_ref)
        elif section == 'weekday':
            status, message, days_list = self.parse_weekday(lines[0], show_ref)
        elif section == 'monthday':
            # print 'parse_day',lines
            status, message, days_list = self.parse_monthday(
                lines[0], show_ref)
        elif section == 'specialday':
            status, message, days_list = self.parse_specialday(
                lines[0], show_ref)
        else:
            return 'error','illegal section name '+section + ' '+ show_ref,[],[]
        if status == 'error':
            return 'error', message, [], []
        if len(lines) > 1:
            time_lines = lines[1:]
            status, message, times_list = self.parse_time_lines(
                time_lines, show_ref, show_type)
            if status == 'error':
                return 'error', message, [], []
        else:
            times_list = []
        return 'normal', '', days_list, times_list

    def parse_everyday(self, line, show_ref):
        words = line.split()
        if words[0] != 'day':
            return 'error','day line does not contain day  '+ line + ' ' + show_ref,[]
        if words[1] != 'everyday':
            return 'error','everday line does not contain everyday  '+ show_ref,[]
        return 'normal', '', ['everyday']

    def parse_weekday(self, line, show_ref):
        words = line.split()
        if words[0] != 'day':
            return 'error','day line does not contain day  ' + line + ' ' + show_ref,[]
        days = words[1:]
        for day in days:
            if day not in TimeOfDay.DAYS_OF_WEEK:
                return 'error','weekday line has illegal day '+ day + ' '+ show_ref,[]
        return 'normal', '', days

    def parse_monthday(self, line, show_ref):
        words = line.split()
        if words[0] != 'day':
            return 'error', 'day line does not contain day  ' + show_ref, []
        days = words[1:]
        for day in days:
            if not day.isdigit():
                return 'error','monthday line has illegal day '+ day + ' '+ show_ref,[]
            if int(day) < 1 or int(day) > 31:
                return 'error','monthday line has out of range day '+ line+ ' '+ show_ref,[]
        return 'normal', '', days

    def parse_specialday(self, line, show_ref):
        words = line.split()
        if words[0] != 'day':
            return 'error', 'day line does not contain day  ' + show_ref, []
        days = words[1:]
        for day in days:
            status, message = self.parse_date(day, show_ref)
            if status == 'error':
                return 'error', message, ''
        return 'normal', '', days

    def parse_time_lines(self, lines, show_ref, show_type):
        # lines - list of  lines each with text 'command time'
        # returns list of lists each being [command, time]
        time_lines = []
        for line in lines:
            # split line into time,command
            words = line.split()
            if len(words) < 2:
                return 'error','time line has wrong length '+ line+ ' '+ show_ref,[]
            status, message, time_item = self.parse_time(words[0], show_ref)
            if status == 'error':
                return 'error', message, []

            if show_type == 'start':
                command = ' '.join(words[1:])
                time_lines.append([command, time_item])
            else:
                if words[1] not in ('open', 'close'):
                    return 'error','illegal command in '+ line+ ' '+ show_ref,[]
                time_lines.append([words[1], time_item])
        return 'normal', '', time_lines

    def build_events_lists(self):
        # builds events dictionary from todays_schedule by
        # converting times in todays schedule from hour:min:sec to datetime
        # and sorts them earliest last
        TimeOfDay.events = {}
        for show_ref in self.todays_schedule:
            # print show_ref
            times = self.todays_schedule[show_ref]
            for time_element in times:
                time_element[1] = self.parse_event_time(time_element[1])
            sorted_times = sorted(times,
                                  key=lambda time_element: time_element[1],
                                  reverse=True)
            TimeOfDay.events[show_ref] = sorted_times
            # print times

    def parse_event_time(self, time_text):
        fields = time_text.split(':')
        if len(fields) > 2:
            secs = int(fields[2])
        else:
            secs = 0
        hours = int(fields[0])
        mins = int(fields[1])
        return time(hour=hours, minute=mins, second=secs)

    def parse_time(self, item, show_ref):
        fields = item.split(':')
        if len(fields) == 0:
            return 'error', 'Time field is empty ' + item + ' ' + show_ref, item
        if len(fields) > 3:
            return 'error', 'Too many fields in ' + item + ' ' + show_ref, item
        if len(fields) == 1:
            seconds = fields[0]
            minutes = '0'
            hours = '0'
        if len(fields) == 2:
            seconds = fields[1]
            minutes = fields[0]
            hours = '0'
        if len(fields) == 3:
            seconds = fields[2]
            minutes = fields[1]
            hours = fields[0]
        if not seconds.isdigit() or not minutes.isdigit() or not hours.isdigit(
        ):
            return 'error', 'Fields of  ' + item + ' are not positive integers ' + show_ref, item
        if int(minutes) > 59:
            return 'error', 'Minutes of  ' + item + ' is out of range ' + show_ref, item
        if int(seconds) > 59:
            return 'error', 'Seconds of  ' + item + ' is out of range ' + show_ref, item
        if int(hours) > 23:
            return 'error', 'Hours of  ' + item + ' is out of range ' + show_ref, item
        return 'normal', '', item

    def parse_date(self, item, show_ref):
        fields = item.split('-')
        if len(fields) == 0:
            return 'error', 'Date field is empty ' + item + ' ' + show_ref
        if len(fields) != 3:
            return 'error', 'Too many or few fields in date ' + item + ' ' + show_ref
        year = fields[0]
        month = fields[1]
        day = fields[2]
        if not year.isdigit() or not month.isdigit() or not day.isdigit():
            return 'error', 'Fields of  ' + item + ' are not positive integers ' + show_ref
        if int(year) < 2018:
            return 'error', 'Year of  ' + item + ' is out of range ' + show_ref + year
        if int(month) > 12:
            return 'error', 'Month of  ' + item + ' is out of range ' + show_ref
        if int(day) > 31:
            return 'error', 'Day of  ' + item + ' is out of range ' + show_ref
        return 'normal', ''

# *********************
# print for debug
# *********************

    def pretty_todays_schedule(self):
        op = 'Schedule For ' + TimeOfDay.now.ctime() + '\n'
        for key in self.todays_schedule:
            op += '  ' + key + '\n'
            for show in self.todays_schedule[key]:
                op += '    ' + show[0] + ':   ' + str(show[1]) + '\n'
            op += '\n'
        return op

    def pretty_events_lists(self):
        op = ' Task list for today'
        for key in self.events:
            op += '\n' + key
            for show in self.events[key]:
                op += '\n    ' + show[0] + ' ' + str(show[1].isoformat())
        return op

    def print_todays_schedule(self):
        print '\nSchedule For ' + TimeOfDay.now.ctime()
        for key in self.todays_schedule:
            print '  ' + key
            for show in self.todays_schedule[key]:
                print '    ' + show[0] + ':   ' + show[1]
            print

    def print_events_lists(self):
        print '\nTask list for today'
        for key in self.events:
            print '\n', key
            for show in self.events[key]:
                print show[0], show[1].isoformat()
        print
Пример #4
0
class ShowManager(object):
    """
    ShowManager manages PiPresents' concurrent shows. It does not manage sub-shows or child-shows but has a bit of common code to initilise them
    concurrent shows are always top level (level 0) shows:
    They can be opened/closed  by the start show(open only) or by 'open/close myshow' in the Show Control field of players, by time of day sceduler or by OSC
    
   Two shows with the same show reference cannot be run concurrently as there is no way to reference an individual instance.
   However a workaround is to make the secong instance a subshow of a mediashow with a different reference.

    """

    # Declare class variables
    shows = []
    canvas = None  #canvas for all shows
    shutdown_required = False
    SHOW_TEMPLATE = ['', None]
    SHOW_REF = 0  # show-reference  - name of the show as in editor
    SHOW_OBJ = 1  # the python object
    showlist = []

    # Initialise class variables, first time through only in pipresents.py

    def init(self, canvas, all_shows_ended_callback, command_callback,
             showlist):
        ShowManager.all_shows_ended_callback = all_shows_ended_callback
        ShowManager.shows = []
        ShowManager.shutdown_required = False
        ShowManager.canvas = canvas
        ShowManager.command_callback = command_callback
        ShowManager.showlist = showlist

# **************************************
# functions to manipulate show register
# **************************************

    def register_shows(self):
        for show in ShowManager.showlist.shows():
            if show['show-ref'] != 'start':
                reason, message = self.register_show(show['show-ref'])
                if reason == 'error':
                    return reason, message
        return 'normal', 'shows regiistered'

    def register_show(self, ref):
        registered = self.show_registered(ref)
        if registered == -1:
            ShowManager.shows.append(copy.deepcopy(ShowManager.SHOW_TEMPLATE))
            index = len(ShowManager.shows) - 1
            ShowManager.shows[index][ShowManager.SHOW_REF] = ref
            ShowManager.shows[index][ShowManager.SHOW_OBJ] = None
            self.mon.trace(
                self, ' - register show: show_ref = ' + ref + ' index = ' +
                str(index))
            return 'normal', 'show registered'
        else:
            # self.mon.err(self, ' more than one show in showlist with show-ref: ' + ref )
            return 'error', ' more than one show in showlist with show-ref: ' + ref

    # is the show registered?
    # can be used to return the index to the show
    def show_registered(self, show_ref):
        index = 0
        for show in ShowManager.shows:
            if show[ShowManager.SHOW_REF] == show_ref:
                return index
            index += 1
        return -1

    # needs calling program to check that the show is not already running
    def set_running(self, index, show_obj):
        ShowManager.shows[index][ShowManager.SHOW_OBJ] = show_obj
        self.mon.trace(
            self,
            'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] +
            ' show_id= ' + str(index))

    # is the show running?
    def show_running(self, index):
        if ShowManager.shows[index][ShowManager.SHOW_OBJ] is not None:
            return ShowManager.shows[index][ShowManager.SHOW_OBJ]
        else:
            return None

    def set_exited(self, index):
        ShowManager.shows[index][ShowManager.SHOW_OBJ] = None
        self.mon.trace(
            self,
            'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] +
            ' show_id= ' + str(index))

    # are all shows exited?
    def all_shows_exited(self):
        all_exited = True
        for show in ShowManager.shows:
            if show[ShowManager.SHOW_OBJ] is not None:
                all_exited = False
        return all_exited

    # fromat for printing
    def pretty_shows(self):
        shows = '\n'
        for show in ShowManager.shows:
            shows += show[ShowManager.SHOW_REF] + '\n'
        return shows


# *********************************
# show control
# *********************************

# show manager can be initialised by a player, shower or by pipresents.py
# if by pipresents.py then show_id=-1

    def __init__(self, show_id, showlist, show_params, root, canvas, pp_dir,
                 pp_profile, pp_home):
        self.show_id = show_id
        self.showlist = showlist
        self.show_params = show_params
        self.root = root
        self.show_canvas = canvas
        self.pp_dir = pp_dir
        self.pp_profile = pp_profile
        self.pp_home = pp_home

        self.mon = Monitor()

    def control_a_show(self, show_ref, show_command):
        if show_command == 'open':
            return self.start_show(show_ref)
        elif show_command == 'close':
            return self.exit_show(show_ref)
        elif show_command == 'closeall':
            return self.exit_all_shows()
        elif show_command == 'openexclusive':
            return self.open_exclusive(show_ref)
        else:
            return 'error', 'command not recognised ' + show_command

    def open_exclusive(self, show_ref):
        self.exit_all_shows()
        self.exclusive_show = show_ref
        self.wait_for_openexclusive()
        return 'normal', 'opened exclusive'

    def wait_for_openexclusive(self):
        if self.all_shows_exited() is False:
            ShowManager.canvas.after(1, self.wait_for_openexclusive)
            return
        self.start_show(self.exclusive_show)

    def exit_all_shows(self):
        for show in ShowManager.shows:
            self.exit_show(show[ShowManager.SHOW_REF])
        return 'normal', 'exited all shows'

    # kick off the exit sequence of a show by calling the shows exit method.
    # it will result in all the shows in a stack being closed and end_play_show being called
    def exit_show(self, show_ref):
        index = self.show_registered(show_ref)
        self.mon.log(
            self,
            "De-registering show " + show_ref + ' show index:' + str(index))
        show_obj = self.show_running(index)
        if show_obj is not None:
            self.mon.log(
                self, "Exiting show " + show_ref + ' show index:' + str(index))
            show_obj.exit()
        return 'normal', 'exited a concurrent show'

    def start_show(self, show_ref):
        index = self.show_registered(show_ref)
        if index < 0:
            return 'error', "Show not found in showlist: " + show_ref
        show_index = self.showlist.index_of_show(show_ref)
        show = self.showlist.show(show_index)
        reason, message, show_canvas = self.compute_show_canvas(show)
        if reason == 'error':
            return reason, message
        # print 'STARTING TOP LEVEL SHOW',show_canvas
        self.mon.sched(
            self, TimeOfDay.now, 'Starting Show: ' + show_ref +
            ' from show: ' + self.show_params['show-ref'])
        self.mon.log(
            self, 'Starting Show: ' + show_ref + ' from: ' +
            self.show_params['show-ref'])
        if self.show_running(index):
            self.mon.sched(
                self, TimeOfDay.now,
                "show already running so ignoring command: " + show_ref)
            self.mon.warn(
                self, "show already running so ignoring command: " + show_ref)
            return 'normal', 'this concurrent show already running'
        show_obj = self.init_show(index, show, show_canvas)
        if show_obj is None:
            return 'error', "unknown show type in start concurrent show - " + show[
                'type']
        else:
            self.set_running(index, show_obj)
            # params - end_callback, show_ready_callback, parent_kickback_signal, level
            show_obj.play(self._end_play_show, None, False, 0, [])
            return 'normal', 'concurrent show started'

    # used by shows to create subshows or child shows
    def init_subshow(self, show_id, show, show_canvas):
        return self.init_show(show_id, show, show_canvas)

    def _end_play_show(self, index, reason, message):
        show_ref_to_exit = ShowManager.shows[index][ShowManager.SHOW_REF]
        show_to_exit = ShowManager.shows[index][ShowManager.SHOW_OBJ]
        self.mon.sched(self, TimeOfDay.now, 'Closed show: ' + show_ref_to_exit)
        self.mon.log(
            self, 'Exited from show: ' + show_ref_to_exit + ' ' + str(index))
        self.mon.log(self, 'Exited with Reason = ' + reason)
        self.mon.trace(
            self,
            ' Show is: ' + show_ref_to_exit + ' show index ' + str(index))
        # closes the video/audio from last track then closes the track
        # print 'show to exit ',show_to_exit, show_to_exit.current_player,show_to_exit.previous_player
        self.set_exited(index)
        if self.all_shows_exited() is True:
            ShowManager.all_shows_ended_callback(reason, message)
        return reason, message

    # common function to initilaise the show by type
    def init_show(
        self,
        show_id,
        selected_show,
        show_canvas,
    ):
        if selected_show['type'] == "mediashow":
            return MediaShow(show_id, selected_show, self.root, show_canvas,
                             self.showlist, self.pp_dir, self.pp_home,
                             self.pp_profile, ShowManager.command_callback)

        elif selected_show['type'] == "liveshow":
            return LiveShow(show_id, selected_show, self.root, show_canvas,
                            self.showlist, self.pp_dir, self.pp_home,
                            self.pp_profile, ShowManager.command_callback)

        elif selected_show['type'] == "radiobuttonshow":
            return RadioButtonShow(show_id, selected_show, self.root,
                                   show_canvas, self.showlist, self.pp_dir,
                                   self.pp_home, self.pp_profile,
                                   ShowManager.command_callback)

        elif selected_show['type'] == "hyperlinkshow":
            return HyperlinkShow(show_id, selected_show, self.root,
                                 show_canvas, self.showlist, self.pp_dir,
                                 self.pp_home, self.pp_profile,
                                 ShowManager.command_callback)

        elif selected_show['type'] == "menu":
            return MenuShow(show_id, selected_show, self.root, show_canvas,
                            self.showlist, self.pp_dir, self.pp_home,
                            self.pp_profile, ShowManager.command_callback)

        elif selected_show['type'] == "artmediashow":
            return ArtMediaShow(show_id, selected_show, self.root, show_canvas,
                                self.showlist, self.pp_dir, self.pp_home,
                                self.pp_profile, ShowManager.command_callback)

        elif selected_show['type'] == "artliveshow":
            return ArtLiveShow(show_id, selected_show, self.root, show_canvas,
                               self.showlist, self.pp_dir, self.pp_home,
                               self.pp_profile, ShowManager.command_callback)
        else:
            return None

    def compute_show_canvas(self, show_params):
        canvas = {}
        canvas['canvas-obj'] = ShowManager.canvas
        status, message, self.show_canvas_x1, self.show_canvas_y1, self.show_canvas_x2, self.show_canvas_y2 = self.parse_show_canvas(
            show_params['show-canvas'])
        if status == 'error':
            # self.mon.err(self,'show canvas error: ' + message + ' in ' + show_params['show-canvas'])
            return 'error', 'show canvas error: ' + message + ' in ' + show_params[
                'show-canvas'], canvas
        else:
            self.show_canvas_width = self.show_canvas_x2 - self.show_canvas_x1
            self.show_canvas_height = self.show_canvas_y2 - self.show_canvas_y1
            self.show_canvas_centre_x = self.show_canvas_width / 2
            self.show_canvas_centre_y = self.show_canvas_height / 2
            canvas['show-canvas-x1'] = self.show_canvas_x1
            canvas['show-canvas-y1'] = self.show_canvas_y1
            canvas['show-canvas-x2'] = self.show_canvas_x2
            canvas['show-canvas-y2'] = self.show_canvas_y2
            canvas['show-canvas-width'] = self.show_canvas_width
            canvas['show-canvas-height'] = self.show_canvas_height
            canvas['show-canvas-centre-x'] = self.show_canvas_centre_x
            canvas['show-canvas-centre-y'] = self.show_canvas_centre_y
            return 'normal', '', canvas

    def parse_show_canvas(self, text):
        fields = text.split()
        # blank so show canvas is the whole screen
        if len(fields) < 1:
            return 'normal', '', 0, 0, int(self.canvas['width']), int(
                self.canvas['height'])

        elif len(fields) in (1, 4):
            # window is specified
            status, message, x1, y1, x2, y2 = parse_rectangle(text)
            if status == 'error':
                return 'error', message, 0, 0, 0, 0
            else:
                return 'normal', '', x1, y1, x2, y2
        else:
            # error
            return 'error', 'Wrong number of fields in Show canvas: ' + text, 0, 0, 0, 0
class OSCDriver(object):

    # executed by main program
    def init(self, pp_profile, manager_unit, preferred_interface, my_ip,
             show_command_callback, input_event_callback, animate_callback):

        self.pp_profile = pp_profile
        self.show_command_callback = show_command_callback
        self.input_event_callback = input_event_callback
        self.animate_callback = animate_callback

        self.mon = Monitor()
        config_file = self.pp_profile + os.sep + 'pp_io_config' + os.sep + 'osc.cfg'
        if not os.path.exists(config_file):
            self.mon.err(self,
                         'OSC Configuration file not found: ' + config_file)
            return 'error', 'OSC Configuration file nof found: ' + config_file

        self.mon.log(self, 'OSC Configuration file found at: ' + config_file)
        self.osc_config = OSCConfig()

        # reads config data
        if self.osc_config.read(config_file) == False:
            return 'error', 'failed to read osc.cfg'

        # unpack config data and initialise

        if self.osc_config.this_unit_name == '':
            return 'error', 'OSC Config -  This Unit has no name'
        if len(self.osc_config.this_unit_name.split()) > 1:
            return 'error', 'OSC config - This Unit Name not a single word: ' + self.osc_config.this_unit_name
        self.this_unit_name = self.osc_config.this_unit_name

        if self.osc_config.this_unit_ip == '':
            self.this_unit_ip = my_ip
        else:
            self.this_unit_ip = self.osc_config.this_unit_ip

        if self.osc_config.slave_enabled == 'yes':
            if not self.osc_config.listen_port.isdigit():
                return 'error', 'OSC Config - Listen port is not a positve number: ' + self.osc_config.listen_port
            self.listen_port = self.osc_config.listen_port

        if self.osc_config.master_enabled == 'yes':
            if not self.osc_config.reply_listen_port.isdigit():
                return 'error', 'OSC Config - Reply Listen port is not a positve number: ' + self.osc_config.reply_listen_port
            self.reply_listen_port = self.osc_config.reply_listen_port

            # prepare the list of slaves
            status, message = self.parse_slaves()
            if status == 'error':
                return status, message

        self.prefix = '/pipresents'
        self.this_unit = '/' + self.this_unit_name

        self.input_server = None
        self.input_reply_client = None
        self.input_st = None

        self.output_client = None
        self.output_reply_server = None
        self.output_reply_st = None

        if self.osc_config.slave_enabled == 'yes' and self.osc_config.master_enabled == 'yes' and self.listen_port == self.reply_listen_port:
            # The two listen ports are the same so use one server for input and output

            #start the client that sends commands to the slaves
            self.output_client = OSC.OSCClient()
            self.mon.log(
                self, 'sending commands to slaves and replies to master on: ' +
                self.reply_listen_port)

            #start the input+output reply server
            self.mon.log(
                self,
                'listen to commands and replies from slave units using: ' +
                self.this_unit_ip + ':' + self.reply_listen_port)
            self.output_reply_server = myOSCServer(
                (self.this_unit_ip, int(self.reply_listen_port)),
                self.output_client)
            self.add_default_handler(self.output_reply_server)
            self.add_input_handlers(self.output_reply_server)
            self.add_output_reply_handlers(self.output_reply_server)

            self.input_server = self.output_reply_server

        else:

            if self.osc_config.slave_enabled == 'yes':
                # we want this to be a slave to something else

                # start the client that sends replies to controlling unit
                self.input_reply_client = OSC.OSCClient()

                #start the input server
                self.mon.log(
                    self, 'listening to commands on: ' + self.this_unit_ip +
                    ':' + self.listen_port)
                self.input_server = myOSCServer(
                    (self.this_unit_ip, int(self.listen_port)),
                    self.input_reply_client)
                self.add_default_handler(self.input_server)
                self.add_input_handlers(self.input_server)
                # print self.pretty_list(self.input_server.getOSCAddressSpace(),'\n')

            if self.osc_config.master_enabled == 'yes':
                #we want to control other units

                #start the client that sends commands to the slaves
                self.output_client = OSC.OSCClient()
                self.mon.log(
                    self, 'sending commands to slaves on port: ' +
                    self.reply_listen_port)

                #start the output reply server
                self.mon.log(
                    self, 'listen to replies from slave units using: ' +
                    self.this_unit_ip + ':' + self.reply_listen_port)
                self.output_reply_server = myOSCServer(
                    (self.this_unit_ip, int(self.reply_listen_port)),
                    self.output_client)
                self.add_default_handler(self.output_reply_server)
                self.add_output_reply_handlers(self.output_reply_server)

        return 'normal', 'osc.cfg read'

    def terminate(self):
        if self.input_server != None:
            self.input_server.close()
        if self.output_reply_server != None:
            self.output_reply_server.close()
        self.mon.log(self, 'Waiting for Server threads to finish')
        if self.input_st != None:
            self.input_st.join()  ##!!!
        if self.output_reply_st != None:
            self.output_reply_st.join()  ##!!!
        self.mon.log(self, 'server threads closed')
        if self.input_reply_client != None:
            self.input_reply_client.close()
        if self.output_client != None:
            self.output_client.close()

    def start_server(self):
        # Start input Server
        self.mon.log(self, 'Starting input OSCServer')
        if self.input_server != None:
            self.input_st = threading.Thread(
                target=self.input_server.serve_forever)
            self.input_st.start()

        # Start output_reply server
        self.mon.log(self, 'Starting output reply OSCServer')
        if self.output_reply_server != None:
            self.output_reply_st = threading.Thread(
                target=self.output_reply_server.serve_forever)
            self.output_reply_st.start()

    def parse_slaves(self):
        name_list = self.osc_config.slave_units_name.split()
        ip_list = self.osc_config.slave_units_ip.split()
        if len(name_list) == 0:
            return 'error', 'OSC Config - List of slaves name is empty'
        if len(name_list) != len(ip_list):
            return 'error', 'OSC Config - Lengths of list of slaves name and slaves IP is different'
        self.slave_name_list = []
        self.slave_ip_list = []
        for i, name in enumerate(name_list):
            self.slave_name_list.append(name)
            self.slave_ip_list.append(ip_list[i])
        return 'normal', 'slaves parsed'

    def parse_osc_command(self, fields):
        # send message to slave unit - INTERFACE WITH pipresents
        if len(fields) < 2:
            return 'error', 'too few fields in OSC command ' + ' '.join(fields)
        to_unit_name = fields[0]
        show_command = fields[1]
        # print 'FIELDS ',fields

        # send an arbitary osc message
        if show_command == 'send':
            if len(fields) > 2:
                osc_address = fields[2]
                arg_list = []
                if len(fields) > 3:
                    arg_list = fields[3:]
            else:
                return 'error', 'OSC - wrong nmber of fields in ' + ' '.join(
                    fields)

        elif show_command in ('open', 'close', 'openexclusive'):
            if len(fields) == 3:
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = [fields[2]]
            else:
                return 'error', 'OSC - wrong number of fields in ' + ' '.join(
                    fields)

        elif show_command == 'monitor':
            if fields[2] in ('on', 'off'):
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = [fields[2]]
            else:
                self.mon.err(
                    self,
                    'OSC - illegal state in ' + show_command + ' ' + fields[2])

        elif show_command == 'event':
            if len(fields) == 3:
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = [fields[2]]

        elif show_command == 'animate':
            if len(fields) > 2:
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = fields[2:]
            else:
                return 'error', 'OSC - wrong nmber of fields in ' + ' '.join(
                    fields)

        elif show_command in ('closeall', 'exitpipresents', 'shutdownnow',
                              'reboot'):
            if len(fields) == 2:
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = []
            else:
                return 'error', 'OSC - wrong nmber of fields in ' + ' '.join(
                    fields)

        elif show_command in ('loopback', 'server-info'):
            if len(fields) == 2:
                osc_address = self.prefix + '/' + to_unit_name + '/system/' + show_command
                arg_list = []
            else:
                return 'error', 'OSC - wrong nmber of fields in ' + ' '.join(
                    fields)

        else:
            return 'error', 'OSC - unkown command in ' + ' '.join(fields)

        ip = self.find_ip(to_unit_name, self.slave_name_list,
                          self.slave_ip_list)
        if ip == '':
            return 'warn', 'OSC Unit Name not in the list of slaves: ' + to_unit_name
        self.sendto(ip, osc_address, arg_list)
        return 'normal', 'osc command sent'

    def find_ip(self, name, name_list, ip_list):
        i = 0
        for j in name_list:
            if j == name:
                break
            i = i + 1

        if i == len(name_list):
            return ''
        else:
            return ip_list[i]

    def sendto(self, ip, osc_address, arg_list):
        # print ip,osc_address,arg_list
        if self.output_client is None:
            self.mon.warn(self, 'Master not enabled, ignoring OSC command')
            return
        msg = OSC.OSCMessage()
        # print address
        msg.setAddress(osc_address)
        for arg in arg_list:
            # print arg
            msg.append(arg)

        try:
            self.output_client.sendto(msg, (ip, int(self.reply_listen_port)))
            self.mon.log(
                self,
                'Sent OSC command: ' + osc_address + ' ' + ' '.join(arg_list) +
                ' to ' + ip + ':' + self.reply_listen_port)
        except Exception as e:
            self.mon.warn(
                self, 'error in client when sending OSC command: ' + str(e))

# **************************************
# Handlers for fallback
# **************************************

    def add_default_handler(self, server):
        server.addMsgHandler('default', self.no_match_handler)

    def no_match_handler(self, addr, tags, stuff, source):
        text = "No handler for message from %s" % OSC.getUrlStr(source) + '\n'
        text += "     %s" % addr + self.pretty_list(stuff, '')
        self.mon.warn(self, text)
        return None

# **************************************
# Handlers for Slave (input)
# **************************************

    def add_input_handlers(self, server):
        server.addMsgHandler(
            self.prefix + self.this_unit + "/system/server-info",
            self.server_info_handler)
        server.addMsgHandler(self.prefix + self.this_unit + "/system/loopback",
                             self.loopback_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/open',
                             self.open_show_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/close',
                             self.close_show_handler)
        server.addMsgHandler(
            self.prefix + self.this_unit + '/core/openexclusive',
            self.openexclusive_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/closeall',
                             self.closeall_handler)
        server.addMsgHandler(
            self.prefix + self.this_unit + '/core/exitpipresents',
            self.exitpipresents_handler)
        server.addMsgHandler(
            self.prefix + self.this_unit + '/core/shutdownnow',
            self.shutdownnow_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/reboot',
                             self.reboot_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/event',
                             self.input_event_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/animate',
                             self.animate_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/monitor',
                             self.monitor_handler)

    # reply to master unit with name of this unit and commands
    def server_info_handler(self, addr, tags, stuff, source):

        msg = OSC.OSCMessage(self.prefix + '/system/server-info-reply')
        msg.append(self.this_unit_name)
        msg.append(self.input_server.getOSCAddressSpace())
        self.mon.log(self,
                     'Sent Server Info reply to %s:' % OSC.getUrlStr(source))
        return msg

    # reply to master unit with a loopback message
    def loopback_handler(self, addr, tags, stuff, source):
        msg = OSC.OSCMessage(self.prefix + '/system/loopback-reply')
        self.mon.log(self,
                     'Sent loopback reply to %s:' % OSC.getUrlStr(source))
        return msg

    def open_show_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('open ', args, 1)

    def openexclusive_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('openexclusive ', args, 1)

    def close_show_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('close ', args, 1)

    def closeall_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('closeall', args, 0)

    def monitor_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('monitor ', args, 1)

    def exitpipresents_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('exitpipresents', args, 0)

    def reboot_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('reboot', args, 0)

    def shutdownnow_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('shutdownnow', args, 0)

    def prepare_show_command_callback(self, command, args, limit):
        if len(args) == limit:
            if limit != 0:
                self.mon.sched(self, TimeOfDay.now,
                               'Received from OSC: ' + command + ' ' + args[0])
                self.show_command_callback(command + args[0])
            else:
                self.mon.sched(self, TimeOfDay.now,
                               'Received from OSC: ' + command)
                self.show_command_callback(command)
        else:
            self.mon.warn(
                self, 'OSC show command does not have ' + limit +
                ' argument - ignoring')

    def input_event_handler(self, address, tags, args, source):
        if len(args) == 1:
            self.input_event_callback(args[0], 'OSC')
        else:
            self.mon.warn(
                self, 'OSC input event does not have 1 argument - ignoring')

    def animate_handler(self, address, tags, args, source):
        if len(args) != 0:
            # delay symbol,param_type,param_values,req_time as a string
            text = '0 '
            for arg in args:
                text = text + arg + ' '
            text = text + '0'
            # print text
            self.animate_callback(text)
        else:
            self.mon.warn(self, 'OSC output event has no arguments - ignoring')


# **************************************
# Handlers for Master- replies from slaves (output)
# **************************************

# reply handlers do not have the destinatuion unit in the address as they are always sent to the originator

    def add_output_reply_handlers(self, server):
        server.addMsgHandler(self.prefix + "/system/server-info-reply",
                             self.server_info_reply_handler)
        server.addMsgHandler(self.prefix + "/system/loopback-reply",
                             self.loopback_reply_handler)

    # print result of info request from slave unit
    def server_info_reply_handler(self, addr, tags, stuff, source):
        self.mon.log(
            self, 'server info reply from slave ' + OSC.getUrlStr(source) +
            self.pretty_list(stuff, '\n'))
        print 'Received reply to Server-Info command from slave: ', OSC.getUrlStr(
            source), self.pretty_list(stuff, '\n')
        return None

    #print result of info request from slave unit
    def loopback_reply_handler(self, addr, tags, stuff, source):
        self.mon.log(
            self, 'server info reply from slave ' + OSC.getUrlStr(source) +
            self.pretty_list(stuff, '\n'))
        print 'Received reply to Loopback command from slave: ' + OSC.getUrlStr(
            source) + ' ' + self.pretty_list(stuff, '\n')
        return None

    def pretty_list(self, fields, separator):
        text = ' '
        for field in fields:
            text += str(field) + separator
        return text + '\n'
class TimeOfDay(object):

    # CLASS VARIABLES

    # change this for another language
    DAYS_OF_WEEK = [
        'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday',
        'sunday'
    ]
    """
        TimeOfDay.events is a dictionary the keys being show-refs.
        Each dictionary entry is a list of time_elements sorted by descending time
        Each time element is a list with the fields:
        0 - command
        1 - time, seconds since midnight

    """
    events = {
    }  # list of times of day used to generate callbacks, earliest first

    # executed by main program and by each object using tod
    def __init__(self):
        self.mon = Monitor()
        TimeOfDay.now = None

    # executed once from main program  only
    def init(self, pp_dir, pp_home, pp_profile, root, callback):

        # instantiate arguments
        TimeOfDay.root = root
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.callback = callback

        # init variables
        self.testing = False
        self.tod_tick = 500
        self.tick_timer = None
        TimeOfDay.now = datetime.now().replace(microsecond=0)
        # read the schedule
        self.schedule = self.open_schedule()

        #create the initial events list
        if 'simulate-time' in self.schedule and self.schedule[
                'simulate-time'] == 'yes':
            year = int(self.schedule['sim-year'])
            month = int(self.schedule['sim-month'])
            day = int(self.schedule['sim-day'])
            hour = int(self.schedule['sim-hour'])
            minute = int(self.schedule['sim-minute'])
            second = int(self.schedule['sim-second'])
            TimeOfDay.now = datetime(day=day,
                                     month=month,
                                     year=year,
                                     hour=hour,
                                     minute=minute,
                                     second=second)
            self.testing = True
            # print '\nInitial SIMULATED time',TimeOfDay.now.ctime()
            self.mon.sched(
                self, TimeOfDay.now, 'Testing is ON, Initial SIMULATED time ' +
                str(TimeOfDay.now.ctime()))
        else:
            #get the current date/time only this once
            TimeOfDay.now = datetime.now().replace(microsecond=0)
            self.mon.sched(
                self, TimeOfDay.now, 'Testing is OFF, Initial REAL time ' +
                str(TimeOfDay.now.ctime()))
            # print '\nInitial REAL time',TimeOfDay.now.ctime()
            self.testing = False
        TimeOfDay.last_now = TimeOfDay.now - timedelta(seconds=1)
        self.build_schedule_for_today(self.schedule)
        self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule())
        # if self.testing:
        # self.print_todays_schedule()
        self.build_events_lists()
        # self.print_events_lists()
        # and do exitpipresents or start any show that should be running at start up time
        self.do_catchup()

    def do_catchup(self):
        TimeOfDay.scheduler_time = TimeOfDay.now.time()
        # shutdown or exit if current time is later than time in event list
        for show_ref in TimeOfDay.events:
            if show_ref == 'pp_core':
                times = TimeOfDay.events[show_ref]
                for time_element in reversed(times):
                    # print 'now', TimeOfDay.scheduler_time, 'time from events', time_element[1], time_element[0]
                    # got past current time can give up and execute exitpipresents or closedown
                    if TimeOfDay.scheduler_time >= time_element[1]:
                        self.do_event(show_ref, time_element)
                        return 'exiting'

        # do the catchup for each real show in turn
        for show_ref in TimeOfDay.events:
            if show_ref != 'pp_core':
                # print '\n*****',show_ref
                times = TimeOfDay.events[show_ref]
                # go through the event list for a show rembering show state until the first future event is found.
                # then if last command was to start the show send it
                show_running = False
                last_start_element = []
                for time_element in reversed(times):
                    # print 'now', now_seconds, 'time from events', time_element[1], time_element[0]
                    # got past current time can give up catch up
                    if time_element[1] >= TimeOfDay.scheduler_time:
                        # print ' gone past time - break'
                        break
                    if time_element[0] == 'open':
                        last_start_element = time_element
                        # print 'open - show-running= true'
                        show_running = True
                    elif time_element[0] == 'close':
                        # print 'close - show-running= false'
                        show_running = False
                if show_running is True:
                    #if self.testing:
                    # print 'End of Catch Up Search', show_ref,last_start_element
                    self.mon.sched(
                        self, TimeOfDay.now, 'Catch up for show: ' + show_ref +
                        ' requires ' + last_start_element[0] + ' ' +
                        str(last_start_element[1]))
                    self.do_event(show_ref, last_start_element)

##                    # print 'catchup time match', show_ref
##                    # now do the inital real time command if time now matches event time
##                    for time_element in reversed(times):
##                        if time_element[1]  ==  TimeOfDay.scheduler_time:
##                            print ' do event  - catchup', TimeOfDay.scheduler_time, 'time from events', time_element[1], time_element[0]
##                            self.do_event(show_ref,time_element)
        return 'not exiting'

    # called by main program only
    def poll(self):
        if self.testing:
            poll_time = TimeOfDay.now
        else:
            poll_time = datetime.now()
        # print 'poll time: ',poll_time.time(),'scheduler time: ',TimeOfDay.now.time()
        # if poll_time != TimeOfDay.now : print 'times different ',poll_time.time(),TimeOfDay.now.time()
        # is current time greater than last time the scheduler was run
        # run in a loop to catch up because root.after can get behind when images are being rendered etc.
        # poll time can be the same twice as poll is run at half second intervals.
        catchup_time = 0
        while TimeOfDay.now <= poll_time:
            if TimeOfDay.now - TimeOfDay.last_now != timedelta(seconds=1):
                print 'POLL TIME FAILED', TimeOfDay.last_now, TimeOfDay.now
            #if catchup_time != 0:
            # print 'scheduler behind by: ',catchup_time, TimeOfDay.now.time(),poll_time.time()
            self.do_scheduler()
            TimeOfDay.last_now = TimeOfDay.now
            catchup_time += 1
            TimeOfDay.now = TimeOfDay.now + timedelta(seconds=1)
        # and loop
        if self.testing:
            self.tick_timer = TimeOfDay.root.after(1000, self.poll)
        else:
            self.tick_timer = TimeOfDay.root.after(self.tod_tick, self.poll)

    # called by main program only
    def terminate(self):
        if self.tick_timer is not None:
            TimeOfDay.root.after_cancel(self.tick_timer)
        self.clear_events_lists()

    # execute events at the appropriate time.
    # called by main program only
    def do_scheduler(self):
        # if its midnight then build the events lists for the new day
        TimeOfDay.scheduler_time = TimeOfDay.now.time()
        if TimeOfDay.scheduler_time == time(hour=0, minute=0, second=0):
            # if self.testing:
            # print 'Its midnight,  today is now', TimeOfDay.now.ctime()
            self.mon.sched(
                self, TimeOfDay.now,
                'Its midnight,  today is now ' + str(TimeOfDay.now.ctime()))
            self.build_schedule_for_today(self.schedule)
            self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule())
            # if self.testing:
            # self.print_todays_schedule()
            self.build_events_lists()
            # self.mon.sched(self,TimeOfDay.now,self.pretty_events_lists())
            # self.print_events_lists()

        # print TimeOfDay.scheduler_time
        for show_ref in TimeOfDay.events:
            # print 'scheduler time match', show_ref
            times = TimeOfDay.events[show_ref]
            # now send a command if time matches
            for time_element in reversed(times):
                # print time_element[1],TimeOfDay.scheduler_time
                if time_element[1] == TimeOfDay.scheduler_time:
                    self.do_event(show_ref, time_element)

    # execute an event
    def do_event(self, show_ref, time_element):
        self.mon.log(
            self, 'Event : ' + time_element[0] + ' ' + show_ref +
            ' required at: ' + time_element[1].isoformat())
        self.mon.sched(
            self, TimeOfDay.now, ' ToD Scheduler : ' + time_element[0] + ' ' +
            show_ref + ' required at: ' + time_element[1].isoformat())
        # if self.testing:
        # print 'Event : ' +  time_element[0] + ' ' + show_ref + ' required at: '+ time_element[1].isoformat()
        self.callback(time_element[0] + ' ' + show_ref)

#
# ************************************************
# The methods below can be called from many classes so need to operate on class variables
# ************************************************

# clear events list

    def clear_events_lists(self):
        self.mon.log(self, 'clear time of day  events list ')
        # empty event list
        TimeOfDay.events = {}

# ***********************************
# Preparing schedule and todays event list
# ************************************

    def open_schedule(self):
        # look for the schedule.json file
        # try inside profile
        filename = self.pp_profile + os.sep + "schedule.json"
        ifile = open(filename, 'rb')
        schedule = json.load(ifile)
        ifile.close()
        self.mon.log(self, "schedule.json read from " + filename)
        return schedule

    def build_schedule_for_today(self, schedule):
        # print this_day.year, this_day.month, this_day.day, TimeOfDay.DAYS_OF_WEEK[ this_day.weekday()]
        """
        self.todays_schedule is a dictionary the keys being show-refs.
        Each dictionary entry is a list of time_elements
        Each time element is a list with the fields:
        0 - command
        1 - time hour:min[:sec]

        """
        self.todays_schedule = {}
        for show in schedule['shows']:
            show_ref = show['show-ref']
            if 'everyday' in show:
                day = show['everyday']
                # print day['day']
                times = day['times']
                for time in times:
                    self.todays_schedule[show['show-ref']] = copy.deepcopy(
                        day['times'])
                # print '\nafter everyday'
                # self.print_todays_schedule()

            if 'weekday' in show:
                day = show['weekday']
                # print day['day']
                if TimeOfDay.DAYS_OF_WEEK[
                        TimeOfDay.now.weekday()] in day['day']:
                    # print 'weekday matched', TimeOfDay.DAYS_OF_WEEK[ TimeOfDay.now.weekday()]
                    times = day['times']
                    for time in times:
                        self.todays_schedule[show['show-ref']] = copy.deepcopy(
                            day['times'])
                # print '\nafter weekday'
                # self.print_todays_schedule()

            if 'monthday' in show:
                day = show['monthday']
                # print day['day']
                if TimeOfDay.now.day in map(int, day['day']):
                    # print 'monthday matched', day['day']
                    times = day['times']
                    for time in times:
                        self.todays_schedule[show['show-ref']] = copy.deepcopy(
                            day['times'])
                # print '\nafter monthday'
                # self.print_todays_schedule()

            if 'specialday' in show:
                days = show['specialday']
                # print days['day']
                for day in days['day']:
                    sdate = datetime.strptime(day, '%Y-%m-%d')
                    if sdate.year == TimeOfDay.now.year and sdate.month == TimeOfDay.now.month and sdate.day == TimeOfDay.now.day:
                        # print 'special matched', day
                        times = days['times']
                        # for time in times:
                        self.todays_schedule[show['show-ref']] = copy.deepcopy(
                            days['times'])
                # print '\nafter specialday'
                # self.print_todays_schedule()

    def build_events_lists(self):
        # builds events dictionary from todays_schedule by
        # converting times in todays schedule from hour:min:sec to datetime
        # and sorts them earliest last
        TimeOfDay.events = {}
        for show_ref in self.todays_schedule:
            # print show_ref
            times = self.todays_schedule[show_ref]
            for time_element in times:
                time_element[1] = self.parse_event_time(time_element[1])
            sorted_times = sorted(times,
                                  key=lambda time_element: time_element[1],
                                  reverse=True)
            TimeOfDay.events[show_ref] = sorted_times
            # print times

    def parse_event_time(self, time_text):
        fields = time_text.split(':')
        if len(fields) > 2:
            secs = int(fields[2])
        else:
            secs = 0
        hours = int(fields[0])
        mins = int(fields[1])
        return time(hour=hours, minute=mins, second=secs)

# *********************
# print for debug
# *********************

    def pretty_todays_schedule(self):
        op = 'Schedule For ' + TimeOfDay.now.ctime() + '\n'
        for key in self.todays_schedule:
            op += '  ' + key + '\n'
            for show in self.todays_schedule[key]:
                op += '    ' + show[0] + ':   ' + str(show[1]) + '\n'
            op += '\n'
        return op

    def pretty_events_lists(self):
        op = ' Events list for today'
        for key in self.events:
            op += '\n' + key
            for show in self.events[key]:
                op += '\n    ' + show[0] + ' ' + str(show[1].isoformat())
        return op

    def print_todays_schedule(self):
        print '\nSchedule For ' + TimeOfDay.now.ctime()
        for key in self.todays_schedule:
            print '  ' + key
            for show in self.todays_schedule[key]:
                print '    ' + show[0] + ':   ' + show[1]
            print

    def print_events_lists(self):
        print '\nevents list for today'
        for key in self.events:
            print '\n', key
            for show in self.events[key]:
                print show[0], show[1].isoformat()

    def save_schedule(self, filename):
        """ save a schedule """
        if filename == "":
            return False
        if os.name == 'nt':
            filename = string.replace(filename, '/', '\\')
        else:
            filename = string.replace(filename, '\\', '/')
        ofile = open(filename, "wb")
        json.dump(self.schedule, ofile, sort_keys=False, indent=1)
        ofile.close()
        return
Пример #7
0
class OSCDriver(object):

    # executed by main program
    def  init(self,pp_profile,manager_unit,preferred_interface,my_ip,show_command_callback,input_event_callback,animate_callback):

        self.pp_profile=pp_profile
        self.show_command_callback=show_command_callback
        self.input_event_callback=input_event_callback
        self.animate_callback=animate_callback

        self.mon=Monitor()
        config_file=self.pp_profile + os.sep +'pp_io_config'+os.sep+ 'osc.cfg'
        if not os.path.exists(config_file):
            self.mon.err(self, 'OSC Configuration file not found: '+config_file)
            return'error','OSC Configuration file nof found: '+config_file
        
        self.mon.log(self, 'OSC Configuration file found at: '+config_file)        
        self.osc_config=OSCConfig()
        
        # reads config data 
        if self.osc_config.read(config_file) ==False:
            return 'error','failed to read osc.cfg'

        # unpack config data and initialise

        if self.osc_config.this_unit_name =='':
            return 'error','OSC Config -  This Unit has no name'
        if len(self.osc_config.this_unit_name.split())>1:
            return 'error','OSC config - This Unit Name not a single word: '+self.osc_config.this_unit_name
        self.this_unit_name=self.osc_config.this_unit_name
               
                 
        if self.osc_config.this_unit_ip=='':
            self.this_unit_ip=my_ip
        else:
            self.this_unit_ip=self.osc_config.this_unit_ip

        if self.osc_config.slave_enabled == 'yes':
            if not self.osc_config.listen_port.isdigit():
                return 'error','OSC Config - Listen port is not a positve number: '+ self.osc_config.listen_port
            self.listen_port= self.osc_config.listen_port

        if self.osc_config.master_enabled == 'yes':
            if not self.osc_config.reply_listen_port.isdigit():
                return 'error','OSC Config - Reply Listen port is not a positve number: '+ self.osc_config.reply_listen_port
            self.reply_listen_port= self.osc_config.reply_listen_port

            # prepare the list of slaves
            status,message=self.parse_slaves()
            if status=='error':
                return status,message
             

        self.prefix='/pipresents'
        self.this_unit='/' + self.this_unit_name
        
        self.input_server=None
        self.input_reply_client=None
        self.input_st=None
        
        self.output_client=None
        self.output_reply_server=None
        self.output_reply_st=None


        if self.osc_config.slave_enabled == 'yes' and self.osc_config.master_enabled == 'yes' and self.listen_port == self.reply_listen_port:
            # The two listen ports are the same so use one server for input and output

            #start the client that sends commands to the slaves
            self.output_client=OSC.OSCClient()
            self.mon.log(self, 'sending commands to slaves and replies to master on: '+self.reply_listen_port)

            #start the input+output reply server
            self.mon.log(self, 'listen to commands and replies from slave units using: ' + self.this_unit_ip+':'+self.reply_listen_port)
            self.output_reply_server=myOSCServer((self.this_unit_ip,int(self.reply_listen_port)),self.output_client)
            self.add_default_handler(self.output_reply_server)
            self.add_input_handlers(self.output_reply_server)
            self.add_output_reply_handlers(self.output_reply_server)

            self.input_server=self.output_reply_server
            
        else:

            if self.osc_config.slave_enabled == 'yes':
                # we want this to be a slave to something else

                # start the client that sends replies to controlling unit
                self.input_reply_client=OSC.OSCClient()
                
                #start the input server
                self.mon.log(self, 'listening to commands on: ' + self.this_unit_ip+':'+self.listen_port)
                self.input_server=myOSCServer((self.this_unit_ip,int(self.listen_port)),self.input_reply_client)
                self.add_default_handler(self.input_server)
                self.add_input_handlers(self.input_server)
                print self.pretty_list(self.input_server.getOSCAddressSpace(),'\n')
                
            if self.osc_config.master_enabled =='yes':
                #we want to control other units
               
                #start the client that sends commands to the slaves
                self.output_client=OSC.OSCClient()
                self.mon.log(self, 'sending commands to slaves on port: '+self.reply_listen_port)

                #start the output reply server
                self.mon.log(self, 'listen to replies from slave units using: ' + self.this_unit_ip+':'+self.reply_listen_port)
                self.output_reply_server=myOSCServer((self.this_unit_ip,int(self.reply_listen_port)),self.output_client)
                self.add_default_handler(self.output_reply_server)
                self.add_output_reply_handlers(self.output_reply_server)
        
        return 'normal','osc.cfg read'


    def terminate(self):
        if self.input_server != None:
            self.input_server.close()
        if self.output_reply_server != None:
            self.output_reply_server.close()
        self.mon.log(self, 'Waiting for Server threads to finish')
        if self.input_st != None:
            self.input_st.join() ##!!!
        if self.output_reply_st != None:
            self.output_reply_st.join() ##!!!
        self.mon.log(self,'server threads closed')
        if self.input_reply_client !=None:
            self.input_reply_client.close()
        if self.output_client !=None:
            self.output_client.close()


    def start_server(self):
        # Start input Server
        self.mon.log(self,'Starting input OSCServer')
        if self.input_server != None:
            self.input_st = threading.Thread( target = self.input_server.serve_forever )
            self.input_st.start()

        # Start output_reply server
        self.mon.log(self,'Starting output reply OSCServer')
        if self.output_reply_server != None:
            self.output_reply_st = threading.Thread( target = self.output_reply_server.serve_forever )
            self.output_reply_st.start()

    def parse_slaves(self):
        name_list=self.osc_config.slave_units_name.split()
        ip_list=self.osc_config.slave_units_ip.split()
        if len(name_list)==0:
            return 'error','OSC Config - List of slaves name is empty'
        if len(name_list) != len(ip_list):
            return 'error','OSC Config - Lengths of list of slaves name and slaves IP is different'       
        self.slave_name_list=[]
        self.slave_ip_list=[]
        for i, name in enumerate(name_list):
            self.slave_name_list.append(name)
            self.slave_ip_list.append(ip_list[i])
        return 'normal','slaves parsed'


    def parse_osc_command(self,fields):
    # send message to slave unit - INTERFACE WITH pipresents
        if len(fields) <2:
               return 'error','too few fields in OSC command '+' '.join(fields)
        to_unit_name=fields[0]
        show_command=fields[1]
        # print 'FIELDS ',fields
        
        # send an arbitary osc message            
        if show_command == 'send':
            if len(fields)>2:
                osc_address= fields[2]
                arg_list=[]
                if len(fields)>3:
                    arg_list=fields[3:]
            else:
                return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields)

        elif show_command in ('open','close','openexclusive'):
            if len(fields)==3:
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list= [fields[2]]
            else:
                return 'error','OSC - wrong number of fields in '+ ' '.join(fields)
                
        elif show_command =='monitor':
            if fields[2] in ('on','off'):
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list=[fields[2]]
            else:
                self.mon.err(self,'OSC - illegal state in '+ show_command + ' '+fields[2])
        
        elif show_command =='event':                
            if len(fields)==3:
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list= [fields[2]]


        elif show_command == 'animate':                
            if len(fields)>2:
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list= fields[2:]
            else:
                return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields)
            
        elif show_command in ('closeall','exitpipresents','shutdownnow','reboot'):
            if len(fields)==2:
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list= []
            else:
                return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields)

        elif show_command in ('loopback','server-info'):
            if len(fields)==2:
                osc_address=self.prefix+'/'+ to_unit_name + '/system/'+ show_command
                arg_list= []
            else:
                return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields)
            
        else:
            return 'error','OSC - unkown command in '+ ' '.join(fields)

        
        ip=self.find_ip(to_unit_name,self.slave_name_list,self.slave_ip_list)
        if ip=='':
            return 'warn','OSC Unit Name not in the list of slaves: '+ to_unit_name
        self.sendto(ip,osc_address,arg_list)
        return 'normal','osc command sent'


    def find_ip(self,name,name_list,ip_list):
        i=0
        for j in name_list:
            if j == name:
                break
            i=i+1
            
        if i==len(name_list):
            return ''
        else:
            return ip_list[i]
                    

    def sendto(self,ip,osc_address,arg_list):
        # print ip,osc_address,arg_list
        if self.output_client is None:
            self.mon.warn(self,'Master not enabled, ignoring OSC command')
            return
        msg = OSC.OSCMessage()
        # print address
        msg.setAddress(osc_address)
        for arg in arg_list:
            # print arg
            msg.append(arg)
            
        try:
            self.output_client.sendto(msg,(ip,int(self.reply_listen_port)))
            self.mon.log(self,'Sent OSC command: '+osc_address+' '+' '.join(arg_list) + ' to '+ ip +':'+self.reply_listen_port)
        except Exception as e:
            self.mon.warn(self,'error in client when sending OSC command: '+ str(e))


# **************************************
# Handlers for fallback
# **************************************

    def add_default_handler(self,server):
        server.addMsgHandler('default', self.no_match_handler)

    def no_match_handler(self,addr, tags, stuff, source):
        text= "No handler for message from %s" % OSC.getUrlStr(source)+'\n'
        text+= "     %s" % addr+ self.pretty_list(stuff,'')
        self.mon.warn(self,text)
        return None

# **************************************
# Handlers for Slave (input)
# **************************************    

    def add_input_handlers(self,server):
        server.addMsgHandler(self.prefix + self.this_unit+"/system/server-info", self.server_info_handler)
        server.addMsgHandler(self.prefix + self.this_unit+"/system/loopback", self.loopback_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/open', self.open_show_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/close', self.close_show_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/openexclusive', self.openexclusive_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/closeall', self.closeall_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/exitpipresents', self.exitpipresents_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/shutdownnow', self.shutdownnow_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/reboot', self.reboot_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/event', self.input_event_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/animate', self.animate_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/monitor', self.monitor_handler)


    # reply to master unit with name of this unit and commands
    def server_info_handler(self,addr, tags, stuff, source):

        msg = OSC.OSCMessage(self.prefix+'/system/server-info-reply')
        msg.append(self.this_unit_name)
        msg.append(self.input_server.getOSCAddressSpace())
        self.mon.log(self,'Sent Server Info reply to %s:' % OSC.getUrlStr(source))
        return msg


    # reply to master unit with a loopback message
    def loopback_handler(self,addr, tags, stuff, source):
        msg = OSC.OSCMessage(self.prefix+'/system/loopback-reply')
        self.mon.log(self,'Sent loopback reply to %s:' % OSC.getUrlStr(source))
        return msg


 
    def open_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('open ',args,1)

    def openexclusive_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('openexclusive ',args,1)
        
    def close_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('close ', args,1)

    def closeall_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('closeall',args,0)

    def monitor_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('monitor ', args,1)

    def exitpipresents_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('exitpipresents',args,0)

    def reboot_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('reboot',args,0)

    def shutdownnow_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('shutdownnow',args,0)

    def prepare_show_command_callback(self,command,args,limit):
        if len(args) == limit:
            if limit !=0:
                self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command + ' ' +args[0])
                self.show_command_callback(command+args[0])
            else:
                self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command)
                self.show_command_callback(command)                
        else:
            self.mon.warn(self,'OSC show command does not have '+limit +' argument - ignoring')  

    def input_event_handler(self,address, tags, args, source):
        if len(args) == 1:
            self.input_event_callback(args[0],'OSC')
        else:
            self.mon.warn(self,'OSC input event does not have 1 argument - ignoring')    


    def animate_handler(self,address, tags, args, source):
        if len(args) !=0:
            # delay symbol,param_type,param_values,req_time as a string
            text='0 '
            for arg in args:
                text= text+ arg + ' '
            text = text + '0'
            print text
            self.animate_callback(text)
        else:
            self.mon.warn(self,'OSC output event has no arguments - ignoring')      

# **************************************
# Handlers for Master- replies from slaves (output)
# **************************************

    # reply handlers do not have the destinatuion unit in the address as they are always sent to the originator
    def add_output_reply_handlers(self,server):
        server.addMsgHandler(self.prefix+"/system/server-info-reply", self.server_info_reply_handler)
        server.addMsgHandler(self.prefix+"/system/loopback-reply", self.loopback_reply_handler)
        
        
    # print result of info request from slave unit
    def server_info_reply_handler(self,addr, tags, stuff, source):
        self.mon.log(self,'server info reply from slave '+OSC.getUrlStr(source)+ self.pretty_list(stuff,'\n'))
        print 'Received reply to Server-Info command from slave: ',OSC.getUrlStr(source), self.pretty_list(stuff,'\n')
        return None

    #print result of info request from slave unit
    def loopback_reply_handler(self,addr, tags, stuff, source):
        self.mon.log(self,'server info reply from slave '+OSC.getUrlStr(source)+ self.pretty_list(stuff,'\n'))
        print 'Received reply to Loopback command from slave: ' + OSC.getUrlStr(source)+ ' '+ self.pretty_list(stuff,'\n')
        return None


    def pretty_list(self,fields, separator):
        text=' '
        for field in fields:
            text += str(field) + separator
        return text+'\n'
Пример #8
0
class OSCDriver(object):

    # executed by main program
    def  init(self,pp_profile,show_command_callback,input_event_callback,output_event_callback):

        self.pp_profile=pp_profile
        self.show_command_callback=show_command_callback
        self.input_event_callback=input_event_callback
        self.output_event_callback=output_event_callback

        self.mon=Monitor()
        config_file=self.pp_profile + os.sep +'pp_io_config'+os.sep+ 'osc.cfg'
        if not os.path.exists(config_file):
            self.mon.err(self, 'OSC Configuration file nof found: '+config_file)
            return'error','OSC Configuration file nof found: '+config_file
        
        self.mon.log(self, 'OSC Configuration file found at: '+config_file)        
        self.options=OSCConfig()
        # only reads the  data for required unit_type 
        if self.options.read(config_file) ==False:
            return 'error','failed to read osc.cfg'

        self.prefix='/pipresents'
        self.this_unit='/' + self.options.this_unit_name
        self.this_unit_type = self.options.this_unit_type

        self.reply_client=None
        self.command_client=None
        self.client=None
        self.server=None

        if self.this_unit_type not in ('master','slave','master+slave'):
            return 'error','this unit type not known: '+self.this_unit_type

        if self.this_unit_type in('slave','master+slave'):
            #start the client that sends replies to controlling unit
            self.reply_client=OSC.OSCClient()
            self.mon.log(self, 'sending replies to controller at: '+self.options.controlled_by_ip+':'+self.options.controlled_by_port)
            self.reply_client.connect((self.options.controlled_by_ip,int(self.options.controlled_by_port)))
            self.mon.log(self,'sending repiles to: '+ str(self.reply_client))
            self.client=self.reply_client
            
        if self.this_unit_type in ('master','master+slave'):
            #start the client that sends commands to the controlled unit
            self.command_client=OSC.OSCClient()
            self.command_client.connect((self.options.controlled_unit_1_ip,int(self.options.controlled_unit_1_port)))
            self.mon.log(self, 'sending commands to controled unit at: '+self.options.controlled_unit_1_ip+':'+self.options.controlled_unit_1_port)
            self.mon.log(self,'sending commands to: '+str(self.command_client))
            self.client=self.command_client

        #start the listener's server
        self.mon.log(self, 'listen to commands from controlled by unit and replies from controlled units on: ' + self.options.this_unit_ip+':'+self.options.this_unit_port)
        self.server=myOSCServer((self.options.this_unit_ip,int(self.options.this_unit_port)),self.client)
        # return_port=int(self.options.controlled_by_port)
        self.mon.log(self,'listening on: '+str(self.server))
        self.add_initial_handlers()
        return 'normal','osc.cfg read'

    def terminate(self):
        if self.server != None:
            self.server.close()
        self.mon.log(self, 'Waiting for Server-thread to finish')
        if self.st != None:
            self.st.join() ##!!!
        self.mon.log(self,'server thread closed')
        self.client.close()



    def start_server(self):
        # Start OSCServer
        self.mon.log(self,'Starting OSCServer')
        self.st = threading.Thread( target = self.server.serve_forever )
        self.st.start()


    def add_initial_handlers(self):
        self.server.addMsgHandler('default', self.no_match_handler)        
        self.server.addMsgHandler(self.prefix+self.this_unit+"/system/server-info", self.server_info_handler)
        self.server.addMsgHandler(self.prefix+self.this_unit+"/system/loopback", self.loopback_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/open', self.open_show_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/close', self.close_show_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/exitpipresents', self.exitpipresents_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/shutdownnow', self.shutdownnow_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/event', self.input_event_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/output', self.output_event_handler)


    def no_match_handler(self,addr, tags, stuff, source):
        self.mon.warn(self,"no match for osc msg with addr : %s" % addr)
        return None


    def server_info_handler(self,addr, tags, stuff, source):
        msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/server-info-reply')
        msg.append('Unit: '+ self.options.this_unit_name)
        return msg


    def loopback_handler(self,addr, tags, stuff, source):
         # send a reply to the client.
        msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/loopback-reply')
        return msg

    
    def open_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('open ',args,1)
        
    def close_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('close ', args,1)

    def exitpipresents_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('exitpipresents',args,0)

    def shutdownnow_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('shutdownnow',args,0)

    def prepare_show_command_callback(self,command,args,limit):
        if len(args) == limit:
            if limit !=0:
                self.mon.sched(self,'Received from OSC: '+ command + ' ' +args[0])
                self.show_command_callback(command+args[0])
            else:
                self.mon.sched(self,'Received from OSC: '+ command)
                self.show_command_callback(command)                
        else:
            self.mon.warn(self,'OSC show command does not have '+limit +' argument - ignoring')  

    def input_event_handler(self,address, tags, args, source):
        if len(args) == 1:
            self.input_event_callback(args[0],'OSC')
        else:
            self.mon.warn(self,'OSC input event does not have 1 argument - ignoring')    


    def output_event_handler(self,address, tags, args, source):
        if len(args) !=0:
            # delay symbol,param_type,param_values,req_time as a string
            text='0 '
            for arg in args:
                text= text+ arg + ' '
            text = text + '0'
            self.output_event_callback(text)
        else:
            self.mon.warn(self,'OSC output event has no arguments - ignoring')      


    #send messages to controlled units
    # parses the message string into fields and sends - NO error checking
    def send_command(self,text):
        self.mon.log(self,'send OSC Command: ' + text )
        if self.this_unit_type not in ('master','remote','master+slave'):
            self.mon.warn(self,'Unit is not an OSC Master, ignoring command')
            return
        fields=text.split()
        address = fields[0]
        # print 'ADDRESS'+address
        address_fields=address[1:].split('/')
        if address_fields[0] != 'pipresents':
            self.mon.warn(self,'prefix is not pipresents: '+address_fields[0])
        if address_fields[1] != self.options.controlled_unit_1_name:
             self.mon.warn(self,'not sending OSC to the controlled unit: ' +self.options.controlled_unit_1_name + ' is '+ address_fields[1])
        arg_list=fields[1:]
        self.send(address,arg_list)


    def send(self,address,arg_list):
        # print self.command_client
        msg = OSC.OSCMessage()
        # print address
        msg.setAddress(address)
        for arg in arg_list:
            # print arg
            msg.append(arg)
        self.command_client.send(msg)    
Пример #9
0
class TimeOfDay(object):

    # CLASS VARIABLES

    # change this for another language
    DAYS_OF_WEEK=['monday','tuesday','wednesday','thursday','friday','saturday','sunday']

    """
        TimeOfDay.events is a dictionary the keys being show-refs.
        Each dictionary entry is a list of time_elements sorted by descending time
        Each time element is a list with the fields:
        0 - command
        1 - time, seconds since midnight

    """
    events={}  # list of times of day used to generate callbacks, earliest first


    # executed by main program and by each object using tod
    def __init__(self):
        self.mon=Monitor()


     # executed once from main program  only 
    def init(self,pp_dir,pp_home,pp_profile,root,callback):
       
        # instantiate arguments
        TimeOfDay.root=root
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile
        self.callback=callback

        # init variables
        self.testing=False
        self.tod_tick=500
        self.tick_timer=None
        TimeOfDay.now = datetime.now().replace(microsecond=0)
        # read the schedule
        self.schedule=self.open_schedule()

        #create the initial events list
        if 'simulate-time' in self.schedule and self.schedule['simulate-time']=='yes':
            year= int(self.schedule['sim-year'])
            month= int(self.schedule['sim-month'])
            day = int(self.schedule['sim-day'])
            hour= int(self.schedule['sim-hour'])
            minute= int(self.schedule['sim-minute'])
            second= int(self.schedule['sim-second'])
            TimeOfDay.now = datetime(day = day, month =month, year=year,hour=hour, minute=minute, second=second)
            self.testing=True
            # print '\nInitial SIMULATED time',TimeOfDay.now.ctime()
            self.mon.sched(self,'Testing is ON, Initial SIMULATED time ' + str(TimeOfDay.now.ctime()))
        else:
            #get the current date/time only this once
            TimeOfDay.now = datetime.now().replace(microsecond=0)
            # print '\nInitial REAL time',TimeOfDay.now.ctime()
            self.testing=False
        TimeOfDay.last_now = TimeOfDay.now - timedelta(seconds=1)           
        self.build_schedule_for_today(self.schedule)
        self.mon.sched(self,self.pretty_todays_schedule())
        # if self.testing:
            # self.print_todays_schedule()
        self.build_events_lists()
        # self.print_events_lists()
        # and do exitpipresents or start any show that should be running at start up time
        self.do_catchup()


    def do_catchup(self):
            TimeOfDay.scheduler_time=TimeOfDay.now.time()
            # shutdown or exit if current time is later than time in event list
            for show_ref in TimeOfDay.events:
                if show_ref == 'pp_core':
                    times = TimeOfDay.events[show_ref]
                    for time_element in reversed(times):
                        # print 'now', TimeOfDay.scheduler_time, 'time from events', time_element[1], time_element[0]
                        # got past current time can give up and execute exitpipresents or closedown
                        if   TimeOfDay.scheduler_time >= time_element[1]:
                            self.do_event(show_ref,time_element)
                            return 'exiting'

                    
            # do the catchup for each real show in turn
            for show_ref in TimeOfDay.events:
                 if show_ref != 'pp_core':
                    # print '\n*****',show_ref
                    times = TimeOfDay.events[show_ref]
                    # go through the event list for a show rembering show state until the first future event is found.
                    # then if last command was to start the show send it
                    show_running=False
                    last_start_element=[]
                    for time_element in reversed(times):
                        # print 'now', now_seconds, 'time from events', time_element[1], time_element[0]
                        # got past current time can give up catch up
                        if time_element[1]  >=  TimeOfDay.scheduler_time:
                            # print ' gone past time - break'
                            break
                        if time_element[0] == 'open':
                            last_start_element=time_element
                            # print 'open - show-running= true' 
                            show_running=True
                        elif time_element[0]=='close':
                            # print 'close - show-running= false' 
                            show_running=False
                    if show_running is True:
                        #if self.testing:
                            # print 'End of Catch Up Search', show_ref,last_start_element
                        self.mon.sched(self,'Catch up for show: ' + show_ref +' requires '+ last_start_element[0] + ' ' +str(last_start_element[1]))
                        self.do_event(show_ref,last_start_element)

##                    # print 'catchup time match', show_ref
##                    # now do the inital real time command if time now matches event time
##                    for time_element in reversed(times):
##                        if time_element[1]  ==  TimeOfDay.scheduler_time:
##                            print ' do event  - catchup', TimeOfDay.scheduler_time, 'time from events', time_element[1], time_element[0]
##                            self.do_event(show_ref,time_element)
            return 'not exiting'        

    # called by main program only         
    def poll(self):
        if self.testing:
            poll_time=TimeOfDay.now
        else:
            poll_time=datetime.now()
        # print 'poll time: ',poll_time.time(),'scheduler time: ',TimeOfDay.now.time()
        # if poll_time != TimeOfDay.now : print 'times different ',poll_time.time(),TimeOfDay.now.time()
        # is current time greater than last time the scheduler was run
        # run in a loop to catch up because root.after can get behind when images are being rendered etc.
        # poll time can be the same twice as poll is run at half second intervals.
        catchup_time=0
        while TimeOfDay.now<=poll_time:
            if  TimeOfDay.now-TimeOfDay.last_now != timedelta(seconds=1):
                print 'POLL TIME FAILED', TimeOfDay.last_now, TimeOfDay.now
            #if catchup_time != 0:
               # print 'scheduler behind by: ',catchup_time, TimeOfDay.now.time(),poll_time.time()
            self.do_scheduler()
            TimeOfDay.last_now=TimeOfDay.now
            catchup_time+=1
            TimeOfDay.now = TimeOfDay.now + timedelta(seconds=1)
        # and loop
        if self.testing:
            self.tick_timer=TimeOfDay.root.after(1000,self.poll)
        else:
            self.tick_timer=TimeOfDay.root.after(self.tod_tick,self.poll)


     # called by main program only           
    def terminate(self):
        if self.tick_timer is not None:
            TimeOfDay.root.after_cancel(self.tick_timer)
        self.clear_events_lists()


        
    # execute events at the appropriate time.
    # called by main program only   
    def do_scheduler(self):
        # if its midnight then build the events lists for the new day
        TimeOfDay.scheduler_time=TimeOfDay.now.time()
        if  TimeOfDay.scheduler_time == time(hour=0,minute=0,second=0):
            # if self.testing:
                # print 'Its midnight,  today is now', TimeOfDay.now.ctime()
            self.mon.sched(self,'Its midnight,  today is now ' + str(TimeOfDay.now.ctime()))
            self.build_schedule_for_today(self.schedule)
            self.mon.sched(self,self.pretty_todays_schedule())
            # if self.testing:
                # self.print_todays_schedule()
            self.build_events_lists()
            # self.mon.sched(self,self.pretty_events_lists())
            # self.print_events_lists()

        # print TimeOfDay.scheduler_time
        for show_ref in TimeOfDay.events:      
            # print 'scheduler time match', show_ref
            times = TimeOfDay.events[show_ref]
            # now send a command if time matches
            for time_element in reversed(times):
                # print time_element[1],TimeOfDay.scheduler_time
                if time_element[1]  ==  TimeOfDay.scheduler_time:
                    self.do_event(show_ref,time_element)


    # execute an event
    def do_event(self,show_ref,time_element):
        self.mon.log (self,'Event : '  + time_element[0] +  ' ' +  show_ref + ' required at: ' + time_element[1].isoformat())
        self.mon.sched (self,' ToD Scheduler : '  + time_element[0] +  ' ' +  show_ref + ' required at: ' + time_element[1].isoformat())
        # if self.testing:
            # print 'Event : ' +  time_element[0] + ' ' + show_ref + ' required at: '+ time_element[1].isoformat()
        self.callback(time_element[0]  + ' ' + show_ref)


#
# ************************************************
# The methods below can be called from many classes so need to operate on class variables
# ************************************************

    # clear events list
    def clear_events_lists(self):
        self.mon.log(self,'clear time of day  events list ')
        # empty event list
        TimeOfDay.events={}


# ***********************************
# Preparing schedule and todays event list
# ************************************

    def open_schedule(self):
        # look for the schedule.json file
        # try inside profile
        filename=self.pp_profile+os.sep+"schedule.json"
        ifile  = open(filename, 'rb')
        schedule= json.load(ifile)
        ifile.close()
        self.mon.log(self,"schedule.json read from "+ filename)
        return schedule

    def build_schedule_for_today(self,schedule):
        # print this_day.year, this_day.month, this_day.day, TimeOfDay.DAYS_OF_WEEK[ this_day.weekday()]
        """
        self.todays_schedule is a dictionary the keys being show-refs.
        Each dictionary entry is a list of time_elements
        Each time element is a list with the fields:
        0 - command
        1 - time hour:min[:sec]

        """
        self.todays_schedule={}
        for show in schedule['shows']:
            show_ref=show['show-ref']
            if 'everyday' in show:
                day=show['everyday']
                # print day['day']
                times=day['times']
                for time in times:
                    self.todays_schedule[show['show-ref']]=copy.deepcopy(day['times'])
                # print '\nafter everyday'
                # self.print_todays_schedule()
      

            if 'weekday' in show:
                day=show['weekday']
                # print day['day']
                if  TimeOfDay.DAYS_OF_WEEK[ TimeOfDay.now.weekday()] in day['day'] :
                    # print 'weekday matched', TimeOfDay.DAYS_OF_WEEK[ TimeOfDay.now.weekday()]
                    times=day['times']
                    for time in times:
                        self.todays_schedule[show['show-ref']]=copy.deepcopy(day['times'])
                # print '\nafter weekday'
                # self.print_todays_schedule()


            if 'monthday' in show:
                day=show['monthday']
                # print day['day']
                if  TimeOfDay.now.day in map(int,day['day']):
                    # print 'monthday matched', day['day']
                    times=day['times']
                    for time in times:
                        self.todays_schedule[show['show-ref']]=copy.deepcopy(day['times'])
                # print '\nafter monthday'
                # self.print_todays_schedule()
                     
            if 'specialday' in show:
                days=show['specialday']
               # print days['day']
                for day in days['day']:
                    sdate=datetime.strptime(day,'%Y-%m-%d')
                    if sdate.year == TimeOfDay.now.year and sdate.month==TimeOfDay.now.month and sdate.day == TimeOfDay.now.day:
                        # print 'special matched', day
                        times=days['times']
                        # for time in times:
                        self.todays_schedule[show['show-ref']]=copy.deepcopy(days['times'])
                # print '\nafter specialday'
                # self.print_todays_schedule()                          
                

    def build_events_lists(self):
        # builds events dictionary from todays_schedule by
        # converting times in todays schedule from hour:min:sec to datetime
        # and sorts them earliest last
        TimeOfDay.events={}
        for show_ref in self.todays_schedule:
            # print show_ref
            times = self.todays_schedule[show_ref]
            for time_element in times:
                time_element[1]=self.parse_event_time(time_element[1])
            sorted_times=sorted(times,key = lambda time_element: time_element[1],reverse=True)
            TimeOfDay.events[show_ref]=sorted_times
            # print times


    def parse_event_time(self,time_text):
        fields=time_text.split(':')
        if len(fields)>2:
            secs=int(fields[2])
        else:
            secs=0
        hours=int(fields[0])
        mins=int(fields[1])
        return time(hour=hours,minute=mins,second=secs)


# *********************
# print for debug
# *********************

    def pretty_todays_schedule(self):
        op='Schedule For ' + TimeOfDay.now.ctime() + '\n'
        for key in self.todays_schedule:
            op += '  '+ key + '\n'
            for show in self.todays_schedule[key]:
                op += '    '+show[0]+ ':   '+ str(show[1])+ '\n'
            op +='\n'
        return op

    def pretty_events_lists(self):
        op = ' Events list for today'
        for key in self.events:
            op += '\n' + key
            for show in self.events[key]:
                op += '\n    ' + show[0] + ' ' + str(show[1].isoformat())
        return op

                    
    def print_todays_schedule(self):
        print '\nSchedule For '+ TimeOfDay.now.ctime()
        for key in self.todays_schedule:
            print '  '+key
            for show in self.todays_schedule[key]:
                print '    '+show[0]+ ':   '+show[1]
            print

    def print_events_lists(self):
        print '\nevents list for today'
        for key in self.events:
            print '\n',key
            for show in self.events[key]:
                print show[0],show[1].isoformat()
                

    def save_schedule(self,filename):
        """ save a schedule """
        if filename=="":
            return False
        if os.name=='nt':
            filename = string.replace(filename,'/','\\')
        else:
            filename = string.replace(filename,'\\','/')
        ofile  = open(filename, "wb")
        json.dump(self.schedule,ofile,sort_keys= False,indent=1)
        ofile.close()
        return
Пример #10
0
class OSCDriver(object):

    # executed by main program
    def  init(self,pp_profile,show_command_callback,input_event_callback,output_event_callback):

        self.pp_profile=pp_profile
        self.show_command_callback=show_command_callback
        self.input_event_callback=input_event_callback
        self.output_event_callback=output_event_callback

        self.mon=Monitor()
        config_file=self.pp_profile + os.sep +'pp_io_config'+os.sep+ 'osc.cfg'
        if not os.path.exists(config_file):
            self.mon.err(self, 'OSC Configuration file not found: '+config_file)
            return'error','OSC Configuration file nof found: '+config_file
        
        self.mon.log(self, 'OSC Configuration file found at: '+config_file)        
        self.options=OSCConfig()
        # only reads the  data for required unit_type 
        if self.options.read(config_file) ==False:
            return 'error','failed to read osc.cfg'

        self.prefix='/pipresents'
        self.this_unit='/' + self.options.this_unit_name
        self.this_unit_type = self.options.this_unit_type

        self.reply_client=None
        self.command_client=None
        self.client=None
        self.server=None

        if self.this_unit_type not in ('master','slave','master+slave'):
            return 'error','OSC config, this unit type not known: '+self.this_unit_type

        if self.this_unit_type in('slave','master+slave'):
            #start the client that sends replies to controlling unit
            self.reply_client=OSC.OSCClient()
            self.mon.log(self, 'sending replies to controller at: '+self.options.controlled_by_ip+':'+self.options.controlled_by_port)
            self.reply_client.connect((self.options.controlled_by_ip,int(self.options.controlled_by_port)))
            self.mon.log(self,'sending repiles to: '+ str(self.reply_client))
            self.client=self.reply_client
            
        if self.this_unit_type in ('master','master+slave'):
            #start the client that sends commands to the controlled unit
            self.command_client=OSC.OSCClient()
            self.command_client.connect((self.options.controlled_unit_1_ip,int(self.options.controlled_unit_1_port)))
            self.mon.log(self, 'sending commands to controled unit at: '+self.options.controlled_unit_1_ip+':'+self.options.controlled_unit_1_port)
            self.mon.log(self,'sending commands to: '+str(self.command_client))
            self.client=self.command_client

        #start the listener's server
        self.mon.log(self, 'listen to commands from controlled by unit and replies from controlled units on: ' + self.options.this_unit_ip+':'+self.options.this_unit_port)
        self.server=myOSCServer((self.options.this_unit_ip,int(self.options.this_unit_port)),self.client)
        # return_port=int(self.options.controlled_by_port)
        self.mon.log(self,'listening on: '+str(self.server))
        self.add_initial_handlers()
        return 'normal','osc.cfg read'

    def terminate(self):
        if self.server != None:
            self.server.close()
        self.mon.log(self, 'Waiting for Server-thread to finish')
        if self.st != None:
            self.st.join() ##!!!
        self.mon.log(self,'server thread closed')
        self.client.close()



    def start_server(self):
        # Start OSCServer
        self.mon.log(self,'Starting OSCServer')
        self.st = threading.Thread( target = self.server.serve_forever )
        self.st.start()


    def add_initial_handlers(self):
        self.server.addMsgHandler('default', self.no_match_handler)        
        self.server.addMsgHandler(self.prefix+self.this_unit+"/system/server-info", self.server_info_handler)
        self.server.addMsgHandler(self.prefix+self.this_unit+"/system/loopback", self.loopback_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/open', self.open_show_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/close', self.close_show_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/exitpipresents', self.exitpipresents_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/shutdownnow', self.shutdownnow_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/event', self.input_event_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/output', self.output_event_handler)


    def no_match_handler(self,addr, tags, stuff, source):
        self.mon.warn(self,"no match for osc msg with addr : %s" % addr)
        return None


    def server_info_handler(self,addr, tags, stuff, source):
        msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/server-info-reply')
        msg.append('Unit: '+ self.options.this_unit_name)
        return msg


    def loopback_handler(self,addr, tags, stuff, source):
         # send a reply to the client.
        msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/loopback-reply')
        return msg

    
    def open_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('open ',args,1)
        
    def close_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('close ', args,1)

    def exitpipresents_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('exitpipresents',args,0)

    def shutdownnow_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('shutdownnow',args,0)

    def prepare_show_command_callback(self,command,args,limit):
        if len(args) == limit:
            if limit !=0:
                self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command + ' ' +args[0])
                self.show_command_callback(command+args[0])
            else:
                self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command)
                self.show_command_callback(command)                
        else:
            self.mon.warn(self,'OSC show command does not have '+limit +' argument - ignoring')  

    def input_event_handler(self,address, tags, args, source):
        if len(args) == 1:
            self.input_event_callback(args[0],'OSC')
        else:
            self.mon.warn(self,'OSC input event does not have 1 argument - ignoring')    


    def output_event_handler(self,address, tags, args, source):
        if len(args) !=0:
            # delay symbol,param_type,param_values,req_time as a string
            text='0 '
            for arg in args:
                text= text+ arg + ' '
            text = text + '0'
            self.output_event_callback(text)
        else:
            self.mon.warn(self,'OSC output event has no arguments - ignoring')      


    #send messages to controlled units
    # parses the message string into fields and sends - NO error checking
    def send_command(self,text):
        self.mon.log(self,'send OSC Command: ' + text )
        if self.this_unit_type not in ('master','remote','master+slave'):
            self.mon.warn(self,'Unit is not an OSC Master, ignoring command')
            return
        fields=text.split()
        address = fields[0]
        # print 'ADDRESS'+address
        address_fields=address[1:].split('/')
        if address_fields[0] != 'pipresents':
            self.mon.warn(self,'prefix is not pipresents: '+address_fields[0])
        if address_fields[1] != self.options.controlled_unit_1_name:
             self.mon.warn(self,'not sending OSC to the controlled unit: ' +self.options.controlled_unit_1_name + ' is '+ address_fields[1])
        arg_list=fields[1:]
        self.send(address,arg_list)


    def send(self,address,arg_list):
        # print self.command_client
        msg = OSC.OSCMessage()
        # print address
        msg.setAddress(address)
        for arg in arg_list:
            # print arg
            msg.append(arg)
        self.command_client.send(msg)    
Пример #11
0
class ShowManager(object):
    """
    ShowManager manages PiPresents' concurrent shows. It does not manage sub-shows or child-shows but has a bit of common code to initilise them
    concurrent shows are always top level (level 0) shows:
    They can be opened/closed  by the start show(open only) or by 'open/close myshow' in the Show Control field of players, by time of day sceduler or by OSC
    
   Two shows with the same show reference cannot be run concurrently as there is no way to reference an individual instance.
   However a workaround is to make the secong instance a subshow of a mediashow with a different reference.

    """
    
    # Declare class variables
    shows=[]
    canvas=None    #canvas for all shows
    shutdown_required=False
    SHOW_TEMPLATE=['',None]
    SHOW_REF= 0   # show-reference  - name of the show as in editor
    SHOW_OBJ = 1   # the python object
    showlist=[]


    # Initialise class variables, first time through only in pipresents.py

    def init(self,canvas,all_shows_ended_callback,command_callback,showlist):
        ShowManager.all_shows_ended_callback=all_shows_ended_callback
        ShowManager.shows=[]
        ShowManager.shutdown_required=False
        ShowManager.canvas=canvas
        ShowManager.command_callback = command_callback
        ShowManager.showlist = showlist

# **************************************
# functions to manipulate show register
# **************************************

    def register_shows(self):
        for show in ShowManager.showlist.shows():
            if show['show-ref'] != 'start':
                reason,message=self.register_show(show['show-ref'])
                if reason =='error':
                    return reason,message
        return 'normal','shows regiistered'
            

    def register_show(self,ref):
        registered=self.show_registered(ref)
        if registered == -1:
            ShowManager.shows.append(copy.deepcopy(ShowManager.SHOW_TEMPLATE))
            index=len(ShowManager.shows)-1
            ShowManager.shows[index][ShowManager.SHOW_REF]=ref
            ShowManager.shows[index][ShowManager.SHOW_OBJ]=None
            self.mon.trace(self,' - register show: show_ref = ' + ref + ' index = ' + str(index))
            return'normal','show registered'
        else:
            # self.mon.err(self, ' more than one show in showlist with show-ref: ' + ref )
            return 'error', ' more than one show in showlist with show-ref: ' + ref 
        
    # is the show registered?
    # can be used to return the index to the show
    def show_registered(self,show_ref):
        index=0
        for show in ShowManager.shows:
            if show[ShowManager.SHOW_REF] == show_ref:
                return index
            index+=1
        return -1

    # needs calling program to check that the show is not already running
    def set_running(self,index,show_obj):
        ShowManager.shows[index][ShowManager.SHOW_OBJ]=show_obj
        self.mon.trace(self, 'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] + ' show_id= ' + str(index))


    # is the show running?
    def show_running(self,index):
        if ShowManager.shows[index][ShowManager.SHOW_OBJ] is not None:
            return ShowManager.shows[index][ShowManager.SHOW_OBJ]
        else:
            return None

    def set_exited(self,index):
        ShowManager.shows[index][ShowManager.SHOW_OBJ]=None
        self.mon.trace(self,'show_ref= ' + ShowManager.shows[index][ShowManager.SHOW_REF] + ' show_id= ' + str(index))


    # are all shows exited?
    def all_shows_exited(self):
        all_exited=True
        for show in ShowManager.shows:
            if show[ShowManager.SHOW_OBJ] is not None:
                all_exited=False
        return all_exited


    # fromat for printing
    def pretty_shows(self):
        shows='\n'
        for show in ShowManager.shows:
            shows += show[ShowManager.SHOW_REF] +'\n'
        return shows
            
# *********************************
# show control
# *********************************

    # show manager can be initialised by a player, shower or by pipresents.py
    # if by pipresents.py then show_id=-1
    def __init__(self,show_id,showlist,show_params,root,canvas,pp_dir,pp_profile,pp_home):
        self.show_id=show_id
        self.showlist=showlist
        self.show_params=show_params
        self.root=root
        self.show_canvas=canvas
        self.pp_dir=pp_dir
        self.pp_profile=pp_profile
        self.pp_home=pp_home


        self.mon=Monitor()

    def control_a_show(self,show_ref,show_command):
        if show_command == 'open':
            return self.start_show(show_ref)
        elif show_command  == 'close':
            return self.exit_show(show_ref)
        elif show_command  == 'closeall':
            return self.exit_all_shows()
        elif show_command  == 'openexclusive':
            return self.open_exclusive(show_ref)
        else:
            return 'error','command not recognised '+ show_command

    def open_exclusive(self,show_ref):
        self.exit_all_shows()
        self.exclusive_show=show_ref
        self.wait_for_openexclusive()
        return 'normal','opened exclusive' 


    def wait_for_openexclusive(self):
        if self.all_shows_exited() is False:
            ShowManager.canvas.after(1,self.wait_for_openexclusive)
            return
        self.start_show(self.exclusive_show)
          
            

    def exit_all_shows(self):
        for show in ShowManager.shows:
            self.exit_show(show[ShowManager.SHOW_REF])
        return 'normal','exited all shows'


    # kick off the exit sequence of a show by calling the shows exit method.
    # it will result in all the shows in a stack being closed and end_play_show being called
    def exit_show(self,show_ref):
        index=self.show_registered(show_ref)
        self.mon.log(self,"De-registering show "+ show_ref + ' show index:' + str(index))
        show_obj=self.show_running(index)
        if show_obj is not None:
            self.mon.log(self,"Exiting show "+ show_ref + ' show index:' + str(index))
            show_obj.exit()
        return 'normal','exited a concurrent show'
            

    def start_show(self,show_ref):
        index=self.show_registered(show_ref)
        if index <0:
            return 'error',"Show not found in showlist: "+ show_ref
        show_index = self.showlist.index_of_show(show_ref)
        show=self.showlist.show(show_index)
        reason,message,show_canvas=self.compute_show_canvas(show)
        if reason == 'error':
            return reason,message
        # print 'STARTING TOP LEVEL SHOW',show_canvas
        self.mon.sched(self,TimeOfDay.now,'Starting Show: ' + show_ref  + ' from show: ' + self.show_params['show-ref'])
        self.mon.log(self,'Starting Show: ' + show_ref  + ' from: ' + self.show_params['show-ref'])
        if self.show_running(index):
            self.mon.sched(self,TimeOfDay.now,"show already running so ignoring command: "+show_ref)
            self.mon.warn(self,"show already running so ignoring command: "+show_ref)
            return 'normal','this concurrent show already running'
        show_obj = self.init_show(index,show,show_canvas)
        if show_obj is None:
            return 'error',"unknown show type in start concurrent show - "+ show['type']
        else:
            self.set_running(index,show_obj)
            # params - end_callback, show_ready_callback, parent_kickback_signal, level
            show_obj.play(self._end_play_show,None,False,0,[])
            return 'normal','concurrent show started'

 
    # used by shows to create subshows or child shows
    def init_subshow(self,show_id,show,show_canvas):
        return self.init_show(show_id,show,show_canvas)



    def _end_play_show(self,index,reason,message):
        show_ref_to_exit =ShowManager.shows[index][ShowManager.SHOW_REF]
        show_to_exit =ShowManager.shows[index][ShowManager.SHOW_OBJ]
        self.mon.sched(self,TimeOfDay.now,'Closed show: ' + show_ref_to_exit)
        self.mon.log(self,'Exited from show: ' + show_ref_to_exit + ' ' + str(index) )
        self.mon.log(self,'Exited with Reason = ' + reason)
        self.mon.trace(self,' Show is: ' + show_ref_to_exit + ' show index ' + str(index))
        # closes the video/audio from last track then closes the track
        # print 'show to exit ',show_to_exit, show_to_exit.current_player,show_to_exit.previous_player
        self.set_exited(index)
        if self.all_shows_exited() is True:
            ShowManager.all_shows_ended_callback(reason,message)
        return reason,message


    # common function to initilaise the show by type
    def init_show(self,show_id,selected_show,show_canvas,):
        if selected_show['type'] == "mediashow":
            return MediaShow(show_id,
                             selected_show,
                             self.root,
                             show_canvas,
                             self.showlist,
                             self.pp_dir,
                             self.pp_home,
                             self.pp_profile,
                             ShowManager.command_callback)

        elif selected_show['type'] == "liveshow":    
            return LiveShow(show_id,
                            selected_show,
                            self.root,
                            show_canvas,
                            self.showlist,
                            self.pp_dir,
                            self.pp_home,
                            self.pp_profile,
                            ShowManager.command_callback)


        elif selected_show['type'] == "radiobuttonshow":
            return RadioButtonShow(show_id,
                                   selected_show,
                                   self.root,
                                   show_canvas,
                                   self.showlist,
                                   self.pp_dir,
                                   self.pp_home,
                                   self.pp_profile,
                                   ShowManager.command_callback)

        elif selected_show['type'] == "hyperlinkshow":
            return HyperlinkShow(show_id,
                                 selected_show,
                                 self.root,
                                 show_canvas,
                                 self.showlist,
                                 self.pp_dir,
                                 self.pp_home,
                                 self.pp_profile,
                                 ShowManager.command_callback)
        
        elif selected_show['type'] == "menu":
            return MenuShow(show_id,
                            selected_show,
                            self.root,
                            show_canvas,
                            self.showlist,
                            self.pp_dir,
                            self.pp_home,
                            self.pp_profile,
                            ShowManager.command_callback)
        
        elif selected_show['type'] == "artmediashow":    
            return ArtMediaShow(show_id,
                                selected_show,
                                self.root,
                                show_canvas,
                                self.showlist,
                                self.pp_dir,
                                self.pp_home,
                                self.pp_profile,
                                ShowManager.command_callback)
        
        elif selected_show['type'] == "artliveshow":    
            return ArtLiveShow(show_id,
                               selected_show,
                               self.root,
                               show_canvas,
                               self.showlist,
                               self.pp_dir,
                               self.pp_home,
                               self.pp_profile,
                               ShowManager.command_callback)
        else:
            return None


    def compute_show_canvas(self,show_params):
        canvas={}
        canvas['canvas-obj']= ShowManager.canvas
        status,message,self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2,self.show_canvas_y2= self.parse_show_canvas(show_params['show-canvas'])
        if status  == 'error':
            # self.mon.err(self,'show canvas error: ' + message + ' in ' + show_params['show-canvas'])
            return 'error','show canvas error: ' + message + ' in ' + show_params['show-canvas'],canvas
        else:
            self.show_canvas_width = self.show_canvas_x2 - self.show_canvas_x1
            self.show_canvas_height=self.show_canvas_y2 - self.show_canvas_y1
            self.show_canvas_centre_x= self.show_canvas_width/2
            self.show_canvas_centre_y= self.show_canvas_height/2
            canvas['show-canvas-x1'] = self.show_canvas_x1
            canvas['show-canvas-y1'] = self.show_canvas_y1
            canvas['show-canvas-x2'] = self.show_canvas_x2
            canvas['show-canvas-y2'] = self.show_canvas_y2
            canvas['show-canvas-width']  = self.show_canvas_width
            canvas['show-canvas-height'] = self.show_canvas_height
            canvas['show-canvas-centre-x'] = self.show_canvas_centre_x 
            canvas['show-canvas-centre-y'] = self.show_canvas_centre_y
            return 'normal','',canvas



    def parse_show_canvas(self,text):
        fields = text.split()
        # blank so show canvas is the whole screen
        if len(fields) < 1:
            return 'normal','',0,0,int(self.canvas['width']),int(self.canvas['height'])
             
        elif len(fields) in (1,4):
            # window is specified
            status,message,x1,y1,x2,y2=parse_rectangle(text)
            if status=='error':
                return 'error',message,0,0,0,0
            else:
                return 'normal','',x1,y1,x2,y2
        else:
            # error
            return 'error','Wrong number of fields in Show canvas: '+ text,0,0,0,0
Пример #12
-1
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