Sub Main()
'receive bytes / no receive serial enabled

    autorunVersion$ = "3.4.40"
    customAutorunVersion$ = "3.4.1"
    debugOn = false
    loggingOn = false
    debuggingSynchro = false
    
    sysFlags = CreateObject("roAssociativeArray")
    sysFlags.debugOn = debugOn
    sysFlags.loggingOn = loggingOn
    sysFlags.debuggingSynchro = debuggingSynchro
    
    expander = CreateObject("roControlPort", "Expander-GPIO")
    if type(expander) = "roControlPort" then
        expanderPresent = true
        expander = invalid
    else
        expanderPresent = false
    endif
    
    modelObject = CreateObject("roDeviceInfo")
    sysInfo = CreateObject("roAssociativeArray")
    sysInfo.autorunVersion$ = autorunVersion$
    sysInfo.customAutorunVersion$ = customAutorunVersion$
    sysInfo.deviceUniqueID$ = modelObject.GetDeviceUniqueId()
    sysInfo.deviceFWVersion$ = modelObject.GetVersion()
    sysInfo.deviceModel$ = modelObject.GetModel()
    sysInfo.deviceFamily$ = modelObject.GetFamily()
    sysInfo.expanderPresent = expanderPresent

    sysInfo.modelSupportsWifi = false
    nc = CreateObject("roNetworkConfiguration", 1)
    if type(nc) = "roNetworkConfiguration" then
        currentConfig = nc.GetCurrentConfig()
        if type(currentConfig) = "roAssociativeArray" then
            sysInfo.modelSupportsWifi = true
        endif
    endif
    nc = invalid

' check to see whether or not the current firmware meets the minimum compatibility requirements
	versionNumber% = modelObject.GetVersionNumber()
	' this version of BrightAuthor requires 3.5.68 or higher - 197956
	if versionNumber% < 197956 then
        videoMode = CreateObject("roVideoMode")
        resX = videoMode.GetResX()
        resY = videoMode.GetResY()
        videoMode = invalid
        r=CreateObject("roRectangle",0,resY/2-resY/64,resX,resY/32)
        twParams = CreateObject("roAssociativeArray")
        twParams.LineCount = 1
        twParams.TextMode = 2
        twParams.Rotation = 0
        twParams.Alignment = 1
        tw=CreateObject("roTextWidget",r,1,2,twParams)
        tw.PushString("Firmware needs to be upgraded to 3.5.68 or greater")
        tw.Show()
        msgPort = CreateObject("roMessagePort")
		gpioPort = CreateObject("roGpioControlPort")
		gpioPort.SetPort(msgPort)
        while true
	        msg = wait(0, msgPort)
			if type(msg) = "roGpioButton" and msg.GetInt()=12 then
		        stop
			endif
        end while
	endif
	
    diagnosticCodes = newDiagnosticCodes()
    
    RunBsp(sysFlags, sysInfo, diagnosticCodes)

End Sub


Sub RunBsp(sysFlags As Object, sysInfo As Object, diagnosticCodes As Object)

    msgPort = CreateObject("roMessagePort")
    
    BSP = newBSP(sysFlags, msgPort)

    BSP.globalVariables = NewGlobalVariables()

    BSP.gpioPort = CreateObject("roGpioControlPort")
    BSP.gpioPort.SetPort(msgPort)

	BSP.bp900InputPort =CreateObject("roControlPort", "TouchBoard-0-GPIO")
	if type(BSP.bp900InputPort) = "roControlPort" then
	    BSP.bp900InputPort.SetPort(msgPort)
    endif
        
    BSP.sysInfo = sysInfo
    BSP.diagnosticCodes = diagnosticCodes
    
    BSP.diagnostics.SetSystemInfo(sysInfo, diagnosticCodes)
    BSP.networking.SetSystemInfo(sysInfo, diagnosticCodes)
    BSP.logging.SetSystemInfo(sysInfo, diagnosticCodes)

    BSP.syncPoolFiles = invalid
    localCurrentSync = CreateObject("roSyncSpec")
    if localCurrentSync.ReadFromFile("local-sync.xml") then
        BSP.syncPoolFiles = CreateObject("roSyncPoolFiles", "pool", localCurrentSync)
    endif
    
    registrySection = CreateObject("roRegistrySection", "networking")
    if type(registrySection)<>"roRegistrySection" then print "Error: Unable to create roRegistrySection":stop
    
    lwsConfig$ = registrySection.Read("lws")
    if lwsConfig$ = "c" or lwsConfig$ = "s" then

        lwsUserName$ = registrySection.Read("lwsu")
        lwsPassword$ = registrySection.Read("lwsp")        
        
        if (len(lwsUserName$) + len(lwsPassword$)) > 0 then
            credentials = CreateObject("roAssociativeArray")
            credentials.AddReplace(lwsUserName$, lwsPassword$)
        else
            credentials = invalid
        end if
                        
        BSP.localServer = CreateObject("roHttpServer", { port: 80 })
        BSP.localServer.SetPort(msgPort)
        
        BSP.GetHeartbeatAA =        { HandleEvent: GetHeartbeat, mVar: BSP }
        BSP.GetIDAA =               { HandleEvent: GetID, mVar: BSP }
        BSP.GetAutorunVersionAA =   { HandleEvent: GetAutorunVersion, mVar: BSP }
        BSP.GetLocalSyncSpecAA =    { HandleEvent: GetLocalSyncSpec, mVar: BSP }
        BSP.GetPoolFilesAA =        { HandleEvent: GetPoolFiles, mVar: BSP }
        BSP.FilePostedAA =          { HandleEvent: FilePosted, mVar: BSP }
        BSP.SyncSpecPostedAA =      { HandleEvent: SyncSpecPosted, mVar: BSP }
        BSP.GetCurrentStatusAA =    { HandleEvent: GetCurrentStatus, mVar: BSP }
        BSP.GetStorageInfoAA =      { HandleEvent: GetStorageInfo, mVar: BSP }
        BSP.PrepareForTransferAA =  { HandleEvent: PrepareForTransfer, mVar: BSP }
        
        BSP.localServer.AddGetFromEvent({ url_path: "/GetID", user_data: BSP.GetIDAA })

        BSP.localServer.AddGetFromEvent({ url_path: "/GetHeartbeat", user_data: BSP.GetHeartbeatAA, passwords: credentials })
        BSP.localServer.AddGetFromEvent({ url_path: "/GetAutorunVersion", user_data: BSP.GetAutorunVersionAA, passwords: credentials })
        BSP.localServer.AddGetFromEvent({ url_path: "/GetLocalSyncSpec", user_data: BSP.GetLocalSyncSpecAA, passwords: credentials })
        BSP.localServer.AddGetFromEvent({ url_path: "/GetPoolFiles", user_data: BSP.GetPoolFilesAA, passwords: credentials })
        BSP.localServer.AddGetFromEvent({ url_path: "/GetCurrentStatus", user_data: BSP.GetCurrentStatusAA, passwords: credentials })        
        BSP.localServer.AddGetFromEvent({ url_path: "/GetStorageInfo", user_data: BSP.GetStorageInfoAA, passwords: credentials })

        BSP.localServer.AddPostToFile({ url_path: "/UploadFile", destination_directory: GetDefaultDrive(), user_data: BSP.FilePostedAA, passwords: credentials })
        BSP.localServer.AddPostToFile({ url_path: "/UploadSyncSpec", destination_directory: GetDefaultDrive(), user_data: BSP.SyncSpecPostedAA, passwords: credentials })
        BSP.localServer.AddPostToFile({ url_path: "/PrepareForTransfer", destination_directory: GetDefaultDrive(), user_data: BSP.PrepareForTransferAA, passwords: credentials })

'        BSP.localServer.AddPostToFile({ url_path: "/UploadFile", destination_directory: GetDefaultDrive(), user_data: BSP.FilePostedAA })
'        BSP.localServer.AddPostToFile({ url_path: "/UploadSyncSpec", destination_directory: GetDefaultDrive(), user_data: BSP.SyncSpecPostedAA })
'        BSP.localServer.AddPostToFile({ url_path: "/PrepareForTransfer", destination_directory: GetDefaultDrive(), user_data: BSP.PrepareForTransferAA })

        unitName$ = registrySection.Read("un")
        unitNamingMethod$ = registrySection.Read("unm")
        unitDescription$ = registrySection.Read("ud")
        
        if lwsConfig$ = "c" then
            BSP.lwsConfig$ = "content"
        else
            BSP.lwsConfig$ = "status"
        endif
        
        service = { name: "BrightSign Web Service", type: "_http._tcp", port: 8080, _functionality: BSP.lwsConfig$, _serialNumber: sysInfo.deviceUniqueID$, _unitName: unitName$, _unitNamingMethod: unitNamingMethod$, _unitDescription: unitDescription$ }
        advert = CreateObject("roNetworkAdvertisement", service)
        if advert = invalid then
            stop
        end if
    
    else
    
        BSP.lwsConfig$ = "none"
    
    endif

' networking is considered active if current-sync.xml is present.
    networkedCurrentSyncValid = false
	networkedCurrentSync = CreateObject("roSyncSpec")
	
	if networkedCurrentSync.ReadFromFile("current-sync.xml") then
	
        networkedCurrentSyncValid = true

        BSP.syncPoolFiles = CreateObject("roSyncPoolFiles", "pool", networkedCurrentSync)
        
        BSP.networkingActive = BSP.networking.InitializeNetworkDownloads()

        if BSP.networkingActive then
        
            if type(BSP.networking.networkTimerDownload.timer) = "roTimer" then

                BSP.networking.networkTimerDownload.timer.Start()
                      
            endif

        endif
        
    else
    
        BSP.networkingActive = false
        
    endif

' indicate charger not present - code elsewhere will look for it
	BSP.ttkChargerPresent = false
    
' initialize logging parameters
    playbackLoggingEnabled = false
    eventLoggingEnabled = false
    diagnosticLoggingEnabled = false
    uploadLogFilesAtBoot = false
    uploadLogFilesAtSpecificTime = false
    uploadLogFilesTime% = 0

    if networkedCurrentSyncValid then
        if networkedCurrentSync.LookupMetadata("client", "playbackLoggingEnabled") = "yes" then playbackLoggingEnabled = true
        if networkedCurrentSync.LookupMetadata("client", "eventLoggingEnabled") = "yes" then eventLoggingEnabled = true
        if networkedCurrentSync.LookupMetadata("client", "diagnosticLoggingEnabled") = "yes" then diagnosticLoggingEnabled = true
        if networkedCurrentSync.LookupMetadata("client", "uploadLogFilesAtBoot") = "yes" then uploadLogFilesAtBoot = true
        if networkedCurrentSync.LookupMetadata("client", "uploadLogFilesAtSpecificTime") = "yes" then uploadLogFilesAtSpecificTime = true
        uploadLogFilesTime$ = networkedCurrentSync.LookupMetadata("client", "uploadLogFilesTime")
    else
        if registrySection.Read("ple") = "yes" then playbackLoggingEnabled = true
        if registrySection.Read("ele") = "yes" then eventLoggingEnabled = true
        if registrySection.Read("dle") = "yes" then diagnosticLoggingEnabled = true
        if registrySection.Read("uab") = "yes" then uploadLogFilesAtBoot = true
        if registrySection.Read("uat") = "yes" then uploadLogFilesAtSpecificTime = true
        uploadLogFilesTime$ = registrySection.Read("ut")
    endif
    if uploadLogFilesTime$ <> "" then uploadLogFilesTime% = int(val(uploadLogFilesTime$))
    
' setup logging
    BSP.logging.InitializeLogging(playbackLoggingEnabled, eventLoggingEnabled, diagnosticLoggingEnabled, uploadLogFilesAtBoot, uploadLogFilesAtSpecificTime, uploadLogFilesTime%)
    
    BSP.LogDiagnostic(diagnosticCodes.EVENT_STARTUP, BSP.sysInfo.deviceFWVersion$ + chr(9) + BSP.sysInfo.autorunVersion$ + chr(9) + BSP.sysInfo.customAutorunVersion$)
        
    BSP.InitializeNonPrintableKeyboardCodeList()
        
    BSP.Restart()
    
    BSP.StartPlayback()
        
    BSP.EventLoop()

End Sub


Function newBSP(sysFlags As Object, msgPort As Object) As Object

    BSP = CreateObject("roAssociativeArray")

    BSP.msgPort = msgPort
    
    BSP.diagnostics = newDiagnostics(sysFlags)

    BSP.CreateObjectsNeededForTransitionCommands = CreateObjectsNeededForTransitionCommands
    
    BSP.CreateSerial = CreateSerial
    BSP.CreateUDPSender = CreateUDPSender    
    BSP.CreateBP900Output = CreateBP900Output
    
    BSP.SetTouchRegions = SetTouchRegions
    BSP.InitializeTouchScreen = InitializeTouchScreen
    BSP.AddRectangularTouchRegion = AddRectangularTouchRegion
    BSP.AddCircularTouchRegion = AddCircularTouchRegion
    BSP.AddRectangularRolloverTouchRegion = AddRectangularRolloverTouchRegion
    BSP.AddCircularRolloverTouchRegion = AddCircularRolloverTouchRegion

    BSP.AddVideoTimeCodeEvent = AddVideoTimeCodeEvent
    
    BSP.Restart = Restart
    BSP.StartPlayback = StartPlayback
    
    BSP.ExecuteMediaStateCommands = ExecuteMediaStateCommands
    BSP.ExecuteCmd = ExecuteCmd
    
    BSP.MapDigitalOutput = MapDigitalOutput
    BSP.SetAudioOutput = SetAudioOutput
    BSP.SetAudioMode = SetAudioMode
    BSP.MapStereoOutput = MapStereoOutput
    BSP.SetSpdifMute = SetSpdifMute
    BSP.SetAnalogMute = SetAnalogMute
    
    BSP.LaunchBSPPlayback = LaunchBSPPlayback
    BSP.LaunchMedia = LaunchMedia
    BSP.DisplayImage = DisplayImage
    BSP.LaunchVideo = LaunchVideo
    BSP.LaunchBackgroundImage = LaunchBackgroundImage
    BSP.LaunchLiveVideo = LaunchLiveVideo
    BSP.LaunchAudio = LaunchAudio
    BSP.LaunchSignChannel = LaunchSignChannel
    BSP.StopSignChannel = StopSignChannel
    BSP.StopSignChannelInZone = StopSignChannelInZone

    BSP.SetVideoTimeCodeEvents = SetVideoTimeCodeEvents
    
    BSP.SetVideoVolume = SetVideoVolume
    BSP.ChangeVideoVolume = ChangeVideoVolume
    BSP.IncrementVideoVolume = IncrementVideoVolume
    BSP.DecrementVideoVolume = DecrementVideoVolume
    BSP.SetVideoChannnelVolume = SetVideoChannnelVolume
    BSP.IncrementVideoChannnelVolumes = IncrementVideoChannnelVolumes
    BSP.DecrementVideoChannnelVolumes = DecrementVideoChannnelVolumes
    
    BSP.SetAudioVolume = SetAudioVolume
    BSP.ChangeAudioVolume = ChangeAudioVolume
    BSP.IncrementAudioVolume = IncrementAudioVolume
    BSP.DecrementAudioVolume = DecrementAudioVolume
    BSP.SetAudioChannnelVolume = SetAudioChannnelVolume
    BSP.IncrementAudioChannnelVolumes = IncrementAudioChannnelVolumes
    BSP.DecrementAudioChannnelVolumes = DecrementAudioChannnelVolumes
    
    BSP.GetVideoZone = GetVideoZone
    BSP.ChangeChannelVolumes = ChangeChannelVolumes
    BSP.SetChannelVolumes = SetChannelVolumes
    
    BSP.PauseVideo = PauseVideo
    BSP.ResumeVideo = ResumeVideo
    BSP.SetPowerSaveMode = SetPowerSaveMode
    
    BSP.EventLoop = EventLoop
    BSP.HandleInternalMessageEvent = HandleInternalMessageEvent
    BSP.HandleVideoEndEvent = HandleVideoEndEvent
    BSP.HandleVideoTimeCodeEvent = HandleVideoTimeCodeEvent
    BSP.HandleAudioEndEvent = HandleAudioEndEvent
    BSP.HandleTimerEvent = HandleTimerEvent
    BSP.HandleGPIOEvent = HandleGPIOEvent
    BSP.HandleBP900Event = HandleBP900Event
    BSP.HandleTouchEvent = HandleTouchEvent
    BSP.HandleURLEvent = HandleURLEvent
    BSP.HandleSyncPoolEvent = HandleSyncPoolEvent
    BSP.HandleSyncPoolProgressEvent = HandleSyncPoolProgressEvent
    BSP.HandleStreamLineEvent = HandleStreamLineEvent
    BSP.HandleIRRemoteEvent = HandleIRRemoteEvent
    BSP.HandleKeyboardEvent = HandleKeyboardEvent
    BSP.HandleDatagramEvent = HandleDatagramEvent
    BSP.GetNextStateName = GetNextStateName
    
    BSP.GetImageStateToPreload = GetImageStateToPreload
    BSP.CheckForImageToPreload = CheckForImageToPreload
    BSP.PreloadImage = PreloadImage
    BSP.PreloadItem = PreloadItem
    BSP.timeoutHandlers = CreateObject("roAssociativeArray")
    BSP.presentationTimeoutHandlers = CreateObject("roAssociativeArray")
    BSP.newTimerHandler = newTimerHandler
    
    BSP.HandleNetworkTimerEvent = HandleNetworkTimerEvent
    BSP.StopPlayback = StopPlayback
    BSP.ResetDownloadTimerToDoRetry = ResetDownloadTimerToDoRetry
    BSP.PrepareForRestart = PrepareForRestart
    
    BSP.DisplayNetworkTimers = DisplayNetworkTimers

    BSP.debuggingSynchro = sysFlags.debuggingSynchro
    
    BSP.LogPlayStart = LogPlayStart
    BSP.LogPlayEnd = LogPlayEnd
    BSP.LogPlay = LogPlay
    BSP.LogEvent = LogEvent
    BSP.LogDiagnostic = LogDiagnostic
    
    BSP.systemTime = CreateObject("roSystemTime")

    BSP.newLogging = newLogging
    BSP.logging = BSP.newLogging()
    BSP.newNetworking = newNetworking
    BSP.networking = BSP.newNetworking(BSP)    
    BSP.logging.networking = BSP.networking

    BSP.CreateBSPObjects = CreateBSPObjects
    
    BSP.WaitForSyncResponse = WaitForSyncResponse
    
    BSP.GetID = GetID
    BSP.GetHeartbeat = GetHeartbeat
    BSP.GetAutorunVersion = GetAutorunVersion
    BSP.GetLocalSyncSpec = GetLocalSyncSpec
    BSP.GetPoolFiles = GetPoolFiles
    BSP.FilePosted = FilePosted
    BSP.SyncSpecPosted = SyncSpecPosted
    BSP.GetCurrentStatus = GetCurrentStatus
    BSP.GetStorageInfo = GetStorageInfo
    BSP.PrepareForTransfer = PrepareForTransfer
    BSP.FreeSpaceOnDrive = FreeSpaceOnDrive
    
    BSP.XMLAutoschedule = XMLAutoSchedule
    
    BSP.GetNonPrintableKeyboardCode = GetNonPrintableKeyboardCode
    BSP.InitializeNonPrintableKeyboardCodeList = InitializeNonPrintableKeyboardCodeList
    
    return BSP
    
End Function


Sub Restart()

    m.activePresentation$ = ""
    
    xmlAutoscheduleFile$ = ReadAsciiFile("autoschedule.xml")
    if xmlAutoscheduleFile$ <> "" then
        schedule = m.XMLAutoschedule("autoschedule.xml")
    else
        stop
    endif
         
    if type(schedule.activeScheduledEvent) = "roAssociativeArray" then
        xmlFileName$ = "autoplay.xml"
    else
        xmlFileName$ = ""
    endif
    
    m.schedule = schedule
	
    if (xmlFileName$ <> "") then
        BrightAuthor = CreateObject("roXMLElement")
        BrightAuthor.Parse(ReadAsciiFile(xmlFileName$))
        
        ' verify that this is a valid BrightAuthor XML file
        if BrightAuthor.GetName() <> "BrightAuthor" then print "Invalid XML file - name not BrightAuthor" : stop
        if type(BrightAuthor@version) <> "roString" then print "Invalid XML file - version not found" : stop    
'        print "BrightAuthor xml file - version = "; BrightAuthor@version

        m.diagnostics.PrintTimestamp()
        m.diagnostics.PrintDebug("### create sign object")

        version% = int(val(BrightAuthor@version))
        
        sign = newSign(BrightAuthor, m.globalVariables, m.msgPort, m.gpioPort, version%)
    else
        sign = invalid
    endif
    
    m.sign = sign

End Sub


Sub StartPlayback()

    existingSign = invalid

    sign = m.sign
    
    if type(sign) = "roAssociativeArray" then 
    
    ' set default serial port speed, mode
        m.serialPortConfigurations = CreateObject("roArray", 6, true)
        for i% = 0 to 5
            if type(sign.serialPortConfigurations[i%]) = "roAssociativeArray" then
                serialPortConfiguration = CreateObject("roAssociativeArray")
                serialPortConfiguration.serialPortSpeed% = sign.serialPortConfigurations[i%].serialPortSpeed%
                serialPortConfiguration.serialPortMode$ = sign.serialPortConfigurations[i%].serialPortMode$
                m.serialPortConfigurations[i%] = serialPortConfiguration
            endif
        next
        
    ' set a default udp receive port
        m.udpReceivePort = sign.udpReceivePort
        m.udpSendPort = sign.udpSendPort
        m.udpAddress$ = sign.udpAddress$
    
        m.diagnostics.PrintTimestamp()
        m.diagnostics.PrintDebug("### CreateBSPObjects")

        m.CreateBSPObjects(existingSign)

	' check to see if this is a TTK
		ttkPortInUse = false
		if type(m.serial) = "roAssociativeArray" then
		    if type(m.serial["1"]) = "roSerialPort" then
				ttkPortInUse = true
			endif
		endif
		
	' see if this is a TTK unit (check by trying to communicate with the charger)
		if not ttkPortInUse then
			ttkCharger = CreateObject("roSerialPort", 1, 9600)
			if type(ttkCharger) = "roSerialPort" then
				p = CreateObject("roMessagePort")
				ttkCharger.SetEol(chr(13))	'Set EOL character for Unix
				ttkCharger.SetLineEventPort(p)
				
				ttkCharger.SendByte(118)

				msg = wait(500,p)
				
				if type(msg) = "roStreamLineEvent" then
					m.diagnostics.PrintDebug("Response from ttk charger is " + msg)
					m.ttk = CreateObject("roAssociativeArray")
					chargeEvent = ParseTTKChargeEvent(msg.GetString())
					if type(chargeEvent) = "roAssociativeArray" then
						m.ttk.voltage = 0
						m.ttk.voltageHex = ""
						m.ttk.current = 0
						m.ttk.currentHex = ""
						m.ttk.previousState$ = "unknown"
						m.ttk.previousEvent$ = "none"
						m.ttk.previousBatteryState$ = "ok"
						value$ = IntToHexString(chargeEvent.value)
						' should the code only accept voltage since that's what it asked for? is there any circumstance under which current would be returned? think schedule changes.
						if chargeEvent.type = "v" then
							m.ttk.voltage = chargeEvent.value
							m.ttk.voltageHex = value$
						else if chargeEvent.type = "i" then
							m.ttk.current = chargeEvent.value
							m.ttk.currentHex = value$
						endif
						m.logging.WriteEventLogEntry("NoZone", "ttk" + chargeEvent.type, value$)
						
						' don't log to network here - should only have a valid voltage value
						' log to network
						' if chargeEvent.type = "i" and m.networkingActive then
						' 	m.networking.AddTTKItem("Reading", m.ttk.voltageHex, m.ttk.currentHex)
						' endif
						
						m.ttkChargerPresent = true
					else
						m.diagnostics.PrintDebug("Device on port 1 does not appear to be a TTK charger - incorrect response")
					endif
				else
					m.diagnostics.PrintDebug("No response from ttk charger")
				endif

				p = invalid
				ttkCharger = invalid
			endif
		endif

		if m.ttkChargerPresent then
            serialPortConfiguration = CreateObject("roAssociativeArray")
            serialPortConfiguration.serialPortSpeed% = 9600
            serialPortConfiguration.serialPortMode$ = "8N1"
            m.serialPortConfigurations[1] = serialPortconfiguration
            m.CreateSerial("1")
			m.ttk.serial = m.serial["1"]

            m.ttk.timer = CreateObject("roTimer")
            m.ttk.timer.SetPort(m.msgPort)
            newTimeout = m.systemTime.GetLocalDateTime()
            newTimeout.AddSeconds(2)
            m.ttk.timer.SetDateTime(newTimeout)
            m.ttk.timer.Start()
            
            m.ttk.lastCommandWasVoltage = true
		endif
			        
    endif
    
    ' kick off playback
    
    ' set timer for when current presentation should end
    activeScheduledPresentation = m.schedule.activeScheduledEvent
    
    if type(activeScheduledPresentation) = "roAssociativeArray" then
        
        if not activeScheduledPresentation.allDayEveryDay then
        
            endDateTime = CopyDateTime(activeScheduledPresentation.dateTime)
            endDateTime.AddSeconds(activeScheduledPresentation.duration% * 60)

            newTimer = CreateObject("roTimer")
            newTimer.SetTime(endDateTime.GetHour(), endDateTime.GetMinute(), 0)
            newTimer.SetDate(endDateTime.GetYear(), endDateTime.GetMonth(), endDateTime.GetDay())
            newTimer.SetDayOfWeek(endDateTime.GetDayOfWeek())
            newTimer.SetPort(m.msgPort)
            newTimer.Start()
                            
            m.schedule.presentationTimer = newTimer ' this is so that the newTimer doesn't go out of scope and get deleted
            timerID$ = stri(newTimer.GetIdentity())
            ' the following is a little bogus
            timerHandler = m.newTimerHandler("presentationTimeout")
            m.presentationTimeoutHandlers.AddReplace(timerID$, timerHandler)

        endif
        
        
        m.diagnostics.PrintTimestamp()
        m.diagnostics.PrintDebug("### set background screen color")

        ' set background screen color
        if type(sign.backgroundScreenColor%) = "roInt" then
            videoMode = CreateObject("roVideoMode")
            videoMode.SetBackgroundColor(sign.backgroundScreenColor%)                     
            videoMode = 0
        endif

    ' the commented out code below didn't always work (BrightScript error?). The new code **appears** to work.
    
        numZones% = sign.zones.Count()
        if numZones% > 0 then
            for i% = 0 to numZones% - 1
                zone = sign.zones[i%]
                if type(zone.playlist) = "roAssociativeArray" then
                    zone.activePlaylist = zone.playlist
                    zone.activeState = zone.activePlaylist.firstState
                    zone.activeStateName$ = zone.activePlaylist.firstStateName$
                    zone.previousStateName$ = zone.activePlaylist.firstStateName$
                    
                    m.diagnostics.PrintTimestamp()
                    m.diagnostics.PrintDebug("### LaunchBSPPlayback")

                    m.LaunchBSPPlayback(zone)
                    
                    m.diagnostics.PrintTimestamp()
                    m.diagnostics.PrintDebug("### PreloadImage")
                                    
                    m.PreloadImage(zone)

                endif
            next        
        endif

'        for each zone in sign.zones
            
            ' launch playback for this zone
            
'            if type(zone.playlist) = "roAssociativeArray" then
'                zone.activePlaylist = zone.playlist
'                zone.activeState = zone.activePlaylist.firstState
'                zone.activeStateName$ = zone.activePlaylist.firstStateName$
'                zone.previousStateName$ = zone.activePlaylist.firstStateName$
                
'                m.diagnostics.PrintTimestamp()
'                m.diagnostics.PrintDebug("### LaunchBSPPlayback")

'                m.LaunchBSPPlayback(zone)
                
'                m.diagnostics.PrintTimestamp()
'                m.diagnostics.PrintDebug("### PreloadImage")
                
'                m.PreloadImage(zone)
'            endif
'        next
        
    else ' no active scheduled event. setup timer for next event (if one exists)
        
        if type(m.schedule.nextScheduledEventTime) = "roDateTime" then
        
            dateTime = m.schedule.nextScheduledEventTime
            newTimer = CreateObject("roTimer")
            newTimer.SetTime(dateTime.GetHour(), dateTime.GetMinute(), 0)
            newTimer.SetDate(dateTime.GetYear(), dateTime.GetMonth(), dateTime.GetDay())
            newTimer.SetDayOfWeek(dateTime.GetDayOfWeek())
            newTimer.SetPort(m.msgPort)
            newTimer.Start()
            
            m.schedule.presentationTimer = newTimer ' this is so that the newTimer doesn't go out of scope and get deleted
            timerID$ = stri(newTimer.GetIdentity())
            timerHandler = m.newTimerHandler("presentationTimeout")
            m.presentationTimeoutHandlers.AddReplace(timerID$, timerHandler)

        endif
        	
    endif

End Sub


Function XMLAutoschedule(xmlFileName$ As String)

    autoScheduleXML = CreateObject("roXMLElement")
    autoScheduleXML.Parse(ReadAsciiFile(xmlFileName$))
    
    ' verify that this is a valid autoschedule XML file
    if autoScheduleXML.GetName() <> "autoschedule" then print "Invalid autoschedule XML file - name not autoschedule" : stop
    if type(autoScheduleXML@version) <> "roString" then print "Invalid autoschedule XML file - version not found" : stop    
'    print "autoschedule xml file - version = "; autoScheduleXML@version

    schedule = newSchedule(autoScheduleXML)

    if type(schedule.activeScheduledEvent) = "roAssociativeArray" then

        presentation$ = schedule.activeScheduledEvent.presentationName$
        m.activePresentation$ = presentation$
        
        autoplayFileName$ = "autoplay-" + presentation$ + ".xml"

        ' if networking is in use, the autoplay file must be copied from the pool folder.
	    currentSync = CreateObject("roSyncSpec")
        if type(currentSync) = "roSyncSpec" and currentSync.ReadFromFile("current-sync.xml") then
            spf = CreateObject("roSyncPoolFiles", "pool", currentSync)
            autoplayPoolFile$ = spf.GetPoolFilePath(autoplayFileName$)
            if autoplayPoolFile$ = "" then stop
            success = CopyFile(autoplayPoolFile$, "autoplay.xml")
            spf = 0
        else if type(currentSync) = "roSyncSpec" and currentSync.ReadFromFile("local-sync.xml") then
            spf = CreateObject("roSyncPoolFiles", "pool", currentSync)
            autoplayPoolFile$ = spf.GetPoolFilePath(autoplayFileName$)
            if autoplayPoolFile$ = "" then stop
            success = CopyFile(autoplayPoolFile$, "autoplay.xml")
            spf = 0
        else
            ' if networking is not in use, the autoplay file is in the root folder.
            success = CopyFile(autoplayFileName$, "autoplay.xml")
        endif
        currentSync = 0
    
        if not success then stop

    endif
    
    return schedule
    
End Function


Function newSchedule(autoScheduleXML As Object) As Object

    schedule = CreateObject("roAssociativeArray")

    ' create and read schedules
    scheduledPresentationsXML = autoScheduleXML.scheduledPresentation
    numScheduledPresentations% = scheduledPresentationsXML.Count()
    
    schedule.scheduledEvents = CreateObject("roArray", numScheduledPresentations%, true)
    
    for each scheduledPresentationXML in scheduledPresentationsXML
    
        scheduledPresentationBS = newScheduledEvent(scheduledPresentationXML)
        schedule.scheduledEvents.push(scheduledPresentationBS)
        
    next

    ' get starting presentation
    schedule.GetActiveScheduledEvent = GetActiveScheduledEvent    
    schedule.GetNextScheduledEventTime = GetNextScheduledEventTime
        
    schedule.activeScheduledEvent = schedule.GetActiveScheduledEvent()

    if type(schedule.activeScheduledEvent)<> "roAssociativeArray" then
        schedule.nextScheduledEventTime = schedule.GetNextScheduledEventTime()
    endif

    return schedule
    
End Function


Function newSign(BrightAuthor As Object, globalVariables As Object, msgPort As Object, gpioPort As Object, version% As Integer) As Object

    Sign = CreateObject("roAssociativeArray")
    
    Sign.numTouchEvents% = 0
    Sign.numVideoTimeCodeEvents% = 0
    
    ' get sign data

    Sign.name$ = BrightAuthor.meta.name.GetText()
    if type(Sign.name$) <> "roString" then print "Invalid XML file - meta name not found" : stop
    
    Sign.videoMode$ = BrightAuthor.meta.videoMode.GetText()
    if type(Sign.videoMode$) <> "roString" then print "Invalid XML file - meta videoMode not found" : stop
'    print "Video mode is ";Sign.videoMode$
    
    videoMode = CreateObject("roVideoMode")
    ok = videoMode.SetMode(Sign.videoMode$)
    if ok = 0 then print "Error: Can't set VIDEOMODE to ::"; videoMode$; "::" : stop
    videoMode = 0
    
    Sign.videoConnector$ = BrightAuthor.meta.videoConnector.GetText()
    if type(Sign.videoConnector$) <> "roString" then print "Invalid XML file - meta videoConnector not found" : stop  
'    print "Video connector is ";Sign.videoConnector$

    Sign.timezone$ = BrightAuthor.meta.timezone.GetText()
    if type(Sign.timezone$) = "roString" then
        bsSystemTime = CreateObject("roSystemTime")
        if Sign.timezone$ <> "" then
            bsSystemTime.SetTimeZone(Sign.timezone$)
        endif
        bsSystemTime = 0
    endif
    
    rssDownloadSpec = BrightAuthor.meta.rssDownloadSpec
    
    if type(rssDownloadSpec) = "roXMLList" then
    
        if rssDownloadSpec.Count() > 0 then
        
            rssDownloadSpecAttrs = rssDownloadSpec.GetAttributes()
            rssDownloadSpecType = rssDownloadSpecAttrs["type"]
        
            if rssDownloadSpecType = "periodic" then
            
                rssDownloadPeriodicValue% = val(rssDownloadSpecAttrs["value"])

                Sign.rssTimerDownload = CreateObject("roAssociativeArray")
                Sign.rssTimerDownload.timerType = "TIMERTYPEPERIODIC"
                Sign.rssTimerDownload.timerInterval = rssDownloadPeriodicValue%
                
                newTimer = CreateObject("roTimer")
                systemTime = CreateObject("roSystemTime")
                newTimeout = systemTime.GetLocalDateTime()
                newTimeout.AddSeconds(rssDownloadPeriodicValue%)
                newTimer.SetDateTime(newTimeout)
                newTimer.SetPort(msgPort)
                newTimer.Start()
                
                Sign.rssTimerDownload.timer = newTimer

            endif
            
        endif
        
    endif
    
    backgroundScreenColor = BrightAuthor.meta.backgroundScreenColor
    Sign.backgroundScreenColor% = GetColor(backgroundScreenColor.GetAttributes())
    
    Sign.languageKey$ = BrightAuthor.meta.languageKey.GetText()
    globalVariables.language$ = Sign.languageKey$

    Sign.serialPortConfigurations = CreateObject("roArray", 6, true)

    if BrightAuthor.meta.baudRate.Count() = 1 then
        ' old format
        serialPortSpeed% = int(val(BrightAuthor.meta.baudRate.GetText()))
        serialPortMode$ = BrightAuthor.meta.dataBits.GetText() + BrightAuthor.meta.parity.GetText() + BrightAuthor.meta.stopBits.GetText()
        serialPortConfiguration = CreateObject("roAssociativeArray")
        serialPortConfiguration.serialPortSpeed% = serialPortSpeed%
        serialPortConfiguration.serialPortMode$ = serialPortMode$
        Sign.serialPortConfigurations[0] = serialPortConfiguration
    else
        serialPortConfigurationsXML = BrightAuthor.meta.SerialPortConfiguration
        for each serialPortConfigurationXML in serialPortConfigurationsXML
            port% = int(val(serialPortConfigurationXML.port.GetText()))
            serialPortSpeed% = int(val(serialPortConfigurationXML.baudRate.GetText()))
            dataBits$ = serialPortConfigurationXML.dataBits.GetText()
            parity$ = serialPortConfigurationXML.parity.GetText()
            stopBits$ = serialPortConfigurationXML.stopBits.GetText()
            serialPortMode$ = dataBits$ + parity$ + stopBits$
            
            serialPortConfiguration = CreateObject("roAssociativeArray")
            serialPortConfiguration.serialPortSpeed% = serialPortSpeed%
            serialPortConfiguration.serialPortMode$ = serialPortMode$
            Sign.serialPortConfigurations[port%] = serialPortConfiguration
        next    
    endif
        
    Sign.udpReceivePort = int(val(BrightAuthor.meta.udpReceiverPort.GetText()))
    Sign.udpSendPort = int(val(BrightAuthor.meta.udpDestinationPort.GetText()))
    Sign.udpAddress$ = BrightAuthor.meta.udpDestinationAddress.GetText()

    Sign.flipCoordinates = false
    flipCoordinates$ = BrightAuthor.meta.flipCoordinates.GetText()
    if flipCoordinates$ = "true" then Sign.flipCoordinates = true
    
    Sign.touchCursorDisplayMode$ = BrightAuthor.meta.touchCursorDisplayMode.GetText()
    
    Sign.gpio0Config = BrightAuthor.meta.gpio0.GetText()
    if Sign.gpio0Config = "input" then
        gpioPort.EnableInput(0)
    else
        gpioPort.EnableOutput(0)
    endif
    
    Sign.gpio1Config = BrightAuthor.meta.gpio1.GetText()
    if Sign.gpio1Config = "input" then
        gpioPort.EnableInput(1)
    else
        gpioPort.EnableOutput(1)
    endif
    
    Sign.gpio2Config = BrightAuthor.meta.gpio2.GetText()
    if Sign.gpio2Config = "input" then
        gpioPort.EnableInput(2)
    else
        gpioPort.EnableOutput(2)
    endif
    
    Sign.gpio3Config = BrightAuthor.meta.gpio3.GetText()
    if Sign.gpio3Config = "input" then
        gpioPort.EnableInput(3)
    else
        gpioPort.EnableOutput(3)
    endif
    
    Sign.gpio4Config = BrightAuthor.meta.gpio4.GetText()
    if Sign.gpio4Config = "input" then
        gpioPort.EnableInput(4)
    else
        gpioPort.EnableOutput(4)
    endif
    
    Sign.gpio5Config = BrightAuthor.meta.gpio5.GetText()
    if Sign.gpio5Config = "input" then
        gpioPort.EnableInput(5)
    else
        gpioPort.EnableOutput(5)
    endif
    
    Sign.gpio6Config = BrightAuthor.meta.gpio6.GetText()
    if Sign.gpio6Config = "input" then
        gpioPort.EnableInput(6)
    else
        gpioPort.EnableOutput(6)
    endif
    
    Sign.gpio7Config = BrightAuthor.meta.gpio7.GetText()
    if Sign.gpio7Config = "input" then
        gpioPort.EnableInput(7)
    else
        gpioPort.EnableOutput(7)
    endif
    
    ' get zones

    zoneList = BrightAuthor.zones.zone
    if type(zoneList) <> "roXMLList" then print "Invalid XML file - zone list not found" : stop
    numZones% = zoneList.Count()

    Sign.zones = CreateObject("roArray", numZones%, true)
    
    for each zone in zoneList

        bsZone = newZone(Sign, zone, globalVariables)
        Sign.zones.push(bsZone)

        if bsZone.type$ = "VideoOrImages" or bsZone.type$ = "VideoOnly" then 
            Sign.videoZone = bsZone
        endif
                    
    next
    
    ' audioOutput, audioMode and audioMapping were set incorrectly on earlier versions of presentations
    if version% < 4 then
        for each bsZone in Sign.zones
            if bsZone.type$ = "VideoOrImages" or bsZone.type$ = "VideoOnly" then
                bsZone.audioOutput% = 4
                bsZone.audioMode% = 0
                bsZone.audioMapping% = 0
            endif
        next
    endif
    
    return Sign
    
End Function


Function newState(stateXML As Object) As Object

    bsState = CreateObject("roAssociativeArray")
    bsState.gpioEvents = CreateObject("roAssociativeArray")
    bsState.bp900Events = CreateObject("roAssociativeArray")
    bsState.serialEvents = CreateObject("roAssociativeArray")
    
' get the name
    bsState.name$ = stateXML.name.GetText()
            
' get the item

    item = CreateObject("roAssociativeArray")

    if stateXML.imageItem.Count() = 1 then
    
        newImagePlaylistItem(stateXML.imageItem, item)
        bsState.imageItem = item
    
    else if stateXML.videoItem.Count() = 1 then
    
        newVideoPlaylistItem(stateXML.videoItem, item)
        bsState.videoItem = item
    
    else if stateXML.liveVideoItem.Count() = 1 then
    
        newLiveVideoPlaylistItem(stateXML.liveVideoItem, item)
        bsState.liveVideoItem = item
    
    else if stateXML.signChannelItem.Count() = 1 then
    
        newSignChannelPlaylistItem(stateXML.signChannelItem, item)
        bsState.signChannelItem = item
                    
    else if stateXML.rssImageItem.Count() = 1 then
    
        newRSSImagePlaylistItem(stateXML.rssImageItem, item)
        bsState.signChannelItem = item
                    
    else if stateXML.audioItem.Count() = 1 then
    
        newAudioPlaylistItem(stateXML.audioItem, item)
        bsState.audioItem = item

    else if stateXML.backgroundImageItem.Count() = 1 then
    
        newBackgroundImagePlaylistItem(stateXML.backgroundImageItem, item)
        bsState.backgroundImageItem = item
            
    else if stateXML.clockItem.Count() = 1 then
    
        newClockPlaylistItem(stateXML.clockItem, item)
        bsState.clockItem = item
        
    else if stateXML.textItem.Count() = 1 then
    
        newTextPlaylistItem(stateXML.textItem, item)
        bsState.textItem = item
        
    else if stateXML.rssItem.Count() = 1 then
    
        newRSSPlaylistItem(stateXML.rssItem, item)
        bsState.rssItem = item
        
    endif
    
' get any media state commands (commands that are executed when a state is entered)
    bsState.cmds = CreateObject("roArray", 1, true)

    ' convert old style commands to new style cmds
    commands = stateXML.brightSignCommand
    if stateXML.brightSignCommand.Count() > 0 then
        for each command in commands
            bsCmd = newCommand(command)
            bsState.cmds.push(bsCmd)
        next
    endif
    
    ' new style commands    
    cmds = stateXML.brightSignCmd
    if stateXML.brightSignCmd.Count() > 0 then
        for each cmd in cmds
            newCmd(cmd, bsState.cmds)
        next
    endif
            
    return bsState
    
End Function


Sub newCmd(cmdXML As Object, cmds As Object)

    numCmds% = cmdXML.command.Count()
    if numCmds% > 0 then
        cmdsXML = cmdXML.command
        for each cmd in cmdsXML
            bsCmd = CreateObject("roAssociativeArray")
            bsCmd.name$ = cmd.name.GetText()
            bsCmd.parameters = CreateObject("roAssociativeArray")
            numParameters% = cmd.parameter.Count()
            if numParameters% > 0 then
                parameters = cmd.parameter
                for each parameter in parameters
                    bsCmd.parameters.AddReplace(parameter.name.GetText(), parameter.value.GetText())
                next
            endif
            cmds.push(bsCmd)
        next
    endif
        
End Sub


Function newCommand(commandXML As Object) As Object

    command$ = commandXML.command.GetText()
    parameter$ = commandXML.parameters.GetText()
    
    bsCmd = CreateObject("roAssociativeArray")
    bsCmd.name$ = command$
    bsCmd.parameters = CreateObject("roAssociativeArray")
    
    parameterKey$ = ""
    
    if command$ = "gpioOnCommand" or command$ = "gpioOffCommand" then
        parameterKey$ = "gpioNumber"
    else if command$ = "gpioSetStateCommand" then
        parameterKey$ = "stateValue"
    else if command$ = "setVideoVolume" or command$ = "setAudioVolume" then
        parameterKey$ = "volume"
    else if command$ = "incrementVideoVolume" or command$ = "decrementVideoVolume" or command$ = "incrementAudioVolume" or command$ = "decrementAudioVolume" then
        parameterKey$ = "volumeDelta"
    else if command$ = "sendSerialStringCommand" or command$ = "sendSerialBlockCommand" then
        bsCmd.parameters.AddReplace("port", "0")
        parameterKey$ = "serialString"
    else if command$ = "sendSerialByteCommand" then
        bsCmd.parameters.AddReplace("port", "0")
        parameterKey$ = "byteValue"    
    else if command$ = "sendSerialBytesCommand" then
        bsCmd.parameters.AddReplace("port", "0")
        parameterKey$ = "byteValues"    
    else if command$ = "sendUDPCommand" then
        parameterKey$ = "udpString"
    else if command$ = "synchronize" or command$ = "internalSynchronize" then
        parameterKey$ = "synchronizeKeyword"
    endif

    if parameterKey$ <> "" then
        bsCmd.parameters.AddReplace(parameterKey$, parameter$)
    endif
    
    return bsCmd
    
End Function


Sub newTransition(sign As Object, transitionXML As Object, playlist As Object)

    stateTable = playlist.stateTable
    
    sourceMediaState$ = transitionXML.sourceMediaState.GetText()

' given the sourceMediaState, find the associated bsState
	bsState = stateTable.Lookup(sourceMediaState$)
    if type(bsState) <> "roAssociativeArray" then print "Media state specified in transition not found" : stop

    userEvent = transitionXML.userEvent
    if userEvent.Count() <> 1 then print "Invalid XML file - userEvent not found" : stop
    userEventName$ = userEvent.name.GetText()

    nextMediaState$ = transitionXML.targetMediaState.GetText()
    
    transition = CreateObject("roAssociativeArray")
    transition.targetMediaState$ = nextMediaState$
    
    nextIsPrevious$ = transitionXML.targetIsPreviousState.GetText()
    transition.targetMediaStateIsPreviousState = false
    if nextIsPrevious$ <> "" and lcase(nextIsPrevious$) = "yes" then
        transition.targetMediaStateIsPreviousState = true
    endif
    
    if userEventName$ = "gpioUserEvent" then

        gpioInput$ = userEvent.parameters.parameter.GetText()
        gpioEvents = bsState.gpioEvents
        gpioEvents[gpioInput$] = transition
        
    else if userEventName$ = "bp900UserEvent" then

        bp900Input$ = userEvent.parameters.parameter.GetText()
        bp900Events = bsState.bp900Events
        bp900Events[bp900Input$] = transition
        
    else if userEventName$ = "serial" then

        ' support both old style and new style serial events
        if userEvent.parameters.parameter2.Count() = 1 then
            port$ = userEvent.parameters.parameter.GetText()
            serial$ = userEvent.parameters.parameter2.GetText()
        else
            port$ = "0"
            serial$ = userEvent.parameters.parameter.GetText()
        endif
        
        serialEvents = bsState.serialEvents
        if type(serialEvents[port$]) <> "roAssociativeArray" then
            serialEvents[port$] = CreateObject("roAssociativeArray")
        endif
        
        serialPortEvents = serialEvents[port$]
        serialPortEvents[serial$] = transition
                            
    else if userEventName$ = "timeout" then
    
        bsState.mstimeoutValue% = int(val(userEvent.parameters.parameter.GetText()) * 1000)        
        bsState.mstimeoutEvent = transition

    else if userEventName$ = "mediaEnd" then
    
        if type(bsState.videoItem) = "roAssociativeArray" then
        
            bsState.videoEndEvent = transition

        else if type(bsState.audioItem) = "roAssociativeArray" then
        
            bsState.audioEndEvent = transition

        else if type(bsState.signChannelItem) = "roAssociativeArray" then
        
            bsState.signChannelEndEvent = transition
        
        endif
        
    else if userEventName$ = "keyboard" then
'FIX FIX FIX    
        keyboardChar$ = userEvent.parameters.parameter.GetText()
        
        if type(bsState.keyboardEvents) <> "roAssociativeArray" then
            bsState.keyboardEvents = CreateObject("roAssociativeArray")
        endif
        
        bsState.keyboardEvents[keyboardChar$] = transition
                
    else if userEventName$ = "remote" then
'FIX FIX FIX    
        remote$ = ucase(userEvent.parameters.parameter.GetText())

        if type(bsState.remoteEvents) <> "roAssociativeArray" then
            bsState.remoteEvents = CreateObject("roAssociativeArray")
        endif
        
        bsState.remoteEvents[remote$] = transition
        
    else if userEventName$ = "usb" then
'FIX FIX FIX    
        usbString$ = userEvent.parameters.parameter.GetText()
        
        if type(bsState.usbStringEvents) <> "roAssociativeArray" then
            bsState.usbStringEvents = CreateObject("roAssociativeArray")
        endif
        
        bsState.usbStringEvents[usbString$] = transition
        
    else if userEventName$ = "udp" then
'FIX FIX FIX    
        udp$ = userEvent.parameters.parameter.GetText()
        
        if type(bsState.udpEvents) <> "roAssociativeArray" then
            bsState.udpEvents = CreateObject("roAssociativeArray")
        endif
        
        bsState.udpEvents[udp$] = transition
    
    else if userEventName$ = "synchronize" then
'FIX FIX FIX    
        synchronize$ = userEvent.parameters.parameter.GetText()
        
        if type(bsState.synchronizeEvents) <> "roAssociativeArray" then
            bsState.synchronizeEvents = CreateObject("roAssociativeArray")
        endif
        
        bsState.synchronizeEvents[synchronize$] = transition
    
    else if userEventName$ = "internalSynchronize" then
    
        internalSynchronize$ = userEvent.parameters.parameter.GetText()
        
        if type(bsState.internalSynchronizeEvents) <> "roAssociativeArray" then
            bsState.internalSynchronizeEvents = CreateObject("roAssociativeArray")
        endif
        
        bsState.internalSynchronizeEvents[internalSynchronize$] = transition
        
    else if userEventName$ = "rectangularTouchEvent" then 
        
        if type(bsState.touchEvents) <> "roAssociativeArray" then
            bsState.touchEvents = CreateObject("roAssociativeArray")
        endif
        
        transition.x% = int(val(userEvent.parameters.x.GetText()))
        transition.y% = int(val(userEvent.parameters.y.GetText()))
        transition.width% = int(val(userEvent.parameters.width.GetText()))
        transition.height% = int(val(userEvent.parameters.height.GetText()))
        
        if sign.flipCoordinates then
            videoMode = CreateObject("roVideoMode")
            resX = videoMode.GetResX()
            resY = videoMode.GetResY()
            videoMode = 0
            
            transition.x% = resX - (transition.x% + transition.width%)
            transition.y% = resY - (transition.y% + transition.height%)
        endif
    
        rolloverSpec = userEvent.parameters.rollover
        if rolloverSpec.Count() > 0 then
            rolloverAttrs = rolloverSpec.GetAttributes()
            transition.onBitmap$ = rolloverAttrs["onBitmap"]
            transition.offBitmap$ = rolloverAttrs["offBitmap"]
            transition.rolloverXOrigin% = int(val(rolloverAttrs["x"]))
            transition.rolloverYOrigin% = int(val(rolloverAttrs["y"]))
        endif
        
        bsState.touchEvents[stri(sign.numTouchEvents%)] = transition
        sign.numTouchEvents% = sign.numTouchEvents% + 1

    else if userEventName$ = "videoTimeCodeEvent" then
    
        if type(bsState.videoTimeCodeEvents) <> "roAssociativeArray" then
            bsState.videoTimeCodeEvents = CreateObject("roAssociativeArray")
        endif
    
        transition.timeInMS% = int(val(userEvent.parameters.parameter.GetText()))
        bsState.videoTimeCodeEvents[stri(sign.numVideoTimeCodeEvents%)] = transition

        sign.numVideoTimeCodeEvents% = sign.numVideoTimeCodeEvents% + 1

    endif
    
    for each transitionCommandXML in transitionXML.GetChildElements()

        if type(transition.transitionCmds) <> "roArray" then
            transition.transitionCmds = CreateObject("roArray", 1, true)
        endif
        
        if transitionCommandXML.GetName() = "brightSignCmd" then
            newCmd(transitionCommandXML, transition.transitionCmds)
        endif

        ' convert any old style command
        if transitionCommandXML.GetName() = "brightSignCommand" then
            transitionCmd = newCommand(command)
            transition.transitionCmds.push(transitionCmd)
        endif
        
    next
    
    for each transitionCmd in transition.transitionCmds
    
        ' if the transition command is for an internal synchronize, add an event that the master will receive after it sends the preload command
        if transitionCmd.name$ = "internalSynchronize" then
        
            if type(bsState.internalSynchronizeEventsMaster) <> "roAssociativeArray" then
                bsState.internalSynchronizeEventsMaster = CreateObject("roAssociativeArray")
            endif
            
            internalSynchronizeMasterTransition = CreateObject("roAssociativeArray")
            internalSynchronizeMasterTransition.targetMediaState$ = nextMediaState$
            internalSynchronizeMasterTransition.targetMediaStateIsPreviousState = false

            bsState.internalSynchronizeEventsMaster[transitionCmd.parameters["synchronizeKeyword"]] = internalSynchronizeMasterTransition

            ' modify this state's transition to not go to the next media state
            transition.internalSyncTargetMediaState$ = transition.targetMediaState$
            transition.targetMediaState$ = ""
            
        endif
    
    next

End Sub


Function newZone(sign As Object, zoneXML As Object, globalVariables As Object) As Object

    zoneType$ = zoneXML.type.GetText()

    ' create objects and read zone specific parameters
    
    if zoneType$ = "VideoOrImages" then
    
        zoneBS = newVideoOrImagesZone(zoneXML, sign)
        
    else if zoneType$ = "VideoOnly" then
    
        zoneBS = newVideoZone(zoneXML, sign)
        
    else if zoneType$ = "Images" then
    
        zoneBS = newImagesZone(zoneXML)
        
    else if zoneType$ = "Ticker" then
    
        zoneBS = newTickerZone(zoneXML)
        
    else if zoneType$ = "Clock" then
    
        zoneBS = newClockZone(zoneXML)
    
    else if zoneType$ = "BackgroundImage" then
    
        zoneBS = newBackgroundImageZone(zoneXML)
            
    endif
        
    zoneBS.Initialize = InitializeZone
    zoneBS.InitializeVideoOrImagesZone = InitializeVideoOrImagesZone
    zoneBS.InitializeVideoZone = InitializeVideoZone
    zoneBS.InitializeImagesZone = InitializeImagesZone
    zoneBS.InitializeClockZone = InitializeClockZone
    zoneBS.InitializeTickerZone = InitializeTickerZone
    zoneBS.InitializeBackgroundImageZone = InitializeBackgroundImageZone
        
    zoneBS.language$ = globalVariables.language$
    
    ' create and read playlist
    playlistXML = zoneXML.playlist
    numPlaylists% = playlistXML.Count()
        
    if numPlaylists% = 1 then
        zoneBS.playlist = newPlaylist(sign, playlistXML)
    endif
        
    zoneBS.playbackActive = false

    return zoneBS
    
End Function


Sub newZoneCommon(zoneXML As Object, zoneBS As Object)

    zoneBS.name$ = zoneXML.name.GetText()
    zoneBS.x% = int(val(zoneXML.x.GetText()))
    zoneBS.y% = int(val(zoneXML.y.GetText()))
    zoneBS.width% = int(val(zoneXML.width.GetText()))
    zoneBS.height% = int(val(zoneXML.height.GetText()))
    zoneBS.type$ = zoneXML.type.GetText()
    ' is this needed? zoneBS.id$ = zoneXML.id.GetText()

End Sub


Function newVideoZone(zoneXML As Object, sign As Object) As Object

    zoneBS = CreateObject("roAssociativeArray")

    newZoneCommon(zoneXML, zoneBS)
    
    zoneBS.viewMode% = GetViewModeValue(zoneXML.zoneSpecificParameters.viewMode.GetText())
    zoneBS.audioOutput% = GetAudioOutputValue(zoneXML.zoneSpecificParameters.audioOutput.GetText())
    zoneBS.audioMode% = GetAudioModeValue(zoneXML.zoneSpecificParameters.audioMode.GetText())
    zoneBS.audioMapping% = GetAudioMappingValue(zoneXML.zoneSpecificParameters.audioMapping.GetText())

    zoneBS.initialVideoVolume% = 100
    videoVolume$ = zoneXML.zoneSpecificParameters.videoVolume.GetText()
    if videoVolume$ <> "" then
        zoneBS.initialVideoVolume% = int(val(videoVolume$))
    endif
    
    zoneBS.initialAudioVolume% = 100
    audioVolume$ = zoneXML.zoneSpecificParameters.audioVolume.GetText()
    if audioVolume$ <> "" then
        zoneBS.initialAudioVolume% = int(val(audioVolume$))
    endif
    
    zoneBS.videoInput$ = zoneXML.zoneSpecificParameters.liveVideoInput.GetText()
    zoneBS.videoStandard$ = zoneXML.zoneSpecificParameters.liveVideoStandard.GetText()
    zoneBS.brightness% = int(val(zoneXML.zoneSpecificParameters.brightness.GetText()))
    zoneBS.contrast% = int(val(zoneXML.zoneSpecificParameters.contrast.GetText()))
    zoneBS.saturation% = int(val(zoneXML.zoneSpecificParameters.saturation.GetText()))
    zoneBS.hue% = int(val(zoneXML.zoneSpecificParameters.hue.GetText()))
    
    return zoneBS

End Function


Function newVideoOrImagesZone(zoneXML As Object, sign As Object) As Object

    zoneBS = newVideoZone(zoneXML, sign)
    
    zoneBS.imageMode% = GetImageModeValue(zoneXML.zoneSpecificParameters.imageMode.GetText())

    return zoneBS
    
End Function


Function newImagesZone(zoneXML As Object) As Object

    zoneBS = CreateObject("roAssociativeArray")

    newZoneCommon(zoneXML, zoneBS)
    
    zoneBS.imageMode% = GetImageModeValue(zoneXML.zoneSpecificParameters.imageMode.GetText())

    return zoneBS
    
End Function


Function newTickerZone(zoneXML As Object) As Object

    zoneBS = CreateObject("roAssociativeArray")

    newZoneCommon(zoneXML, zoneBS)

	zoneBS.numberOfLines% = 3
	zoneBS.delay% = 5
	zoneBS.rotation% = 0
	zoneBS.alignment% = 0
    zoneBS.scrollingMethod% = 0
    
    ' don't use dot notation due to BrightScript bugs
    
    zoneSpecificParametersElements = zoneXML.GetNamedElements("zoneSpecificParameters")
    if zoneSpecificParametersElements.Count() = 1 then
		' zoneSpecificParameters = zoneSpecificParametersElements[0]
		textWidgetElements = zoneSpecificParametersElements[0].GetNamedElements("textWidget")
		if textWidgetElements.Count() = 1 then
			
			textWidgetXML = textWidgetElements[0]

			textWidgetItems = textWidgetXML.GetChildElements()
			for each textWidgetItem in textWidgetItems
				print textWidgetItem.GetName();"=";textWidgetItem.GetText()
				
				itemName$ = textWidgetItem.GetName()
				if itemName$ = "numberOfLines" then
					zoneBS.numberOfLines% = int(val(textWidgetItem.GetText()))
				else if itemName$ = "delay" then
					zoneBS.delay% = int(val(textWidgetItem.GetText()))
				else if itemName$ = "rotation" then
					rotation$ = textWidgetItem.GetText()
					if rotation$ = "90" then
						zoneBS.rotation% = 3
					else if rotation$ = "180" then
						zoneBS.rotation% = 2
					else if rotation$ = "270" then
						zoneBS.rotation% = 1
					endif
				else if itemName$ = "alignment" then
					alignment$ = textWidgetItem.GetText()
					if alignment$ = "center" then
						zoneBS.alignment% = 1
					else if alignment$ = "right" then
						zoneBS.alignment% = 2
					endif
				else if itemName$ = "scrollingMethod" then
					zoneBS.scrollingMethod% = int(val(textWidgetItem.GetText()))
				endif
				
			next
			
'			numberOfLinesElements = textWidgetXML.GetNamedElements("numberOfLines")
'			if numberOfLinesElements.Count() = 1 then
'				zoneBS.numberOfLines% = int(val(numberOfLinesElements[0].GetText()))
'			endif
			
'			delayElements = textWidgetXML.GetNamedElements("delay")
'			if delayElements.Count() = 1 then
'				zoneBS.delay% = int(val(delayElements[0].GetText()))
'			endif
			
'			rotationElements = textWidgetXML.GetNamedElements("rotation")
'			if rotationElements.Count() = 1 then
'				rotation$ = rotationElements[0].GetText()
'				if rotation$ = "90" then
'					zoneBS.rotation% = 3
'				else if rotation$ = "180" then
'					zoneBS.rotation% = 2
'				else if rotation$ = "270" then
'					zoneBS.rotation% = 1
'				endif
'			endif
			
'			alignmentElements = textWidgetXML.GetNamedElements("alignment")
'			if alignmentElements.Count() = 1 then
'				alignment$ = alignmentElements[0].GetText()
'				if alignment$ = "center" then
'					zoneBS.alignment% = 1
'				else if alignment$ = "right" then
'					zoneBS.alignment% = 2
'				endif
'			endif
			
'			scrollingMethodElements = textWidgetXML.GetNamedElements("scrollingMethod")
'			if scrollingMethodElements.Count() = 1 then
'				zoneBS.scrollingMethod% = int(val(scrollingMethodElements[0].GetText()))
'			endif
			
		endif
    endif
        
    widget = zoneXML.zoneSpecificParameters.widget
    foregroundTextColor = widget.foregroundTextColor
    zoneBS.foregroundTextColor% = GetColor(foregroundTextColor.GetAttributes())
    backgroundTextColor = widget.backgroundTextColor
    zoneBS.backgroundTextColor% = GetColor(backgroundTextColor.GetAttributes())
    zoneBS.font$ = widget.font.GetText()

    backgroundBitmap = widget.backgroundBitmap
    if backgroundBitmap.Count() = 1 then
        backgroundBitmapAttrs = backgroundBitmap.GetAttributes()
        zoneBS.backgroundBitmapFile$ = backgroundBitmapAttrs["file"]
        stretchStr = backgroundBitmapAttrs["stretch"]
        if stretchStr = "true" then
            zoneBS.stretch% = 1
        else
            zoneBS.stretch% = 0
        endif
    endif
    
    safeTextRegion = widget.safeTextRegion
    if safeTextRegion.Count() = 1 then
        zoneBS.safeTextRegionX% = int(val(safeTextRegion.safeTextRegionX.GetText()))
        zoneBS.safeTextRegionY% = int(val(safeTextRegion.safeTextRegionY.GetText()))
        zoneBS.safeTextRegionWidth% = int(val(safeTextRegion.safeTextRegionWidth.GetText()))
        zoneBS.safeTextRegionHeight% = int(val(safeTextRegion.safeTextRegionHeight.GetText()))
    endif
    
    return zoneBS
    
End Function


Function newClockZone(zoneXML As Object) As Object

    zoneBS = CreateObject("roAssociativeArray")

    newZoneCommon(zoneXML, zoneBS)
    
    displayTime$ = zoneXML.zoneSpecificParameters.displayTime.GetText()
    if lcase(displayTime$) = "true" then
        zoneBS.displayTime% = 1
    else
        zoneBS.displayTime% = 0
    endif
    
    widget = zoneXML.zoneSpecificParameters.widget
    foregroundTextColor = widget.foregroundTextColor
    zoneBS.foregroundTextColor% = GetColor(foregroundTextColor.GetAttributes())
    backgroundTextColor = widget.backgroundTextColor
    zoneBS.backgroundTextColor% = GetColor(backgroundTextColor.GetAttributes())
    zoneBS.font$ = widget.font.GetText()

    backgroundBitmap = widget.backgroundBitmap
    if backgroundBitmap.Count() = 1 then
        backgroundBitmapAttrs = backgroundBitmap.GetAttributes()
        zoneBS.backgroundBitmapFile$ = backgroundBitmapAttrs["file"]
        stretchStr = backgroundBitmapAttrs["stretch"]
        if stretchStr = "true" then
            zoneBS.stretch% = 1
        else
            zoneBS.stretch% = 0
        endif
    endif
    
    safeTextRegion = widget.safeTextRegion
    if safeTextRegion.Count() = 1 then
        zoneBS.safeTextRegionX% = int(val(safeTextRegion.safeTextRegionX.GetText()))
        zoneBS.safeTextRegionY% = int(val(safeTextRegion.safeTextRegionY.GetText()))
        zoneBS.safeTextRegionWidth% = int(val(safeTextRegion.safeTextRegionWidth.GetText()))
        zoneBS.safeTextRegionHeight% = int(val(safeTextRegion.safeTextRegionHeight.GetText()))
    endif
    
    return zoneBS
    
End Function


Function newBackgroundImageZone(zoneXML As Object) As Object

    zoneBS = CreateObject("roAssociativeArray")

    newZoneCommon(zoneXML, zoneBS)
    
    return zoneBS
    
End Function


Sub InitializeZone(existingSign As Object, msgPort As Object)

    zone = m

    zone.msgPort = msgPort
    
'    zone.videoPlayer = 0
'    zone.videoInput = 0
'    zone.imagePlayer = 0
'    zone.audioPlayer = 0
'    zone.videoInput = 0
'    zone.widget = 0
    zone.isVideoZone = false
    zone.isStaticImage = false
    zone.preloadedStateName$ = ""
    
    zone.rectangle = CreateObject("roRectangle", zone.x%, zone.y%, zone.width%, zone.height%)

    if zone.type$ = "VideoOrImages" then
        zone.InitializeVideoOrImagesZone(existingSign)
    else if zone.type$ = "VideoOnly" then
        zone.InitializeVideoZone(existingSign)
    else if zone.type$ = "Images" then
        zone.InitializeImagesZone()
    else if zone.type$ = "Clock" then
        zone.InitializeClockZone()
    else if zone.type$ = "Ticker" then
        zone.InitializeTickerZone()
    else if zone.type$ = "BackgroundImage" then
        zone.InitializeBackgroundImageZone()
    endif
    
End Sub


Sub InitializeVideoZone(existingSign As Object)

    zone = m
    
    ' create players
    
    ' reuse video player if possible
    createVideoPlayer = true
    if type(existingSign) = "roAssociativeArray" then
        if type(existingSign.videoZone) = "roAssociativeArray" then
            if type(existingSign.VideoZone.videoPlayer) = "roVideoPlayer" then
                print "reuse existing videoPlayer"
                videoPlayer = existingSign.videoZone.videoPlayer
                createVideoPlayer = false
            endif
        endif
    endif
    
    if createVideoPlayer then
        videoPlayer = CreateObject("roVideoPlayer")
    endif
    
    if type(videoPlayer) <> "roVideoPlayer" then print "videoPlayer creation failed" : stop
    videoPlayer.SetRectangle(zone.rectangle)
    
    videoInput = CreateObject("roVideoInput")

    zone.videoPlayer = videoPlayer
    zone.videoInput = videoInput
    zone.isVideoZone = true
    zone.videoVolume% = zone.initialVideoVolume%
    zone.audioVolume% = zone.initialAudioVolume%
    
    zone.videoChannelVolumes = CreateObject("roArray", 6, true)
    zone.audioChannelVolumes = CreateObject("roArray", 6, true)
    for i% = 0 to 5
        zone.videoChannelVolumes[i%] = zone.videoVolume%
        zone.audioChannelVolumes[i%] = zone.audioVolume%
    next
    
    ' initialize video player parameters
    videoPlayer.SetPort(zone.msgPort)
    videoPlayer.SetViewMode(zone.viewMode%)
    videoPlayer.SetLoopMode(false)
    videoPlayer.SetAudioOutput(zone.audioOutput%)
    videoPlayer.SetAudioMode(zone.audioMode%)
    videoPlayer.MapStereoOutput(zone.audioMapping%)
    videoPlayer.SetVolume(zone.videoVolume%)

    ' initialize live video parameters
    videoInput.SetInput(zone.videoInput$)
    videoInput.SetStandard(zone.videoStandard$)
    videoInput.SetControlValue("brightness", zone.brightness%)
    videoInput.SetControlValue("contrast", zone.contrast%)
    videoInput.SetControlValue("saturation", zone.saturation%)
    videoInput.SetControlValue("hue", zone.hue%)

End Sub


Sub InitializeVideoOrImagesZone(existingSign As Object)

    m.InitializeVideoZone(existingSign)
    
    zone = m
    
    ' create players
    
    imagePlayer = CreateObject("roImageWidget", zone.rectangle)

    audioPlayer = CreateObject("roAudioPlayer")
    audioPlayer.SetPort(zone.msgPort)
    
    zone.imagePlayer = imagePlayer
    zone.audioPlayer = audioPlayer
    zone.audioVolume% = zone.initialAudioVolume%
    
    audioPlayer.SetAudioOutput(zone.audioOutput%)
    audioPlayer.SetAudioMode(zone.audioMode%)
    audioPlayer.MapStereoOutput(zone.audioMapping%)
    audioPlayer.SetVolume(zone.audioVolume%)
    audioPlayer.SetLoopMode(false)
              
    ' initialize image player parameters
    imagePlayer.SetDefaultMode(zone.imageMode%)

End Sub


Sub InitializeImagesZone()

    zone = m
    
    imagePlayer = CreateObject("roImageWidget", zone.rectangle)
    
    zone.imagePlayer = imagePlayer
    
    ' initialize image player parameters
    imagePlayer.SetDefaultMode(zone.imageMode%)

End Sub


Sub InitializeClockZone()

    zone = m

    resourceManager = CreateObject("roResourceManager", "resources.txt")
    
    if type(resourceManager) = "roResourceManager" then

        ok = resourceManager.SetLanguage(zone.language$)
        if not ok then print "No resources for language ";zone.language$ : stop

        clockWidget = CreateObject("roClockWidget", zone.rectangle, resourceManager, zone.displayTime%)

        zone.widget = clockWidget
        
    endif
    
    
End Sub


Sub InitializeTickerZone()

    zone = m
    
    a=CreateObject("roAssociativeArray")
    a["PauseTime"] = zone.delay%
    a["Rotation"] = zone.rotation%
    a["Alignment"] = zone.alignment%
    
    textWidget = CreateObject("roTextWidget", zone.rectangle, zone.numberOfLines%, zone.scrollingMethod%, a)

    zone.widget = textWidget
    
End Sub


Sub InitializeBackgroundImageZone()

    zone = m
    
    ' create players
    
    videoPlayer = CreateObject("roVideoPlayer")
    if type(videoPlayer) <> "roVideoPlayer" then print "videoPlayer creation failed" : stop
    videoPlayer.SetRectangle(zone.rectangle)
    
    zone.videoPlayer = videoPlayer
    zone.isVideoZone = true
              
    ' initialize video player parameters
    ' videoPlayer.SetPort(zone.msgPort)
    ' videoPlayer.SetViewMode(zone.viewMode%)
    ' videoPlayer.SetLoopMode(false)
    ' videoPlayer.SetAudioOutput(zone.audioOutput%)
    ' videoPlayer.SetAudioMode(zone.audioMode%)
    ' videoPlayer.MapStereoOutput(zone.audioMapping%)
    
End Sub


Function newPlaylist(sign As Object, playlistXML As Object) As Object

    playlistBS = CreateObject("roAssociativeArray")
    
    playlistBS.name$ = playlistXML.name.GetText()

    ' get states
    
    stateList = playlistXML.states.state
    if type(stateList) <> "roXMLList" then print "Invalid XML file - state list not found" : stop
    
    initialStateXML = playlistXML.states.initialState
    if type(initialStateXML) <> "roXMLList" then print "Invalid XML file - initial state not found" : stop

    initialStateName$ = initialStateXML.GetText()
    
    playlistBS.stateTable = CreateObject("roAssociativeArray")

    for each state in stateList
    
        bsState = newState(state)
        
        if bsState.name$ = initialStateName$ then
            playlistBS.firstState = bsState
            playlistBS.firstStateName$ = initialStateName$
        endif
        
        playlistBS.stateTable[bsState.name$] = bsState
        
    next
      
    ' get transitions    
    transitionList = playlistXML.states.transition

    for each transition in transitionList
    
        newTransition(sign, transition, playlistBS)
        
    next
                
    return playlistBS
    
End Function


Sub newMediaPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    file = playlistItemXML.file
    fileAttrs = file.GetAttributes()
    playlistItemBS.fileName$ = fileAttrs["name"]

End Sub


Sub newVideoPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    newMediaPlaylistItem(playlistItemXML, playlistItemBS)
    itemVolume$ = playlistItemXML.volume.GetText()
    if itemVolume$ <> "" then
        playlistItemBS.volume% = int(val(itemVolume$))
    endif
    
End Sub


Sub newLiveVideoPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    itemVolume$ = playlistItemXML.volume.GetText()
    if itemVolume$ <> "" then
        playlistItemBS.volume% = int(val(itemVolume$))
    endif

    playlistItemBS.timeOnScreen% = int(val(playlistItemXML.timeOnScreen.GetText()))
    
End Sub


Sub newSignChannelPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    playlistItemBS.timeOnScreen% = int(val(playlistItemXML.timeOnScreen.GetText()))
    playlistItemBS.rssURL$ = ""
    playlistItemBS.slideTransition% = 15
    
End Sub


Sub newRSSImagePlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    rssSpec = playlistItemXML.rssSpec
    rssSpecAttrs = rssSpec.GetAttributes()
    playlistItemBS.rssURL$ = rssSpecAttrs["url"]
    
End Sub


Sub newImagePlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    newMediaPlaylistItem(playlistItemXML, playlistItemBS)
    playlistItemBS.slideDelayInterval% = int(val(playlistItemXML.slideDelayInterval.GetText()))
    playlistItemBS.slideTransition% = GetSlideTransitionValue(playlistItemXML.slideTransition.GetText())

End Sub


Sub newAudioPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    newMediaPlaylistItem(playlistItemXML, playlistItemBS)

    itemVolume$ = playlistItemXML.volume.GetText()
    if itemVolume$ <> "" then
        playlistItemBS.volume% = int(val(itemVolume$))
    endif

End Sub


Sub newBackgroundImagePlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    newMediaPlaylistItem(playlistItemXML, playlistItemBS)
    
End Sub


Sub newClockPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

End Sub


Sub newTextPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    strings = playlistItemXML.strings

    numTextStrings% = 0
    if strings <> invalid then
        children = strings.GetChildElements()
        if children <> invalid then
            numTextStrings% = children.Count()
        end if
    end if

    playlistItemBS.textStrings = CreateObject("roArray", numTextStrings%, true)

    for each textStringXML in strings.GetChildElements()

        textString = textStringXML.GetText()
        playlistItemBS.textStrings.push(textString)

    next

End Sub


Sub newRSSPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    ' read rss url
    ' read rss title
    rssSpec = playlistItemXML.rssSpec
    rssSpecAttrs = rssSpec.GetAttributes()
    playlistItemBS.rssURL$ = rssSpecAttrs["url"]
    playlistItemBS.rssTitle$ = rssSpecAttrs["title"]
    
    playlistItemBS.rssURLXfer = CreateObject("roUrlTransfer")
    playlistItemBS.rssURLXfer.SetUrl(playlistItemBS.rssURL$)
    playlistItemBS.rssURLXfer.GetToFile("tmp:/rss.xml")
    playlistItemBS.rssParser = CreateObject("roRssParser")
    playlistItemBS.rssParser.ParseFile("tmp:/rss.xml")
        
End Sub


Sub newGPIOOnOffPlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    gpioAttrs = playlistItemXML.GetAttributes()
    playlistItemBS.gpioPort% = int(val(gpioAttrs["gpioPort"]))
    
End Sub


Sub newGPIOSetStatePlaylistItem(playlistItemXML As Object, playlistItemBS As Object)

    playlistItemBS.gpioStates = CreateObject("roArray", 8, true)

    playlistItemBS.gpioStates[0] = playlistItemXML.gpio0.GetText()
    playlistItemBS.gpioStates[1] = playlistItemXML.gpio1.GetText()
    playlistItemBS.gpioStates[2] = playlistItemXML.gpio2.GetText()
    playlistItemBS.gpioStates[3] = playlistItemXML.gpio3.GetText()
    playlistItemBS.gpioStates[4] = playlistItemXML.gpio4.GetText()
    playlistItemBS.gpioStates[5] = playlistItemXML.gpio5.GetText()
    playlistItemBS.gpioStates[6] = playlistItemXML.gpio6.GetText()
    playlistItemBS.gpioStates[7] = playlistItemXML.gpio7.GetText()
    
End Sub


Sub newGPIOWaitForAnyInputItem(playlistItemXML As Object, playlistItemBS As Object)

    
End Sub


Function FixDateTime(dateTime$ As String) As Object

    dateTime = CreateObject("roDateTime")
    
    ' strip '-' and ':' so that BrightSign can parse the dateTime properly
    index = instr(1, dateTime$, "-")
    while index > 0
        
        a$ = mid(dateTime$, 1, index - 1)
        b$ = mid(dateTime$, index + 1)
        dateTime$ = a$ + b$

        index = instr(1, dateTime$, "-")
        
    end while

    index = instr(1, dateTime$, ":")
    while index > 0
        
        a$ = mid(dateTime$, 1, index - 1)
        b$ = mid(dateTime$, index + 1)
        dateTime$ = a$ + b$

        index = instr(1, dateTime$, ":")
        
    end while

	if not dateTime.FromIsoString(dateTime$) then stop

    return dateTime
        
End Function


Function newScheduledEvent(scheduledEventXML As Object) As Object

    scheduledEventBS = CreateObject("roAssociativeArray")
        
    if scheduledEventXML.playlist.Count() > 0 then
        scheduledEventBS.playlist$ = scheduledEventXML.playlist.GetText()
    endif
    if scheduledEventXML.presentationToSchedule.Count() > 0 then
        scheduledEventBS.presentationName$ = scheduledEventXML.presentationToSchedule.name.GetText()
    endif
    
    dateTime$ = scheduledEventXML.dateTime.GetText()    
    scheduledEventBS.dateTime = FixDateTime(dateTime$)
    
    scheduledEventBS.duration% = int(val(scheduledEventXML.duration.GetText()))
    
    if lcase(scheduledEventXML.allDayEveryDay.GetText()) = "true" then
        scheduledEventBS.allDayEveryDay = true
    else
        scheduledEventBS.allDayEveryDay = false
    endif
        
    if lcase(scheduledEventXML.recurrence.GetText()) = "true" then
        scheduledEventBS.recurrence = true
    else
        scheduledEventBS.recurrence = false
    endif
        
    scheduledEventBS.recurrencePattern$ = scheduledEventXML.recurrencePattern.GetText()
    
    scheduledEventBS.recurrencePatternDaily$ = scheduledEventXML.recurrencePatternDaily.GetText()

    scheduledEventBS.recurrencePatternDaysOfWeek% = int(val(scheduledEventXML.recurrencePatternDaysOfWeek.GetText()))
    
    dateTime$ = scheduledEventXML.recurrenceStartDate.GetText()    
    scheduledEventBS.recurrenceStartDate = FixDateTime(dateTime$)

    if lcase(scheduledEventXML.recurrenceGoesForever.GetText()) = "true" then
        scheduledEventBS.recurrenceGoesForever = true
    else
        scheduledEventBS.recurrenceGoesForever = false
    endif
    
    dateTime$ = scheduledEventXML.recurrenceEndDate.GetText()    
    recurrenceEndDate = FixDateTime(dateTime$)
    recurrenceEndDate.AddSeconds(60 * 60 * 24) ' adjust the recurrence end date to refer to the beginning of the next day
    scheduledEventBS.recurrenceEndDate = recurrenceEndDate
            
    return scheduledEventBS    
    
End Function


Function GetActiveScheduledEvent() As Object

'   determine if there is a scheduled event that should be active at this time

systemTime = CreateObject("roSystemTime")    
eventDateTime = systemTime.GetLocalDateTime()
' print "GetActiveScheduledEvent() called on zone ";zone.name$;" at ";eventDateTime.GetString()
systemTime = 0

    activeScheduledEvent = 0

    for each scheduledEvent in m.scheduledEvents
    
'       is there a playlist that should be active now based on the scheduledEvent?

        if scheduledEvent.allDayEveryDay then
        
            activeScheduledEvent = scheduledEvent
            exit for
            
        endif
                         
'       is the current scheduledEvent active today? if no, go to next scheduledEvent

        eventDateTime = scheduledEvent.dateTime
        systemTime = CreateObject("roSystemTime")
        currentDateTime = systemTime.GetLocalDateTime()

        scheduledEventActiveToday = false
        
'       if it's not a recurring event and its start date is today, then it is active today

        if not scheduledEvent.recurrence then
        
            if eventDateTime.GetYear() = currentDateTime.GetYear() and eventDateTime.GetMonth() = currentDateTime.GetMonth() and eventDateTime.GetDay() = currentDateTime.GetDay() then
               scheduledEventActiveToday = true
            endif
            
        endif
        
        if (not scheduledEventActiveToday) and scheduledEvent.recurrence then
        
'           determine if the date represented by the scheduled event is within the recurrence range

            dateWithinRange = false
            if scheduledEvent.recurrenceStartDate.GetString() < currentDateTime.GetString() then
            
                if scheduledEvent.recurrenceGoesForever then
                    dateWithinRange = true
                else if scheduledEvent.recurrenceEndDate.GetString() >= currentDateTime.GetString() then
                    dateWithinRange = true
                endif
                
            endif
                            
'           if it is within the range, check the recurrence pattern

            if dateWithinRange then
            
                if scheduledEvent.recurrencePattern$ = "Daily" then
                
                    if scheduledEvent.recurrencePatternDaily$ = "EveryDay" then
            
                        scheduledEventActiveToday = true
                        
                    else if scheduledEvent.recurrencePatternDaily$ = "EveryWeekday" then
                    
                        if currentDateTime.GetDayOfWeek() > 0 and currentDateTime.GetDayOfWeek() < 6 then
                        
                            scheduledEventActiveToday = true
                        
                        endif
                    
                    else ' EveryWeekend
                    
                        if currentDateTime.GetDayOfWeek() = 0 or currentDateTime.GetDayOfWeek() = 6 then
                        
                            scheduledEventActiveToday = true
                            
                        endif
                        
                    endif
                    
                else ' Weekly
                
                    bitwiseDaysOfWeek% = scheduledEvent.recurrencePatternDaysOfWeek%
                    currentDayOfWeek = currentDateTime.GetDayOfWeek()
                    bitDayOfWeek% = 2 ^ currentDayOfWeek
                    if (bitwiseDaysOfWeek% and bitDayOfWeek%) <> 0 then
                        scheduledEventActiveToday = true
                    endif
                                        
                endif
                                
            endif
            
        endif

'           see if the currentScheduledEvent should be active right now
'               it will be active right now if its start time < current start time and its end time > current start time

        if scheduledEventActiveToday then
        
            eventTodayStartTime = systemTime.GetLocalDateTime()
            eventTodayStartTime.SetHour(scheduledEvent.dateTime.GetHour())
            eventTodayStartTime.SetMinute(scheduledEvent.dateTime.GetMinute())
            eventTodayStartTime.SetSecond(scheduledEvent.dateTime.GetSecond())
            eventTodayStartTime.SetMillisecond(0)
            
            eventTodayEndTime = systemTime.GetLocalDateTime()
            eventTodayEndTime.SetHour(scheduledEvent.dateTime.GetHour())
            eventTodayEndTime.SetMinute(scheduledEvent.dateTime.GetMinute())
            eventTodayEndTime.SetSecond(scheduledEvent.dateTime.GetSecond())
            eventTodayEndTime.SetMillisecond(0)
            eventTodayEndTime.AddSeconds(scheduledEvent.duration% * 60)
            
            if eventTodayStartTime.GetString() <= currentDateTime.GetString() and eventTodayEndTime.GetString() > currentDateTime.GetString() then
                
                activeScheduledEvent = scheduledEvent
                activeScheduledEvent.dateTime = eventTodayStartTime
                
'                print "at end, eventDateTime = ";eventDateTime.GetString()
'                print "at end, currentDateTime = ";currentDateTime.GetString()

                exit for
                
            endif
            
        endif

    next
    
    return activeScheduledEvent
    
End Function    


Function GetNextScheduledEventTime() As Object

    dim futureScheduledEvents[10]
    dim futureScheduledEventStartTimes[10]
    
    nextScheduledEventTime = CreateObject("roDateTime")
    
    ' for each scheduled event, see if it could start in the future. If yes, determine the earliest
    ' future start time that is later than now. Store the scheduled event and that start time.
    ' Use the scheduled event in that list with the lowest start time.
    
    for each scheduledEvent in m.scheduledEvents
    
        if scheduledEvent.allDayEveryDay then
        
            ' an allDayEveryDay event is always active, so by definition, it is not a future event.
            goto endLoop
            
        endif

        eventDateTime = scheduledEvent.dateTime
        systemTime = CreateObject("roSystemTime")
        currentDateTime = systemTime.GetLocalDateTime()

'       if it's not a recurring event and its start date/time is in the future, then it is eligible

        if not scheduledEvent.recurrence then
        
            if eventDateTime.GetString() > currentDateTime.GetString() then
                
                futureScheduledEvents.push(scheduledEvent)
                futureScheduledEventStartTimes.push(eventDateTime)
                goto endLoop
                
            endif
            
        endif
        
'       if it's a recurring event, see if its date range includes the future

        if scheduledEvent.recurrence then
        
            eventToday = CreateObject("roDateTime")
            eventToday.SetYear(currentDateTime.GetYear())
            eventToday.SetMonth(currentDateTime.GetMonth())
            eventToday.SetDay(currentDateTime.GetDay())
            eventToday.SetHour(eventDateTime.GetHour())
            eventToday.SetMinute(eventDateTime.GetMinute())

            if scheduledEvent.recurrenceGoesForever or scheduledEvent.recurrenceEndDate.GetString() > currentDateTime.GetString() then
        
'               find the earliest time > now that this recurring event could start

                if scheduledEvent.recurrencePattern$ = "Daily" then
                
                    if scheduledEvent.recurrencePatternDaily$ = "EveryDay" then
            
                        
                        if eventToday.GetString() > currentDateTime.GetString() then
                        
                            futureScheduledEvents.push(scheduledEvent)
                            futureScheduledEventStartTimes.push(eventToday)
                            goto endLoop
                        
                        else ' use the next day
                        
                            eventToday.AddSeconds(60 * 60 * 24)
                            futureScheduledEvents.push(scheduledEvent)
                            futureScheduledEventStartTimes.push(eventToday)
                            goto endLoop
                            
                        endif
                        
                    else if scheduledEvent.recurrencePatternDaily$ = "EveryWeekday" then
                        ' if today is a weekday, proceed as in the case above, except that instead of using
                        ' the 'next day', use the 'next weekday' (which may or may not be the next day) for the test
                        
                        if currentDateTime.GetDayOfWeek() > 0 and currentDateTime.GetDayOfWeek() < 6 then
                            
                            ' current day is a weekday
                            
                            if eventToday.GetString() > currentDateTime.GetString() then
                            
                                futureScheduledEvents.push(scheduledEvent)
                                futureScheduledEventStartTimes.push(eventToday)
                                goto endLoop
                        
                            else
                                
                                ' if today is Friday, add 3 days
                                daysToAdd% = 1
                                if currentDateTime.GetDayOfWeek() = 5 then daysToAdd% = 3
                                eventToday.AddSeconds(60 * 60 * 24 * daysToAdd%)
                                futureScheduledEvents.push(scheduledEvent)
                                futureScheduledEventStartTimes.push(eventToday)
                                goto endLoop
                                
                            endif
                            
                        else ' current day is a weekend
                            
                            ' if today is not a weekday, the next weekday (Monday) is the future event
                            daysToAdd% = 1
                            if currentDateTime.GetDayOfWeek() = 6 then daysToAdd% = 2
                            eventToday.AddSeconds(60 * 60 * 24 * daysToAdd%)
                            futureScheduledEvents.push(scheduledEvent)
                            futureScheduledEventStartTimes.push(eventToday)
                            goto endLoop
                            
                        endif
                    
                    else ' EveryWeekend
                        ' if today is a weekend, proceed as in the case above, except that instead of using
                        ' the 'next day', use the 'next weekend' (which may or may not be the next day) for the test

                        if currentDateTime.GetDayOfWeek() = 0 or currentDateTime.GetDayOfWeek() = 6 then
                            
                            ' current day is a weekend
                            
                            if eventToday.GetString() > currentDateTime.GetString() then
                            
                                futureScheduledEvents.push(scheduledEvent)
                                futureScheduledEventStartTimes.push(eventToday)
                                goto endLoop
                        
                            else
                                
                                ' if today is Sunday, add 6 days
                                daysToAdd% = 1
                                if currentDateTime.GetDayOfWeek() = 5 then daysToAdd% = 6
                                eventToday.AddSeconds(60 * 60 * 24 * daysToAdd%)
                                futureScheduledEvents.push(scheduledEvent)
                                futureScheduledEventStartTimes.push(eventToday)
                                goto endLoop
                                
                            endif
                            
                        else ' current day is a weekday
                            
                            ' if today is not a weekday, the next weekday (Monday) is the future event
                            daysToAdd% = 6 - currentDateTime.GetDayOfWeek()
                            eventToday.AddSeconds(60 * 60 * 24 * daysToAdd%)
                            futureScheduledEvents.push(scheduledEvent)
                            futureScheduledEventStartTimes.push(eventToday)
                            goto endLoop
                            
                        endif
                        
                    endif        
        
                else ' Weekly
                
                    ' if today is one of the days specified, test against today. if the test fails,
                    ' or today is not one of the days specified, find the next specified day and use it.
                    
                    bitwiseDaysOfWeek% = scheduledEvent.recurrencePatternDaysOfWeek%
                    currentDayOfWeek = currentDateTime.GetDayOfWeek()
                    bitDayOfWeek% = 2 ^ currentDayOfWeek
                    if (bitwiseDaysOfWeek% and bitDayOfWeek%) <> 0 then
                    
                        if eventToday.GetString() > currentDateTime.GetString() then
                        
                            futureScheduledEvents.push(scheduledEvent)
                            futureScheduledEventStartTimes.push(eventToday)
                            goto endLoop
                            
                        endif
                    
                    endif
                        
                    ' find the next specified day and use it    
                    if bitwiseDaysOfWeek% <> 0 then
                    
                        while true
                            currentDayOfWeek = currentDayOfWeek + 1
                            if currentDayOfWeek > 7 then currentDayOfWeek = 0
                            bitDayOfWeek% = 2 ^ currentDayOfWeek
                            eventToday.AddSeconds(60 * 60 * 24)
                            if (bitwiseDaysOfWeek% and bitDayOfWeek%) <> 0 then
                                futureScheduledEvents.push(scheduledEvent)
                                futureScheduledEventStartTimes.push(eventToday)
                                goto endLoop
                            endif
                        end while        
                        
                    endif

                endif
                
            endif
            
        endif        

endLoop:

    next
    
    ' sort the future events
    dim sortedFutureEventTimes[10]
    if futureScheduledEventStartTimes.Count() > 1 then
        SortFutureScheduledEvents(futureScheduledEventStartTimes, sortedFutureEventTimes)
        nextScheduledEventTime = futureScheduledEventStartTimes[sortedFutureEventTimes[0]]
    else
        nextScheduledEventTime = futureScheduledEventStartTimes[0]
    endif
        
    return nextScheduledEventTime
    
End Function


Sub SortFutureScheduledEvents(futureEventTimes As Object, sortedIndices As Object)

    ' initialize array with indices.
    for i% = 0 to futureEventTimes.Count()-1
        sortedIndices[i%] = i%
    next

    numItemsToSort% = futureEventTimes.Count()

    for i% = numItemsToSort% - 1 to 1 step -1
        for j% = 0 to i%-1
	        index0 = sortedIndices[j%]
	        time0 = futureEventTimes[index0].GetString()
            index1 = sortedIndices[j%+1]
            time1 = futureEventTimes[index1].GetString()
            if time0 > time1 then
                k% = sortedIndices[j%]
                sortedIndices[j%] = sortedIndices[j%+1]
                sortedIndices[j%+1] = k%
            endif
        next
    next

    return
    
End Sub


Function GetID(userData as Object, e as Object, eventData As Object)

    mVar = userData.mVar
    
    print "respond to GetID request"
  
    registrySection = CreateObject("roRegistrySection", "networking")
    if type(registrySection)<>"roRegistrySection" then print "Error: Unable to create roRegistrySection":stop
    unitName$ = registrySection.Read("un")
    unitNamingMethod$ = registrySection.Read("unm")
    unitDescription$ = registrySection.Read("ud")
    registrySection = invalid
    
    root = CreateObject("roXMLElement")
    root.SetName("BrightSignID")

    elem = root.AddElement("unitName")
    elem.SetBody(unitName$)

    elem = root.AddElement("unitNamingMethod")
    elem.SetBody(unitNamingMethod$)

    elem = root.AddElement("unitDescription")
    elem.SetBody(unitDescription$)

    elem = root.AddElement("serialNumber")
    elem.SetBody(mVar.sysInfo.deviceUniqueID$)

    elem = root.AddElement("functionality")
    elem.SetBody(mVar.lwsConfig$)
    
    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })

    e.AddResponseHeader("Content-type", "text/xml")
    e.SetResponseBodyString(xml)
    e.SendResponse(200)
  
    return eventData
    
End Function


Function GetHeartbeat(userData as Object, e as Object, eventData As Object)

    print "respond to GetHeartbeat request"
  
    e.AddResponseHeader("Content-type", "text/plain")
    e.SetResponseBodyString("Alive")
    e.SendResponse(200)
  
    return eventData
    
End Function


Function GetAutorunVersion(userData as Object, e as Object, eventData As Object)

    print "respond to GetAutorunVersion request"
  
    e.AddResponseHeader("Content-type", "text/plain")
    e.SetResponseBodyString("3.2.14LWS")
    e.SendResponse(200)
  
    return eventData
    
End Function


Function GetLocalSyncSpec(userData as Object, e as Object, eventData As Object)

    print "respond to GetLocalSyncSpec request"

    e.AddResponseHeader("Content-type", "text/plain")
    ok = e.SetResponseBodyFile("local-sync.xml")
    e.SendResponse(200)

    return eventData
    
End Function


Function GetPoolFiles(userData as Object, e as Object, eventData As Object)

    print "respond to GetPoolFiles request"

    root = CreateObject("roXMLElement")
    root.SetName("poolFiles")

    listOfPoolFiles = MatchFiles("/pool", "*")
    for each file in listOfPoolFiles
    
        pathOnCard$ = "pool/" + file
        fileInPool = CreateObject("roReadFile", pathOnCard$)
        
        if type(fileInPool) = "roReadFile" then
        
            fileInPool.SeekToEnd()
            fileSizeOnCard = fileInPool.CurrentPosition()
            
            item = root.AddBodyElement()
            item.SetName("file")

            elem = item.AddElement("fileName")
            elem.SetBody(file)

            elem = item.AddElement("length")
            elem.SetBody(stri(fileSizeOnCard))
            
        endif
        
    next

    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })

    e.AddResponseHeader("Content-type", "text/plain")
    e.SetResponseBodyString(xml)
    e.SendResponse(200)

    return eventData
    
End Function


Function FilePosted(userData as Object, e as Object, eventData As Object)

    print "respond to FilePosted request"

    MoveFile(e.GetRequestBodyFile(), e.GetRequestHeader("Destination-Filename"))
    e.SetResponseBodyString("RECEIVED")
    e.SendResponse(200)
    
    return eventData
    
End Function


Function FreeSpaceOnDrive() As Object

    filesToPublish$ = ReadAsciiFile("filesToPublish.xml")
    if filesToPublish$ = "" then stop

' files that need to be copied by BrightAuthor
    filesToCopy = CreateObject("roAssociativeArray")
    
' files that can be deleted to make room for more content    
    deletionCandidates = CreateObject("roAssociativeArray")
    
' create list of files already on the card
    listOfPoolFiles = MatchFiles("/pool", "*")
    for each file in listOfPoolFiles
        deletionCandidates.AddReplace(file, file)
    next
        
' create the list of files that need to be copied. this is the list of files in filesToPublish that are not in listOfPoolFiles
    filesToPublish = CreateObject("roXMLElement")
    filesToPublish.Parse(filesToPublish$)

' determine total space required
    totalSpaceRequired! = 0    
    for each fileXML in filesToPublish.file
        fullFileName$ = fileXML.fullFileName.GetText()
        o = deletionCandidates.Lookup(fullFileName$)
        if type(o) <> "roString" then
        
            fileItem = CreateObject("roAssociativeArray")
            fileItem.fileName$ = fileXML.fileName.GetText()
            fileItem.filePath$ = fileXML.filePath.GetText()
            fileItem.hashValue$ = fileXML.hashValue.GetText()
            fileItem.fileSize$ = fileXML.fileSize.GetText()

            filesToCopy.AddReplace(fullFileName$, fileItem)

            fileSize% = val(fileItem.fileSize$)
            totalSpaceRequired! = totalSpaceRequired! + fileSize%
            
        endif
    next
    filesToPublish = invalid

' determine if additional space is required
	du = CreateObject("roStorageInfo", "./")
    freeInMegabytes! = du.GetFreeInMegabytes()
    totalFreeSpace! = freeInMegabytes! * 1048576
    
print "totalFreeSpace = "; totalFreeSpace!;", totalSpaceRequired = ";totalSpaceRequired!
        
    if totalFreeSpace! < totalSpaceRequired! then
    
' parse local-sync.xml - remove its files from deletionCandidates
        localSync$ = ReadAsciiFile("local-sync.xml")
        if localSync$ <> "" then
            localSync = CreateObject("roXMLElement")
            localSync.Parse(localSync$)

            for each fileXML in localSync.files.download
                hashValue$ = fileXML.hash.GetText()
                hashMethod$ = fileXML.hash@method
                fileName$ = hashMethod$ + "-" + hashValue$
                fileExisted = deletionCandidates.Delete(fileName$)
            next
        endif

' parse filesToPublish.xml - remove its files from deletionCandidates
        
        filesToPublish = CreateObject("roXMLElement")
        filesToPublish.Parse(filesToPublish$)
        
        for each fileXML in filesToPublish.file
            fullFileName$ = fileXML.fullFileName.GetText()
            fileExisted = deletionCandidates.Delete(fullFileName$)
        next

' delete files from deletionCandidates until totalFreeSpace! > totalSpaceRequired!

        for each fileToDelete in deletionCandidates
        
            pathOnCard$ = "pool/" + fileToDelete
            deletionCandidates.Delete(fileToDelete)
            DeleteFile(pathOnCard$)
            
            du = invalid
            du = CreateObject("roStorageInfo", "./")
            freeInMegabytes! = du.GetFreeInMegabytes()
            totalFreeSpace! = freeInMegabytes! * 1048576
            
print "Delete file ";pathOnCard$        
print "totalFreeSpace = "; totalFreeSpace!;", totalSpaceRequired = ";totalSpaceRequired!

            if totalFreeSpace! > totalSpaceRequired! then
                return filesToCopy
            endif

        next
        
        return "fail"
            
    endif
    
    return filesToCopy
    
End Function


Function PrepareForTransfer(userData as Object, e as Object, eventData As Object)

    mVar = userData.mVar
    
    print "respond to PrepareForTransfer request"

    MoveFile(e.GetRequestBodyFile(), "filesToPublish.xml")
    
    filesToCopy = mVar.FreeSpaceOnDrive()
    if type(filesToCopy) = "roAssociativeArray" then

        root = CreateObject("roXMLElement")
        
        root.SetName("filesToCopy")

        for each key in filesToCopy
        
            fileItem = filesToCopy[key]
            
            item = root.AddBodyElement()
            item.SetName("file")

            elem = item.AddElement("fileName")
            elem.SetBody(fileItem.fileName$)
        
            elem = item.AddElement("filePath")
            elem.SetBody(fileItem.filePath$)
        
            elem = item.AddElement("hashValue")
            elem.SetBody(fileItem.hashValue$)
        
            elem = item.AddElement("fileSize")
            elem.SetBody(fileItem.fileSize$)
        
        next

        xml = root.GenXML({ header: true })
            
    else
        stop
    endif
        
    e.SetResponseBodyString(xml)
    e.SendResponse(200)

    return eventData
    
End Function


Function SyncSpecPosted(userData as Object, e as Object, eventData As Object)

    mVar = userData.mVar
    
    print "respond to SyncSpecPosted request"

'    MoveFile(e.GetRequestBodyFile(), "tmp:new-sync.xml")
    MoveFile(e.GetRequestBodyFile(), "new-sync.xml")
    e.SetResponseBodyString("RECEIVED")
    e.SendResponse(200)
    
    oldSync = CreateObject("roSyncSpec")
    ok = oldSync.ReadFromFile("local-sync.xml")
    if not ok then stop
    
	newSync = CreateObject("roSyncSpec")
	ok = newSync.ReadFromFile("new-sync.xml")
    if not ok then stop

    mVar.diagnostics.PrintTimestamp()
    mVar.diagnostics.PrintDebug("### LWS DOWNLOAD COMPLETE")
            
    mVar.syncPoolFiles = CreateObject("roSyncPoolFiles", "pool", newSync)
    if type(mVar.syncPoolFiles) <> "roSyncPoolFiles" then stop
            
    autorunFile$ = GetPoolFilePath(mVar.syncPoolFiles, "autorun.brs")
    if autorunFile$ = "" then stop

    autoscheduleFile$ = GetPoolFilePath(mVar.syncPoolFiles, "autoschedule.xml")
    if autoscheduleFile$ = "" then stop
    
    resourcesFile$ = GetPoolFilePath(mVar.syncPoolFiles, "resources.txt")

	if not newSync.WriteToFile("local-sync.xml") then stop

    success = CopyFile(autorunFile$, "autorun.brs")
    if not success then stop
            
    ' see if the autorun file has changed
    autorunFileChanged = false
    currentSyncPoolFiles = CreateObject("roSyncPoolFiles", "pool", oldSync)
    if type(currentSyncPoolFiles) <> "roSyncPoolFiles" then stop
    oldAutorunFile$ = currentSyncPoolFiles.GetPoolFilePath("autorun.brs")
    if autorunFile$ <> oldAutorunFile$ then autorunFileChanged = true

print "### autorunFile$ : ";autorunFile$
print "### oldAutorunFile$ : ";oldAutorunFile$

    success = CopyFile(autoscheduleFile$, "autoschedule.xml")
    if not success then stop

    if resourcesFile$ <> "" then
        success = CopyFile(resourcesFile$, "resources.txt")
        if not success then stop
    endif

    updateFile$ = GetPoolFilePath(mVar.syncPoolFiles, "update.rok")
    if updateFile$ <> "" then
        success = MoveFile(updateFile$, "update.rok")
        if not success then stop
        mVar.diagnostics.PrintDebug("### FW update found - reboot")
        RebootSystem()
    endif

    if autorunFileChanged then
        mVar.diagnostics.PrintDebug("### new autorun.brs found - reboot")
        RebootSystem()
    endif
            
	DeleteFile("new-sync.xml")
	newSync = invalid

    eventData.code = -2
    return eventData
    
End Function


Function GetStorageInfo(userData as Object, e as Object, eventData As Object)

    mVar = userData.mVar
    
    print "respond to GetStorageInfo request"
  
	du = CreateObject("roStorageInfo", "./")
	
	if type(du) = "roStorageInfo" then
	
        root = CreateObject("roXMLElement")
        root.SetName("BrightSignStorageInfo")

        freeInMegabytes = du.GetFreeInMegabytes()
        sizeInMegabytes = du.GetSizeInMegabytes()
	    usedInMegabytes = sizeInMegabytes - freeInMegabytes
        fileSystemType = du.GetFileSystemType()

        elem = root.AddElement("freeInMegabytes")
        elem.SetBody(StripLeadingSpaces(stri(freeInMegabytes)))
        
        elem = root.AddElement("usedInMegabytes")
        elem.SetBody(StripLeadingSpaces(stri(usedInMegabytes)))
        
        elem = root.AddElement("sizeInMegabytes")
        elem.SetBody(StripLeadingSpaces(stri(sizeInMegabytes)))
        
        elem = root.AddElement("fileSystemType")
        elem.SetBody(fileSystemType)
        
        xml = root.GenXML({ header: true })

        e.AddResponseHeader("Content-type", "text/xml")
        e.SetResponseBodyString(xml)
        e.SendResponse(200)
    
    else
    
        e.SendResponse(500)
        
    endif    
  
    return eventData
    
End Function


Function GetCurrentStatus(userData as Object, e as Object, eventData As Object)
    
    mVar = userData.mVar
    
    print "respond to GetCurrentStatus request"
  
    root = CreateObject("roXMLElement")
    root.SetName("BrightSignStatus")

    autorunVersion$ = mVar.sysInfo.autorunVersion$

    registrySection = CreateObject("roRegistrySection", "networking")
    if type(registrySection)<>"roRegistrySection" then print "Error: Unable to create roRegistrySection":stop
    unitName$ = registrySection.Read("un")
    unitNamingMethod$ = registrySection.Read("unm")
    unitDescription$ = registrySection.Read("ud")
    registrySection = invalid
    
    elem = root.AddElement("unitName")
    elem.AddAttribute("label", "Unit Name")
    elem.SetBody(unitName$)

    elem = root.AddElement("unitNamingMethod")
    elem.AddAttribute("label", "Unit Naming Method")
    elem.SetBody(unitNamingMethod$)

    elem = root.AddElement("unitDescription")
    elem.AddAttribute("label", "Unit Description")
    elem.SetBody(unitDescription$)

    modelObject = CreateObject("roDeviceInfo")
    
    elem = root.AddElement("model")
    elem.AddAttribute("label", "Model")
    elem.SetBody(modelObject.GetModel())

    elem = root.AddElement("firmware")
    elem.AddAttribute("label", "Firmware")
    elem.SetBody(modelObject.GetVersion())

    elem = root.AddElement("autorun")
    elem.AddAttribute("label", "Autorun")
    elem.SetBody(mVar.sysInfo.autorunVersion$)

    elem = root.AddElement("serialNumber")
    elem.AddAttribute("label", "Serial Number")
    elem.SetBody(modelObject.GetDeviceUniqueId())

    elem = root.AddElement("functionality")
    elem.AddAttribute("label", "Functionality")
    elem.SetBody(mVar.lwsConfig$)
    
' 86400 seconds per day
    deviceUptime% = modelObject.GetDeviceUptime()
    numDays% = deviceUptime% / 86400
    numHours% = (deviceUptime% - (numDays% * 86400)) / 3600
    numMinutes% = (deviceUptime% - (numDays% * 86400) - (numHours% * 3600)) / 60
    numSeconds% = deviceUptime% - (numDays% * 86400) - (numHours% * 3600) - (numMinutes% * 60)
    deviceUptime$ = ""
    if numDays% > 0 then deviceUptime$ = stri(numDays%) + " days "
    if numHours% > 0 then deviceUptime$ = deviceUptime$ + stri(numHours%) + " hours "
    if numMinutes% > 0 then deviceUptime$ = deviceUptime$ + stri(numMinutes%) + " minutes "
    if numSeconds% > 0 then deviceUptime$ = deviceUptime$ + stri(numSeconds%) + " seconds"
            
    elem = root.AddElement("deviceUptime")
    elem.AddAttribute("label", "Device Uptime")
    elem.SetBody(deviceUptime$)
    
    elem = root.AddElement("activePresentation")
    elem.AddAttribute("label", "Active Presentation")
    elem.SetBody(mVar.activePresentation$)
    
'    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })
    xml = root.GenXML({ header: true })

    e.AddResponseHeader("Content-type", "text/xml")
    e.SetResponseBodyString(xml)
    e.SendResponse(200)
  
    return eventData

End Function


Function GetPoolFilePath(syncPoolFiles As Object, fileName$ As String) As String

    if type(syncPoolFiles) = "roSyncPoolFiles" then
        return syncPoolFiles.GetPoolFilePath(fileName$)
    else
        return fileName$
    endif

End Function


Sub CreateBSPObjects(existingSign As Object)

    EnableZoneSupport(true)

    if m.debuggingSynchro then m.CreateUDPSender()

    ' create other objects as needed
    for each zone in m.sign.zones
    
        zone.Initialize(existingSign, m.msgPort)

        playlist = zone.playlist
        if type(playlist) = "roAssociativeArray" then
        
            for each key in playlist.stateTable
        
                state = playlist.stateTable[key]

                gpioEvents = state.gpioEvents
                for each gpioEventNumber in gpioEvents
                    if type(gpioEvents[gpioEventNumber]) = "roAssociativeArray" then
                        m.CreateObjectsNeededForTransitionCommands(gpioEvents[gpioEventNumber])
                    endif
                next
                
                bp900Events = state.bp900Events
                for each bp900EventNumber in bp900Events
                    if type(bp900Events[bp900EventNumber]) = "roAssociativeArray" then
                        m.CreateObjectsNeededForTransitionCommands(bp900Events[bp900EventNumber])
                    endif
                next
                
                if type(state.mstimeoutEvent) = "roAssociativeArray"
                    m.CreateObjectsNeededForTransitionCommands(state.mstimeoutEvent)
                endif
                        
                if type(state.videoEndEvent) = "roAssociativeArray"
                    m.CreateObjectsNeededForTransitionCommands(state.videoEndEvent)
                endif
                        
                if type(state.signChannelEndEvent) = "roAssociativeArray"
                    m.CreateObjectsNeededForTransitionCommands(state.signChannelEndEvent)
                endif
                        
                if type(state.audioEndEvent) = "roAssociativeArray"
                    m.CreateObjectsNeededForTransitionCommands(state.audioEndEvent)
                endif
                        
                if type(state.keyboardEvents) = "roAssociativeArray" or type(state.usbStringEvents) = "roAssociativeArray" then
                    
                    if type(m.keyboard) <> "roKeyboard" then
                        m.keyboard = CreateObject("roKeyboard")
                        m.keyboard.SetPort(m.msgPort)
                    endif
                    
                    if type(state.keyboardEvents) = "roAssociativeArray" then
                        keyboardEvents = state.keyboardEvents
                        for each keyboardEvent in state.keyboardEvents
                            if type(keyboardEvents[keyboardEvent]) = "roAssociativeArray" then
                                m.CreateObjectsNeededForTransitionCommands(keyboardEvents[keyboardEvent])
                            endif
                        next
                    endif                    

                    if type(state.usbStringEvents) = "roAssociativeArray"
                        usbEvents = state.usbStringEvents
                        for each usbEvent in state.usbEvents
                            if type(usbEvents[usbEvent]) = "roAssociativeArray" then
                                m.CreateObjectsNeededForTransitionCommands(usbEvents[usbEvent])
                            endif
                        next                        
                    endif
                    
                endif
                
                if type(state.remoteEvents) = "roAssociativeArray" then
                
                    if type(m.remote) <> "roIRRemote" then
                        m.remote = CreateObject("roIRRemote")
                        m.remote.SetPort(m.msgPort)
                    endif
                
                    remoteEvents = state.remoteEvents
                    for each remoteEvent in state.remoteEvents
                        m.CreateObjectsNeededForTransitionCommands(remoteEvents[remoteEvent])                      
                    next
                
                endif
                
                serialEvents = state.serialEvents
                for each serialPort in serialEvents

                    m.CreateSerial(serialPort)

                    for each serialEvent in serialEvents[serialPort]
                        m.CreateObjectsNeededForTransitionCommands(serialEvents[serialPort][serialEvent])
                    next
                    
                next
                                                 
                if type(state.udpEvents) = "roAssociativeArray" or type(state.synchronizeEvents) = "roAssociativeArray" then
                    
                    if type(m.udpReceiver) <> "roDatagramReceiver" then
                        m.udpReceiver = CreateObject("roDatagramReceiver", m.udpReceivePort)
                        m.udpReceiver.SetPort(m.msgPort)
                    endif
                    
                    if type(state.udpEvents) = "roAssociativeArray" then
                        udpEvents = state.udpEvents
                        for each udpEvent in state.udpEvents
                            m.CreateObjectsNeededForTransitionCommands(udpEvents[udpEvent])                      
                        next
                    endif
                    
                    if type(state.synchronizeEvents) = "roAssociativeArray" then
                        synchronizeEvents = state.synchronizeEvents
                        for each synchronizeEvent in state.synchronizeEvents
                            m.CreateObjectsNeededForTransitionCommands(synchronizeEvents[synchronizeEvent])                      
                        next
                    endif
                    
                endif
                
                if type(state.touchEvents) = "roAssociativeArray" then
                    m.InitializeTouchScreen(zone)
                                
                    for each eventNum in state.touchEvents
                        m.AddRectangularTouchRegion(zone, state.touchEvents[eventNum], val(eventNum))
                        m.CreateObjectsNeededForTransitionCommands(state.touchEvents[eventNum])                      
                    next
                endif
                
                if type(state.videoTimeCodeEvents) = "roAssociativeArray" then
                    for each eventNum in state.videoTimeCodeEvents
                        m.CreateObjectsNeededForTransitionCommands(state.videoTimeCodeEvents[eventNum])                      
                    next
                endif
                                
                if type(state.cmds) = "roArray" then
                    for each cmd in state.cmds
                        commandName$ = cmd.name$
                        if commandName$ = "sendUDPCommand" or commandName$ = "synchronize" then
                            m.CreateUDPSender()
                        else if commandName$ = "sendSerialStringCommand" or commandName$ = "sendSerialBlockCommand" or commandName$ = "sendSerialByteCommand" or commandName$ = "sendSerialBytesCommand" then
                            m.CreateSerial(cmd.parameters.port)
                        else if commandName$ = "sendBP900Output" then
                            m.CreateBP900Output()
                        endif
                    next
                endif
                       
            next
            
        endif
        
    next

End Sub


Sub CreateObjectsNeededForTransitionCommands(transition As Object)

    if type(transition.transitionCmds) = "roArray" then
        for each cmd in transition.transitionCmds
            commandName$ = cmd.name$
            if commandName$ = "sendUDPCommand" or commandName$ = "synchronize" then
                m.CreateUDPSender()
            else if commandName$ = "sendSerialStringCommand" or commandName$ = "sendSerialBlockCommand" or commandName$ = "sendSerialByteCommand" or commandName$ = "sendSerialBytesCommand" then
                m.CreateSerial(cmd.parameters.port)
            else if commandName$ = "sendBP900Output" then
                m.CreateBP900Output()
            endif
        next
    endif

End Sub


Function newTimerHandler(zone As Object) As Object

    timerHandler = CreateObject("roAssociativeArray")

    timerHandler.zone = zone

    return timerHandler

End Function


Sub UpdateRSSStrings(zone as Object, playlistItem as Object)
    rssStringCount = zone.widget.GetStringCount()
    zone.widget.PopStrings(rssStringCount)

    article = playlistItem.rssParser.GetNextArticle()
    while type(article) = "roRssArticle"
        zone.widget.PushString(article.GetDescription())
        article = playlistItem.rssParser.GetNextArticle()
    endwhile

End Sub


Sub LaunchBSPPlayback(zone as Object)

    if m.debuggingSynchro then m.udpSender.Send("LaunchBSPPlayback")

    playlist = zone.activePlaylist

    if zone.type$ = "Clock" or zone.type$ = "Ticker" then
    
        if type(zone.foregroundTextColor%) = "roInt" then
            zone.widget.SetForegroundColor(zone.foregroundTextColor%)
        endif
        
        if type(zone.backgroundTextColor%) = "roInt" then
            zone.widget.SetBackgroundColor(zone.backgroundTextColor%)
        endif
        
        if zone.font$ <> "" and zone.font$ <> "System" then
            fontPath$ = GetPoolFilePath(m.syncPoolFiles, zone.font$)
            zone.widget.SetFont(fontPath$)
        endif
                
        if type(zone.backgroundBitmapFile$) = "roString" then
            backgroundBitmapFilePath$ = GetPoolFilePath(m.syncPoolFiles, zone.backgroundBitmapFile$)
            zone.widget.SetBackgroundBitmap(backgroundBitmapFilePath$, zone.stretch%)
        endif
                
        if type(zone.safeTextRegionX%) = "roInt" then
            r = CreateObject("roRectangle", zone.safeTextRegionX%, zone.safeTextRegionY%, zone.safeTextRegionWidth%, zone.safeTextRegionHeight%)
            zone.widget.SetSafeTextRegion(r)
            r = 0
        endif

        if zone.type$ = "Ticker" then
        
            zone.widget.Clear()

            stateTable = playlist.stateTable
            
            for each stateName in stateTable
            
                state = stateTable[stateName]
            
                if type(state.rssItem) = "roAssociativeArray" then
                
                    UpdateRSSStrings(zone, state.rssItem)
                
                else if type(state.textItem) = "roAssociativeArray" then
                            
                    for each textString in state.textItem.textStrings
                        zone.widget.PushString(textString)
                    next
                    
                endif
                
            next
            
        endif

        zone.widget.Show()
        
        return
        
    endif
    
    state = zone.activeState
    
    if type(state) = "roAssociativeArray" then
    
        state.usbInputBuffer$ = ""

        m.ExecuteMediaStateCommands(zone, state)
        
        m.LaunchMedia(zone, state)
    
    endif
    
End Sub


Sub AddToEventData(eventData As Object, nextState$ As String, zone As Object)

    if nextState$ <> "" then
        eventData.code = 0
        eventData.zones.push(zone)
        eventData.nextStates.push(nextState$)
    endif
    
End Sub


Sub AddToTransitionList(transitionList As Object, zone As Object, transition As Object)

    eventTransition = CreateObject("roAssociativeArray")
    eventTransition.zone = zone
    eventTransition.transition = transition
    transitionList.push(eventTransition)

End Sub


Sub EventLoop()

    ' debugStr$ = "enter EventLoop" : m.diagnostics.PrintDebug(debugStr$)

    eventData = CreateObject("roAssociativeArray")
    eventData.code = 0
    eventData.zones = CreateObject("roArray", 1, true)
    eventData.nextStates = CreateObject("roArray", 1, true)

    transitionList = CreateObject("roArray", 1, true)

    while true
        
        msg = wait(0, m.msgPort)

        m.diagnostics.PrintTimestamp()
        m.diagnostics.PrintDebug("msg received - type=" + type(msg))

        if type(msg) = "roHttpEvent" then
        
            userdata = msg.GetUserData()
            eventData = userData.HandleEvent(userData, msg, eventData)
                                     
        else if type(msg) = "roAssociativeArray" then
        
            eventData = m.HandleInternalMessageEvent(msg, eventData, transitionList)
            
        else if type(msg) = "roVideoEvent" Then

            if msg.GetInt() = 8 then ' video end 
                eventData = m.HandleVideoEndEvent(msg, eventData, transitionList)
            else if msg.GetInt() = 3 then 'video start
                ' dispatch to feedPlayer object if appropriate
                if type(m.feedPlayer) = "roAssociativeArray" and m.feedPlayer.IsEnabled and type(m.feedPlayer.videoPlayer) = "roVideoPlayer" then
                        m.feedPlayer.HandleVideoEvent(msg)
                endif
            else if msg.GetInt() = 12 then
                eventData = m.HandleVideoTimeCodeEvent(msg, eventData, transitionList)
            endif
            
        elseif type(msg) = "roAudioEvent" Then
        
            eventData = m.HandleAudioEndEvent(msg, eventData, transitionList)

        elseif type(msg) = "roTimerEvent" Then
            
            eventData = m.HandleTimerEvent(msg, eventData, transitionList)
            
        elseif type(msg) = "roGpioButton" then

            eventData = m.HandleGPIOEvent(msg, eventData, transitionList)
            
        elseif type(msg) = "roControlDown" then

            eventData = m.HandleBP900Event(msg, eventData, transitionList)
            
        elseif type(msg) = "roTouchEvent" then
        
            eventData = m.HandleTouchEvent(msg, eventData, transitionList)
            
        elseif (type(msg) = "roUrlEvent") then
            
            eventData = m.HandleURLEvent(msg, eventData, transitionList)
            
	    elseif (type(msg) = "roSyncPoolEvent") then
		    
		    eventData = m.HandleSyncPoolEvent(msg, eventData, transitionList)

	    elseif (type(msg) = "roSyncPoolProgressEvent") then
	    
	        eventData = m.HandleSyncPoolProgressEvent(msg, eventData, transitionList)
	
        elseif type(msg) = "roStreamByteEvent" then
        
            eventData = m.HandleStreamLineEvent(msg, eventData, transitionList)
			
        elseif type(msg) = "roStreamLineEvent" then
        
            eventData = m.HandleStreamLineEvent(msg, eventData, transitionList)
            
        elseif type(msg) = "roIRRemotePress" then
        
            eventData = m.HandleIRRemoteEvent(msg, eventData, transitionList)
                        
        elseif type(msg) = "roKeyboardPress" then
        
            eventData = m.HandleKeyboardEvent(msg, eventData, transitionList)
            
        elseif type(msg) = "roDatagramEvent" then
        
            eventData = m.HandleDatagramEvent(msg, eventData, transitionList)
            
        else
        
            m.diagnostics.PrintDebug("Unhandled Event type: " + type(msg))
            
        endif

' execute transition commands
        for each eventTransition in transitionList
        
            zone = eventTransition.zone
            transition = eventTransition.transition
                        
            ' execute any specified commands for this zone / transition
            if type(transition) = "roAssociativeArray" then

                transitionCmds = transition.transitionCmds
                if type(transitionCmds) = "roArray" then
                
                    for each transitionCmd in transitionCmds
                    
                        command$ = transitionCmd.name$
                        
                        if command$ = "synchronize" then
                        
                            ' if the next command is synchronize, it is necessary to get the file to preload
                            nextState$ = m.GetNextStateName(zone, transition)
                            ' nextState$ = transition.targetMediaState$
                            zone.preloadState = zone.activePlaylist.stateTable[nextState$]
                                                   
                        else if command$ = "internalSynchronize" then
                        
                            nextState$ = transition.internalSyncTargetMediaState$
                            zone.preloadState = zone.activePlaylist.stateTable[nextState$]

                        endif
                    
                        m.ExecuteCmd(zone, transitionCmd.name$, transitionCmd.parameters)
                        
                    next
                    
                endif
                                
            endif
        next
        
        transitionList.Clear()
        
' check for impacted zones (zones waiting for new media items)
        
        if eventData.code = 3 or eventData.code = -1 or eventData.code = -2 then ' presentation timeout or new content from network, start over

            currentSync = CreateObject("roSyncSpec")
            if currentSync.ReadFromFile("current-sync.xml") then
                m.syncPoolFiles = CreateObject("roSyncPoolFiles", "pool", currentSync)
            else if currentSync.ReadFromFile("local-sync.xml") then
                m.syncPoolFiles = CreateObject("roSyncPoolFiles", "pool", currentSync)
            else
                stop
            endif
            
            m.diagnostics.PrintTimestamp()
            m.diagnostics.PrintDebug("### read xml files")

            m.Restart()

            m.StartPlayback()
    
            m.diagnostics.PrintTimestamp()
            m.diagnostics.PrintDebug("### prepare to wait for next event")
        
        else ' continue after normal event
        
            index% = 0

            for each zone in eventData.zones
            
                zone.previousStateName$ = zone.activeStateName$
                nextState$ = eventData.nextStates[index%]
                zone.activeStateName$ = nextState$
                zone.activeState = zone.activePlaylist.stateTable[nextState$]

                index% = index% + 1

            next
            
            for each zone in eventData.zones
                m.LaunchBSPPlayback(zone)
            next

            for each zone in eventData.zones
                m.diagnostics.PrintTimestamp()
                m.diagnostics.PrintDebug("### processed normal event - call PreloadImage")
                m.PreloadImage(zone)
                m.diagnostics.PrintDebug("### return from PreloadImage")
            next
        
        endif
        
' reset member variables and wait for next event

        eventData = invalid
        
        eventData = CreateObject("roAssociativeArray")
        eventData.code = 0
        eventData.zones = CreateObject("roArray", 1, true)
        eventData.nextStates = CreateObject("roArray", 1, true)
        
    end while
    
End Sub


Function GetNextStateName(zone As Object, transition As Object) As String

    if transition.targetMediaStateIsPreviousState then
        nextState$ = zone.previousStateName$
    else
        nextState$ = transition.targetMediaState$
    endif

    return nextState$
    
End Function


Function HandleInternalMessageEvent(msg As Object, eventData As Object, transitionList As Object)

    if type(msg["EventType"]) = "roString" then

        if type(m.feedPlayer) = "roAssociativeArray" then

		    if msg["EventType"] = "EndCycleEvent" or msg["EventType"] = "LoadEvent" then
                m.feedPlayer.HandleScriptEvent(msg)
                return eventData
            endif
            
        endif
        
        if msg["EventType"] = "VideoPlaybackFailureEvent" then
        
            nextState$ = msg["NextState"]
            zone = msg["Zone"]
            
            if nextState$ <> "" then
                AddToEventData(eventData, nextState$, zone)
                AddToTransitionList(transitionList, zone, zone.activeState.videoEndEvent)
            endif
            
        else if msg["EventType"] = "SignChannelEndEvent" then
        
            for each zone in m.sign.zones

                if type(zone.activeState) = "roAssociativeArray" then                
                    if type(zone.activeState.signChannelEndEvent) = "roAssociativeArray" then

                        nextState$ = m.GetNextStateName(zone, zone.activeState.signChannelEndEvent)

                        AddToEventData(eventData, nextState$, zone)
                        AddToTransitionList(transitionList, zone, zone.activeState.signChannelEndEvent)
                    
                        zone.lastEvent$ = "SignChannelEndEvent"
                        
                    endif
                endif
                
            next
        
        else if msg["EventType"] = "InternalSyncPreloadEvent" then

            internalSyncParameter$ = msg["EventParameter"]

            m.diagnostics.PrintDebug("InternalSyncPreloadEvent " + internalSyncParameter$)

            for each zone in m.sign.zones

                if type(zone.activeState) = "roAssociativeArray" then                                

                    if type(zone.activeState.internalSynchronizeEvents) = "roAssociativeArray" then
                        if type(zone.activeState.internalSynchronizeEvents[internalSyncParameter$]) = "roAssociativeArray" then
                        
                            ' get the next file and preload it
                            next$ = zone.activeState.internalSynchronizeEvents[internalSyncParameter$].targetMediaState$
                            nextState = zone.activePlaylist.stateTable[next$]

                            preloadRequired = true
                            if type(zone.preloadState) = "roAssociativeArray" then
                                if zone.preloadedStateName$ = nextState.name$ then
                                    preloadRequired = false
                                endif
                            endif                                    
                                                                
                            ' set this variable so that launchVideo knows what has been preloaded
                            zone.preloadState = nextState
                            
                            if preloadRequired then
                                m.PreloadItem(zone, zone.preloadState)
    	                    endif
    	                    
                            AddToTransitionList(transitionList, zone, zone.activeState.internalSynchronizeEvents[internalSyncParameter$])
    	                    
                        endif
                    endif
                endif
                
            next
        
        else if msg["EventType"] = "InternalSyncPlayEvent" then

            internalSyncParameter$ = msg["EventParameter"]

            m.diagnostics.PrintDebug("InternalSyncPlayEvent " + internalSyncParameter$)

            for each zone in m.sign.zones

                if type(zone.activeState) = "roAssociativeArray" then                                

                    if type(zone.activeState.internalSynchronizeEventsMaster) = "roAssociativeArray" then

                        if type(zone.activeState.internalSynchronizeEventsMaster[internalSyncParameter$]) = "roAssociativeArray" then

                            nextState$ = m.GetNextStateName(zone, zone.activeState.internalSynchronizeEventsMaster[internalSyncParameter$])
                            
                            m.diagnostics.PrintDebug("master play event received - prepare to return")
                            
                            AddToEventData(eventData, nextState$, zone)
                                                            
                        endif
                        
                    else if type(zone.activeState.internalSynchronizeEvents) = "roAssociativeArray" then

                        if type(zone.activeState.internalSynchronizeEvents[internalSyncParameter$]) = "roAssociativeArray" then

                            nextState$ = m.GetNextStateName(zone, zone.activeState.internalSynchronizeEvents[internalSyncParameter$])
                            
                            m.diagnostics.PrintDebug("slave play event received - prepare to return")
                            
                            AddToEventData(eventData, nextState$, zone)
                        
                            zone.lastEvent$ = "InternalSyncEvent " + internalSyncParameter$

                            m.LogEvent("internalSyncEvent", internalSyncParameter$, zone)
                                
                        endif
                        
                    endif
                    
                endif
            next
            
        else if msg["EventType"] = "InternalSyncMasterPreloadEvent" then
        	                
            internalSyncParameter$ = msg["EventParameter"]

            m.diagnostics.PrintDebug("InternalSyncMasterPreloadEvent " + internalSyncParameter$)

            for each zone in m.sign.zones

                if type(zone.activeState) = "roAssociativeArray" then                                

                    if type(zone.activeState.internalSynchronizeEventsMaster) = "roAssociativeArray" then

                        if type(zone.activeState.internalSynchronizeEventsMaster[internalSyncParameter$]) = "roAssociativeArray" then

                            m.diagnostics.PrintDebug("post play message with parameter " + internalSyncParameter$)

                            internalSyncPlay = CreateObject("roAssociativeArray")
                            internalSyncPlay["EventType"] = "InternalSyncPlayEvent"
                            internalSyncPlay["EventParameter"] = internalSyncParameter$
                            m.msgPort.PostMessage(internalSyncPlay)
                            
                        endif
                    endif
                endif
                
            next
            
        endif
        
    endif

    return eventData
    
End Function


Function HandleVideoEndEvent(msg As Object, eventData As Object, transitionList As Object)

    MEDIA_START = 3
    MEDIA_END = 8
        
    m.diagnostics.PrintDebug("Video Event" + stri(msg.GetInt()))
    
    ' dispatch to feedPlayer object if appropriate
    if type(m.feedPlayer) = "roAssociativeArray" then

        if m.feedPlayer.IsEnabled then
        
			if type(m.feedPlayer.videoPlayer) = "roVideoPlayer" then
			
				m.feedPlayer.HandleVideoEvent(msg)
            
				return eventData

			endif
           			
		endif
        
    endif
    
    if msg.GetInt() = MEDIA_END Then
    
        m.diagnostics.PrintDebug("VideoFinished")
        
        zone = m.sign.videoZone
        
        if type(zone) = "roAssociativeArray" then

            if type(zone.activeState) = "roAssociativeArray" then
            
                if type(zone.activeState.videoEndEvent) = "roAssociativeArray" then
                
                    nextState$ = m.GetNextStateName(zone, zone.activeState.videoEndEvent)

                    AddToEventData(eventData, nextState$, zone)
                    AddToTransitionList(transitionList, zone, zone.activeState.videoEndEvent)
                
                    zone.lastEvent$ = "VideoEndEvent"

                    m.LogEvent("videoEndEvent", "", zone)

                endif
                
            endif
        
        endif
                        
    elseif msg.GetInt() = MEDIA_START or msg.GetInt() = 2 then
    
        if type(m.sign.videoZone.imagePlayer) = "roImageWidget" then
            m.sign.videoZone.imagePlayer.StopDisplay()
        endif
        
    endif

    return eventData
    
End Function


Function HandleVideoTimeCodeEvent(msg As Object, eventData As Object, transitionList As Object)

    videoTimeCodeIndex$ = str(msg.GetData())

    m.diagnostics.PrintDebug("Video TimeCode Event " + videoTimeCodeIndex$)

    zone = m.sign.videoZone
    
    if type(zone) = "roAssociativeArray" then
        
        if type(zone.activeState) = "roAssociativeArray" then

            state = zone.activeState

            if type(state.videoTimeCodeEvents) = "roAssociativeArray" then
            
                videoTimeCodeEvent = state.videoTimeCodeEvents[videoTimeCodeIndex$]
                
                if type(videoTimeCodeEvent) = "roAssociativeArray" then
                
                    nextState$ = m.GetNextStateName(zone, videoTimeCodeEvent)

                    AddToEventData(eventData, nextState$, zone)
                    AddToTransitionList(transitionList, zone, videoTimeCodeEvent)
                                    
                    zone.lastEvent$ = "VideoTimeCodeEvent"

                    m.LogEvent("videoTimeCodeEvent", videoTimeCodeIndex$, zone)

                endif
                
            endif
        
        endif

    endif
    
    return eventData
                       
End Function


Function HandleAudioEndEvent(msg As Object, eventData As Object, transitionList As Object)

    MEDIA_START = 3
    MEDIA_END = 8
        
    m.diagnostics.PrintDebug("Audio Event" + stri(msg.GetInt()))
    
    if msg.GetInt() = MEDIA_END Then
    
        m.diagnostics.PrintDebug("AudioFinished")
        
        zone = m.sign.videoZone
        
        if type(zone) = "roAssociativeArray" then
        
            if type(zone.activeState) = "roAssociativeArray" then
            
                if type(zone.activeState.audioEndEvent) = "roAssociativeArray" then
                
                    nextState$ = m.GetNextStateName(zone, zone.activeState.audioEndEvent)

                    AddToEventData(eventData, nextState$, zone)
                    AddToTransitionList(transitionList, zone, zone.activeState.audioEndEvent)
                                
                    zone.lastEvent$ = "AudioEndEvent"

                    m.LogEvent("audioEndEvent", "", zone)
                                
                endif
            
            endif
            
        endif
        
    endif
    
    return eventData

End Function


Function HandleTimerEvent(msg As Object, eventData As Object, transitionList As Object)

    nextState$ = ""
    
    timerID$ = stri(msg.GetSourceIdentity())

    ' see if the timer is for SignChannel
    feedPlayerTimeout = false
    if type(m.feedPlayer) = "roAssociativeArray" then
        if msg.GetSourceIdentity() = m.feedPlayer.imageTimer.GetIdentity() or msg.GetSourceIdentity() = m.feedPlayer.imageRetryTimer.GetIdentity() or msg.GetSourceIdentity() = m.feedPlayer.feedTimer.GetIdentity() then
            m.feedPlayer.HandleTimerEvent(msg)
            ' indicate that event was for SignChannel
            feedPlayerTimeout = true
        endif
    endif
    
    if not feedPlayerTimeout then
    
        ' see if the timer is for Logging
        loggingTimeout = false
        if type(m.logging) = "roAssociativeArray" then
            if type(m.logging.cutoverTimer) = "roTimer" then
                if msg.GetSourceIdentity() = m.logging.cutoverTimer.GetIdentity() then
                    ' indicate that event was for logging
                    m.logging.HandleTimerEvent(msg)
                    loggingTimeout = true
                endif
            endif
        endif
    
        if not loggingTimeout then

			eventConsumed = false

            ' see if the timer is for one of the zones in the sign (image timeout)
            if type(m.sign) = "roAssociativeArray" then
                for each zone in m.sign.zones

                    if type(zone.activeState) = "roAssociativeArray" then                
                        state = zone.activeState
                        if type(state.mstimeoutEvent) = "roAssociativeArray" then
                            if type(state.mstimeoutTimer) = "roTimer" then
                                if state.mstimeoutTimer.GetIdentity() = msg.GetSourceIdentity() then
                                
                                    nextState$ = m.GetNextStateName(zone, state.mstimeoutEvent)
                                    
                                    AddToEventData(eventData, nextState$, zone)
                                    AddToTransitionList(transitionList, zone, state.mstimeoutEvent)
                                    
                                    state.mstimeoutTimer.Stop()
                                    state.mstimeoutTimer = 0
                                    
                                    zone.lastEvent$ = "TimeoutEvent"

                                    m.LogEvent("timeout", "", zone)

                                    eventConsumed = true
                                    
                                endif
                            endif
                        endif
                    endif
                next
            endif
            
            if not eventConsumed then
            
                if type(m.presentationTimeoutHandlers[timerID$]) = "roAssociativeArray" then

                    m.diagnostics.PrintDebug("### presentationTimeoutHandler timeout received")

				    eventData.code = 3
				    m.PrepareForRestart()
				    eventConsumed = true
				    ' m.StopPlayback()
				    ' SEAMLESS
				    ' return eventData
    				
                else if m.ttkChargerPresent then
					if type(m.ttk.timer) = "roTimer" then
						if m.ttk.timer.GetIdentity() = msg.GetSourceIdentity() then
		                    m.diagnostics.PrintDebug("### ttkCharger timeout received")
				            newTimeout = m.systemTime.GetLocalDateTime()
							newTimeout.AddSeconds(2)
							m.ttk.timer.SetDateTime(newTimeout)
							m.ttk.timer.Start()
							eventConsumed = true
							
							if m.ttk.lastCommandWasVoltage then
								command% = 105
								m.ttk.lastCommandWasVoltage = false
							else
								command% = 118
								m.ttk.lastCommandWasVoltage = true
							endif
							
							m.ttk.serial.SendByte(command%)
						endif
					endif
				endif
				
                if not eventConsumed then
            
                    returnValue = m.HandleNetworkTimerEvent(msg)
    '                        if returnValue = -1 then
    '                            m.PrepareForRestart()
    '                            eventData.code = -1
    '                            return eventData
    '                        endif
                
                endif
                
            endif
            
        endif
    
    endif
            
    return eventData
    
End Function


Function HandleGPIOEvent(msg As Object, eventData As Object, transitionList As Object)

    m.diagnostics.PrintDebug("Button Press" + str(msg.GetInt()))
    
    if msg.GetInt()=12 then
        stop
    endif

    for each zone in m.sign.zones
        
        if type(zone.activeState) = "roAssociativeArray" then
        
            gpioNum$ = StripLeadingSpaces(str(msg.GetInt()))
        
            gpioEvents = zone.activeState.gpioEvents

            if type(gpioEvents[gpioNum$]) = "roAssociativeArray" then
            
                nextState$ = m.GetNextStateName(zone, gpioEvents[gpioNum$])
                AddToEventData(eventData, nextState$, zone)
                AddToTransitionList(transitionList, zone, gpioEvents[gpioNum$])
                                    
                zone.lastEvent$ = "GPIOInputEvent" + gpioNum$

                m.LogEvent("gpioInputEvent", gpioNum$, zone)

            endif                

        endif
    next
            
    return eventData
    
End Function


Function HandleBP900Event(msg As Object, eventData As Object, transitionList As Object)

    m.diagnostics.PrintDebug("BP900 Press" + str(msg.GetInt()))
    
    for each zone in m.sign.zones
        
        if type(zone.activeState) = "roAssociativeArray" then
        
            bp900Num$ = StripLeadingSpaces(str(msg.GetInt()))
        
            bp900Events = zone.activeState.bp900Events

            if type(bp900Events[bp900Num$]) = "roAssociativeArray" then
            
                nextState$ = m.GetNextStateName(zone, bp900Events[bp900Num$])
                AddToEventData(eventData, nextState$, zone)
                AddToTransitionList(transitionList, zone, bp900Events[bp900Num$])
                                    
                zone.lastEvent$ = "BP900InputEvent" + bp900Num$

                m.LogEvent("bp900InputEvent", bp900Num$, zone)

            endif                

        endif
    next
            
    return eventData
    
End Function


Function HandleTouchEvent(msg As Object, eventData As Object, transitionList As Object)

    touchIndex$ = str(msg.GetInt())

    m.diagnostics.PrintDebug("Touch event" + touchIndex$)

    for each zone in m.sign.zones
        
        if type(zone.activeState) = "roAssociativeArray" then

            state = zone.activeState

            if type(state.touchEvents) = "roAssociativeArray" then
            
                touchEvent = state.touchEvents[touchIndex$]
                
                if type(touchEvent) = "roAssociativeArray" then
                
                    nextState$ = m.GetNextStateName(zone, touchEvent)

                    AddToEventData(eventData, nextState$, zone)
                    AddToTransitionList(transitionList, zone, touchEvent)
                                    
                    zone.lastEvent$ = "TouchEvent"

                    m.LogEvent("touchEvent", "", zone)

                endif
                
            endif
        
        endif

    next

    return eventData

End Function


Function HandleURLEvent(msg As Object, eventData As Object, transitionList As Object)

    eventHandled = m.networking.URLEvent(msg)

    if not eventHandled then
    
        if type(m.feedVideoDownloader) = "roAssociativeArray" then
        
            m.feedVideoDownloader.HandleUrlEvent(msg, m.feedPlayer)
            
        endif
        
        if type(m.feedPlayer) = "roAssociativeArray" then
        
            rv = m.feedPlayer.HandleUrlEvent(msg)
        
        endif
        
    endif
            
    return eventData
    
End Function


Function HandleSyncPoolEvent(msg As Object, eventData As Object, transitionList As Object)

    returnValue = m.networking.PoolEvent(msg)
    if returnValue = -1 then

        m.diagnostics.PrintTimestamp()
        m.diagnostics.PrintDebug("### Restart BSP - call StopPlayback")

        m.StopPlayback()
        m.PrepareForRestart()
        eventData.code = -1
        ' return eventData
    endif

    return eventData

End Function


Function HandleSyncPoolProgressEvent(msg As Object, eventData As Object, transitionList As Object)

    m.networking.SyncPoolProgressEvent(msg)

    return eventData

End Function


Function ParseTTKChargeEvent(serialInput$ As String) As Object

	chargeEvent = CreateObject("roAssociativeArray")
	
	type$ = mid(serialInput$, 1, 2)
	if ucase(type$) = "V=" then
		chargeEvent.type = "v"
	else if ucase(type$) = "I=" then
		chargeEvent.type = "i"
	else
		return invalid
	endif

	value$ = mid(serialInput$, 3)
	chargeEvent.value = HexStringToInt(value$)

	return chargeEvent
		
End Function


Function HexStringToInt(value$ As String) As Integer

	value$ = ucase(value$)
	
	value% = HexCharToInt(mid(value$, 1, 1)) * 4096
	value% = value% + HexCharToInt(mid(value$, 2, 1)) * 256
	value% = value% + HexCharToInt(mid(value$, 3, 1)) * 16
	value% = value% + HexCharToInt(mid(value$, 4, 1)) 
	
	return value%
	
End Function


Function HexCharToInt(char$ As String) As Integer

	if asc(char$) >= 65 then
		value% = asc(char$) - asc("A") + 10
	else
		value% = asc(char$) - asc("0")
	endif
	
	return value%
	
End Function


Function IntToHexString(value% As Integer) As String

	retString$ = ""
	bitValue% = 4096

	while bitValue% >= 1
	
		if value% > bitValue% then
			partial% = value% / bitValue%
			if partial% >= 10 then
				partial$ = StripLeadingSpaces(chr(65 + partial% - 10))
			else
				partial$ = StripLeadingSpaces(chr(48 + partial%))
			endif
			value% = value% - (partial% * bitValue%)
		else
			partial$ = "0"
		endif
		
		retString$ = retString$ + partial$
	
		bitValue% = bitValue% / 16
		
	end while

	return retString$
	
End Function


Function HandleStreamLineEvent(msg As Object, eventData As Object, transitionList As Object)

    m.diagnostics.PrintDebug("Serial Event " + str(msg))

    'serialEvent$ = msg.GetString()
     serialEvent$ = mid(str(msg),2)
    port$ = msg.GetUserData()

	if m.ttkChargerPresent then
		if port$ = "1" then
			chargeEvent = ParseTTKChargeEvent(serialEvent$)
			if type(chargeEvent) = "roAssociativeArray" then
				value$ = IntToHexString(chargeEvent.value)
				if chargeEvent.type = "v" then
					m.ttk.voltage = chargeEvent.value
					m.ttk.voltageHex = value$
				else if chargeEvent.type = "i" then
					m.ttk.current = chargeEvent.value
					m.ttk.currentHex = value$
				endif
				
				m.logging.WriteEventLogEntry("NoZone", "ttk" + chargeEvent.type, value$)

				' determine current state and determine if it's different than the last state
				if chargeEvent.type = "i" then
	
					if m.networkingActive then
						m.networking.AddTTKItem("Reading", m.ttk.voltageHex, m.ttk.currentHex)
					endif

					drain = false
					charge = false

' issues - first power up, current was 7000, 7280 (charger was plugged in)
					
					if m.ttk.current < &H6BC0 then
						drain = true
						charge = false
						currentState$ = "drain"
					else if m.ttk.current > &H7F00 then
						drain = false
						charge = true
						currentState$ = "charge"
					else
						print "unknown state"
						currentState$ = "unknown"
					endif
					
					if currentState$ <> "unknown" then
					
						event$ = "none"
						currentBatteryState$ = "ok"
					
						if currentState$ = "drain" and (m.ttk.previousState$ = "charge" or m.ttk.previousState$ = "unknown") then
							event$ = "chargerRemoved"
						endif
						
						if currentState$ = "charge" and (m.ttk.previousState$ = "drain" or m.ttk.previousState$ = "unknown") then
							event$ = "chargingStarted"
						endif
							
						if m.ttk.voltage < &HAF00 then
							currentBatteryState$ = "batteryEmpty"
						endif
							
						if currentState$ = "charge" and m.ttk.voltage > &HCD00 then
							currentBatteryState$ = "batteryFull"
						endif
						
print "previousState = " + m.ttk.previousState$					
print "currentState = " + currentState$
print "event = " + event$
print "previousBatteryState = " + m.ttk.previousBatteryState$
print "currentBatteryState = " + currentBatteryState$

						if event$ <> "none" and event$ <> m.ttk.previousEvent$ then
							m.logging.WriteEventLogEntry("NoZone", "ttk " + event$, "")

							if m.networkingActive then
								m.networking.AddTTKItem(event$, m.ttk.voltageHex, m.ttk.currentHex)
							endif
							
							m.ttk.previousEvent$ = event$
						endif	
						
						if currentBatteryState$ <> "ok" and currentBatteryState$ <> m.ttk.previousBatteryState$ then
							m.logging.WriteEventLogEntry("NoZone", "ttk " + currentBatteryState$, "")

							if m.networkingActive then
								m.networking.AddTTKItem(currentBatteryState$, m.ttk.voltageHex, m.ttk.currentHex)
							endif
							
						endif	

						m.ttk.previousBatteryState$ = currentBatteryState$
						m.ttk.previousState$ = currentState$
						
					endif
					
				endif
			endif
			return eventData
		endif
	endif

    for each zone in m.sign.zones

        if type(zone.activeState) = "roAssociativeArray" then
        
            state = zone.activeState
            
            serialEvents = state.serialEvents

            if type(serialEvents) = "roAssociativeArray" then
                if type(serialEvents[port$]) = "roAssociativeArray" then
                    if type(serialEvents[port$][serialEvent$]) = "roAssociativeArray" then
                    
                        transition = serialEvents[port$][serialEvent$]
                        nextState$ = m.GetNextStateName(zone, transition)
                        
                        AddToEventData(eventData, nextState$, zone)
                        AddToTransitionList(transitionList, zone, transition)
                        
                        zone.lastEvent$ = "SerialEvent " + serialEvent$
                        
                        m.LogEvent("serialEvent", serialEvent$, zone)

                    endif
                endif
            endif
                        
        endif
        
    next

    return eventData

End Function


Function HandleIRRemoteEvent(msg As Object, eventData As Object, transitionList As Object)

    m.diagnostics.PrintDebug("Remote Event" + str(msg.GetInt()))

    for each zone in m.sign.zones

        if type(zone.activeState) = "roAssociativeArray" then
        
            state = zone.activeState
        
            remoteEvent% = msg.GetInt()
            remoteEvent$ = ConvertToRemoteCommand(remoteEvent%)
        
            remoteEvents = state.remoteEvents
        
            if type(remoteEvents) = "roAssociativeArray" then
                if type(remoteEvents[remoteEvent$]) = "roAssociativeArray" then
                    nextState$ = m.GetNextStateName(zone, state.remoteEvents[remoteEvent$])
                    
                    AddToEventData(eventData, nextState$, zone)
                    AddToTransitionList(transitionList, zone, state.remoteEvents[remoteEvent$])
                    
                    zone.lastEvent$ = "RemoteEvent " + remoteEvent$

                    m.LogEvent("remoteEvent", remoteEvent$, zone)

                endif
            endif            
        
        endif
        
    next

    return eventData

End Function


Sub InitializeNonPrintableKeyboardCodeList()
' Space				<sp>	32
' Left arrow        <la>    32848
' Right arrow       <ra>    32847
' Up arrow          <ua>    32850
' Down arrow        <da>    32849
' Return            <rn>    10
' Enter             <en>    13
' Escape            <es>    27
' Page Up           <pu>    32843
' Page Down         <pd>    32846
' F1                <f1>    32826
' F2                <f2>    32827
' F3                <f3>    32828
' F4                <f4>    32829
' F5                <f5>    32830
' F6                <f6>    32831
' F7                <f7>    32832
' F8                <f8>    32833
' F9                <f9>    32834
' F10               <f10>   32835
' F11               <f11>   32836
' F12               <f12>   32837
' F13 (Print Screen)<f13>   32838
' F14 (Scroll Lock) <f14>   32839
' F15 (Pause Break) <f15>   32840
' Backspace         <bs>    8
' Tab               <tb>    9
' Insert            <in>    32841
' Delete            <de>    127
' Home              <ho>    32842
' End               <ed>    32845

    m.nonPrintableKeyboardKeys = CreateObject("roAssociativeArray")
    m.nonPrintableKeyboardKeys.AddReplace("8","<bs>")
    m.nonPrintableKeyboardKeys.AddReplace("9","<tb>")
    m.nonPrintableKeyboardKeys.AddReplace("10","<rn>")
    m.nonPrintableKeyboardKeys.AddReplace("13","<en>")
    m.nonPrintableKeyboardKeys.AddReplace("27","<es>")
    m.nonPrintableKeyboardKeys.AddReplace("32","<sp>")
    m.nonPrintableKeyboardKeys.AddReplace("127","<de>")
    m.nonPrintableKeyboardKeys.AddReplace("32848","<la>")
    m.nonPrintableKeyboardKeys.AddReplace("32847","<ra>")
    m.nonPrintableKeyboardKeys.AddReplace("32850","<ua>")
    m.nonPrintableKeyboardKeys.AddReplace("32849","<da>")
    m.nonPrintableKeyboardKeys.AddReplace("32843","<pu>")
    m.nonPrintableKeyboardKeys.AddReplace("32846","<pd>")
    m.nonPrintableKeyboardKeys.AddReplace("32826","<f1>")
    m.nonPrintableKeyboardKeys.AddReplace("32827","<f2>")
    m.nonPrintableKeyboardKeys.AddReplace("32828","<f3>")
    m.nonPrintableKeyboardKeys.AddReplace("32829","<f4>")
    m.nonPrintableKeyboardKeys.AddReplace("32830","<f5>")
    m.nonPrintableKeyboardKeys.AddReplace("32831","<f6>")
    m.nonPrintableKeyboardKeys.AddReplace("32832","<f7>")
    m.nonPrintableKeyboardKeys.AddReplace("32833","<f8>")
    m.nonPrintableKeyboardKeys.AddReplace("32834","<f9>")
    m.nonPrintableKeyboardKeys.AddReplace("32835","<f10>")
    m.nonPrintableKeyboardKeys.AddReplace("32836","<f11>")
    m.nonPrintableKeyboardKeys.AddReplace("32837","<f12>")
    m.nonPrintableKeyboardKeys.AddReplace("32838","<f13>")
    m.nonPrintableKeyboardKeys.AddReplace("32839","<f14>")
    m.nonPrintableKeyboardKeys.AddReplace("32840","<f15>")
    m.nonPrintableKeyboardKeys.AddReplace("32841","<in>")
    m.nonPrintableKeyboardKeys.AddReplace("32842","<ho>")
    m.nonPrintableKeyboardKeys.AddReplace("32843","<ed>")
    
End Sub


Function HandleKeyboardEvent(msg As Object, eventData As Object, transitionList As Object)

    ' note - this code does not fully cover the case where one state is waiting for keyboard input
    ' and another state is waiting for barcode input.
    
    m.diagnostics.PrintDebug("Keyboard Press" + str(msg.GetInt()))

    usbMatchFound = false
    
    keyboardChar$ = chr(msg.GetInt())
                    
    for each zone in m.sign.zones

        if type(zone.activeState) = "roAssociativeArray" then
        
            state = zone.activeState
            
            usbStringEvents = state.usbStringEvents
            keyboardEvents = state.keyboardEvents

            checkUSBInputString = false
            if type(usbStringEvents) = "roAssociativeArray" then
                if msg.GetInt() <> 13 then
                    state.usbInputBuffer$ = state.usbInputBuffer$ + keyboardChar$
                    checkUSBInputString = false
                else
                    checkUSBInputString = true
                endif
            endif
            
            ' check for bar code input (usb characters terminated by an Enter key)
            if type(usbStringEvents) = "roAssociativeArray" then
                if checkUSBInputString and type(usbStringEvents[state.usbInputBuffer$]) = "roAssociativeArray" then
                    nextState$ = m.GetNextStateName(zone, state.usbStringEvents[state.usbInputBuffer$])
                    
                    AddToEventData(eventData, nextState$, zone)
                    AddToTransitionList(transitionList, zone, state.usbStringEvents[state.usbInputBuffer$])
                    
                    usbMatchFound = true
                    
                    zone.lastEvent$ = "USBEvent " + state.usbInputBuffer$

                    m.LogEvent("usbEvent", state.usbInputBuffer$, zone)
                    
                endif
            endif
            
            ' check for single keyboard characters
            if type(keyboardEvents) = "roAssociativeArray" and (not usbMatchFound) then
            
                ' if keyboard input is non printable character, convert it to the special code
                keyboardCode$ = m.GetNonPrintableKeyboardCode(msg.GetInt())
                if keyboardCode$ <> "" then
                    keyboardChar$ = keyboardCode$
                endif
                                
                if type(keyboardEvents[keyboardChar$]) = "roAssociativeArray" then
                    nextState$ = m.GetNextStateName(zone, state.keyboardEvents[keyboardChar$])
                    
                    AddToEventData(eventData, nextState$, zone)
                    AddToTransitionList(transitionList, zone, state.keyboardEvents[keyboardChar$])
                    
                    zone.lastEvent$ = "KeyboardEvent " + keyboardChar$

                    m.LogEvent("keyboardEvent", keyboardChar$, zone)
                    
                endif
            endif        
            
            ' clear the buffer when the user presses enter
            if msg.GetInt() = 13 then state.usbInputBuffer$ = ""
            
        endif
        
    next
              
    return eventData

End Function


Function GetNonPrintableKeyboardCode(keyboardInput% As Integer) As String

    keyboardInput$ = LCase(StripLeadingSpaces(stri(keyboardInput%)))
    if m.nonPrintableKeyboardKeys.DoesExist(keyboardInput$) then
        return m.nonPrintableKeyboardKeys.Lookup(keyboardInput$)
    endif
    
    return ""
    
End Function


Function HandleDatagramEvent(msg As Object, eventData As Object, transitionList As Object)

    ' could be either a udp event or a synchronize event
    
    m.diagnostics.PrintDebug("UDP Event " + msg.GetString())
    
    udpEvent$ = msg.GetString()
    
    for each zone in m.sign.zones

        if type(zone.activeState) = "roAssociativeArray" then
        
            synchronizeEvents = zone.activeState.synchronizeEvents
            udpEvents = zone.activeState.udpEvents
            
            ' check to see if this is a synchronization preload or play event
            if type(synchronizeEvents) = "roAssociativeArray" then
            
                index% = instr(1, udpEvent$, "pre-")
                if index% = 1 then
                    ' preload next file
                    synchronizeEvent$ = mid(udpEvent$, 5)
                    if type(synchronizeEvents[synchronizeEvent$]) = "roAssociativeArray" then
                    
                        ' get the next file and preload it
                        next$ = synchronizeEvents[synchronizeEvent$].targetMediaState$
                        nextState = zone.activePlaylist.stateTable[next$]

                        preloadRequired = true
                        if type(zone.preloadState) = "roAssociativeArray" then
                            if zone.preloadedStateName$ = nextState.name$
                                preloadRequired = false
                            endif
                        endif                                    

                        ' set this variable so that launchVideo knows what has been preloaded
                        zone.preloadState = nextState
                        
                        ' currently only support preload / synchronizing with images and videos
                        if preloadRequired then
                            m.PreloadItem(zone, zone.preloadState)
	                    endif
	                    
                    endif
                    
                endif
                
                index% = instr(1, udpEvent$, "ply-")
                if index% = 1 then
                
                    if m.debuggingSynchro then m.udpSender.Send("received ply-1")
                    
                    ' just transition to the next state where the file will be played
                    synchronizeEvent$ = mid(udpEvent$, 5)
                    if type(synchronizeEvents[synchronizeEvent$]) = "roAssociativeArray" then
                    
                        nextState$ = m.GetNextStateName(zone, zone.activeState.synchronizeEvents[synchronizeEvent$])
                        
                        AddToEventData(eventData, nextState$, zone)
                        AddToTransitionList(transitionList, zone, zone.activeState.synchronizeEvents[synchronizeEvent$])

                        zone.lastEvent$ = "SyncEvent " + synchronizeEvent$

                        m.LogEvent("syncEvent", synchronizeEvent$, zone)

                    endif
                    
                endif
            
            else if type(udpEvents) = "roAssociativeArray" then
            
                if type(udpEvents[udpEvent$]) = "roAssociativeArray" then
                    nextState$ = m.GetNextStateName(zone, udpEvents[udpEvent$])
                    
                    AddToEventData(eventData, nextState$, zone)
                    AddToTransitionList(transitionList, zone, udpEvents[udpEvent$])
                    
                    zone.lastEvent$ = "UDPEvent " + udpEvent$

                    m.LogEvent("udpEvent", udpEvent$, zone)

                endif
                
            endif          
        
        endif
                        
    next

    return eventData
    
End Function


Sub PreloadItem(zone As Object, preloadState As Object)

    if type(preloadState.imageItem) = "roAssociativeArray" then
        imageItem = preloadState.imageItem
        imageItemFilePath$ = GetPoolFilePath(m.syncPoolFiles, imageItem.fileName$)
        zone.imagePlayer.PreloadFile(imageItemFilePath$)
        zone.preloadedStateName$ = preloadState.name$
        m.diagnostics.PrintDebug("Preloaded file in PreloadItem: " + imageItem.fileName$)
    else if type(preloadState.videoItem) = "roAssociativeArray" then
        videoItem = preloadState.videoItem
        videoItemFilePath$ = GetPoolFilePath(m.syncPoolFiles, videoItem.fileName$)
        zone.videoPlayer.PreloadFile(videoItemFilePath$)
        zone.preloadedStateName$ = preloadState.name$
        m.diagnostics.PrintDebug("Preloaded file in PreloadItem: " + videoItem.fileName$)
    endif

End Sub


Function CheckForImageToPreload(zone As Object, nextStateName As Object) As Object

    if type(nextStateName) = "roString" and nextStateName <> "" then
        
        nextState = zone.activePlaylist.stateTable[nextStateName]
        
        if type(nextState.imageItem) = "roAssociativeArray" then

            return nextState
            
        endif
                
    endif
    
    return 0
    
End Function


Function GetImageStateToPreload(zone As Object) As Object

    if type(zone.activeState) <> "roAssociativeArray" return invalid
                
    state = zone.activeState
    gpioEvents = state.gpioEvents
    bp900Events = state.bp900Events

    for each gpioEventNumber in gpioEvents
        preloadState = m.CheckForImageToPreload(zone, gpioEvents[gpioEventNumber].targetMediaState$)        
        if type(preloadState) = "roAssociativeArray" then return preloadState
    next

    for each bp900EventNumber in bp900Events
        preloadState = m.CheckForImageToPreload(zone, bp900Events[bp900EventNumber].targetMediaState$)        
        if type(preloadState) = "roAssociativeArray" then return preloadState
    next

    if type(state.mstimeoutEvent) = "roAssociativeArray" then
        preloadState = m.CheckForImageToPreload(zone, state.mstimeoutEvent.targetMediaState$)
        if type(preloadState) = "roAssociativeArray" then
            return preloadState
        endif
    endif
    
    if type(state.videoEndEvent) = "roAssociativeArray" then
        preloadState = m.CheckForImageToPreload(zone, state.videoEndEvent.targetMediaState$)
        if type(preloadState) = "roAssociativeArray" then
            return preloadState
        endif
    endif
    
    if type(state.signChannelEndEvent) = "roAssociativeArray" then
'        preloadState = m.CheckForImageToPreload(zone, state.signChannelEndEvent.targetMediaState$)
'        if type(preloadState) = "roAssociativeArray" then
'            return preloadState
'        endif
        return 0
    endif
    
    if type(state.audioEndEvent) = "roAssociativeArray" then
        preloadState = m.CheckForImageToPreload(zone, state.audioEndEvent.targetMediaState$)
        if type(preloadState) = "roAssociativeArray" then
            return preloadState
        endif
    endif
    
    if type(state.keyboardEvents) = "roAssociativeArray" then
        for each keyboardEvent in state.keyboardEvents
            preloadState = m.CheckForImageToPreload(zone, state.keyboardEvents[keyboardEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif

    if type(state.remoteEvents) = "roAssociativeArray" then
        for each remoteEvent in state.remoteEvents
            preloadState = m.CheckForImageToPreload(zone, state.remoteEvents[remoteEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif
    
    if type(state.serialEvents) = "roAssociativeArray" then
        for each serialEvent in state.serialEvents
            preloadState = m.CheckForImageToPreload(zone, state.serialEvents[serialEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif
    
    if type(state.usbEvents) = "roAssociativeArray" then
        for each usbEvent in state.usbEvents
            preloadState = m.CheckForImageToPreload(zone, state.usbEvents[usbEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif
    
    if type(state.udpEvents) = "roAssociativeArray" then
        for each udpEvent in state.udpEvents
            preloadState = m.CheckForImageToPreload(zone, state.udpEvents[udpEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif
    
    if type(state.synchronizeEvents) = "roAssociativeArray" then
        for each synchronizeEvent in state.synchronizeEvents
            preloadState = m.CheckForImageToPreload(zone, state.synchronizeEvents[synchronizeEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif

    if type(state.internalSynchronizeEvents) = "roAssociativeArray" then
        for each internalSynchronizeEvent in state.internalSynchronizeEvents
            preloadState = m.CheckForImageToPreload(zone, state.internalSynchronizeEvents[internalSynchronizeEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif

    if type(state.touchEvents) = "roAssociativeArray" then
        for each touchEvent in state.touchEvents
            preloadState = m.CheckForImageToPreload(zone, state.touchEvents[touchEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif

    if type(state.videoTimeCodeEvents) = "roAssociativeArray" then
        for each videoTimeCodeEvent in state.videoTimeCodeEvents
            preloadState = m.CheckForImageToPreload(zone, state.videoTimeCodeEvents[videoTimeCodeEvent].targetMediaState$)        
            if type(preloadState) = "roAssociativeArray" then return preloadState
        next
    endif

End Function


Sub PreloadImage(zone As Object)

    m.diagnostics.PrintTimestamp()

    ' get an image to preload if appropriate
    zone.preloadState = m.GetImageStateToPreload(zone)
    if type(zone.preloadState) = "roAssociativeArray" then
        imageFile$ = zone.preloadState.imageItem.fileName$
        imageFilePath$ = GetPoolFilePath(m.syncPoolFiles, imageFile$)
        ok = zone.imagePlayer.PreloadFile(imageFilePath$)
        if ok = 0 then
            m.diagnostics.PrintDebug("Error preloading file in PreloadImage: " + imageFile$ + ", " + imageFilePath$)
        else
            m.diagnostics.PrintDebug("Preloaded file in PreloadImage: " + imageFile$)
            m.diagnostics.PrintTimestamp()
            zone.preloadedStateName$ = zone.preloadState.name$
        endif   
    endif
    
End Sub


Sub DisplayAA(name$ As String, aa As Object)

    print "Dump contents of ";name$
    
    for each key in aa
        print "key is ";key
    next
    
End Sub


Sub CreateBP900Output()

    if type(m.bp900Output) <> "roControlPort" then
        m.diagnostics.PrintDebug("Creating bp900Output")
        m.bp900Output = CreateObject("roControlPort", "TouchBoard-0-LED")
        if type(m.bp900Output) = "roControlPort" then
            m.bp900OutputSetup = CreateObject("roControlPort", "TouchBoard-0-LED-SETUP")
            if type(m.bp900OutputSetup) = "roControlPort" then
                m.bp900OutputSetup.SetOutputValue(0, 22)
            endif
        endif
    endif
    
End Sub


Sub CreateUDPSender()

    if type(m.udpSender) <> "roDatagramSender" then
        m.diagnostics.PrintDebug("Creating roDatagramSender")
        m.udpSender = CreateObject("roDatagramSender")
        m.udpSender.SetDestination(m.udpAddress$, m.udpSendPort)
    endif

    return
    
End Sub


Sub CreateSerial(port$ As String)

    if type(m.serial) <> "roAssociativeArray" then
        m.serial = CreateObject("roAssociativeArray")
    endif

    if type(m.serial[port$]) = "roSerialPort" then
        return
    endif

    port% = int(val(port$))
    serialPortConfiguration = m.serialPortConfigurations[port%]
    serialPortSpeed% = serialPortConfiguration.serialPortSpeed%
    serialPortMode$ = serialPortConfiguration.serialPortMode$    
      
    serial = CreateObject("roSerialPort", port%, serialPortSpeed%)
    if type(serial) <> "roSerialPort" then 
        m.diagnostics.PrintDebug("Error creating roSerialPort " + port$)
        return
    endif
    
    ok = serial.SetMode(serialPortMode$)
    if not ok then print "Error setting serial mode" : stop
	
	'if port$ = "1" then
		serial.SetByteEventPort(m.msgport)
	'else
	'	serial.SetLineEventPort(m.msgPort)
    'endif 
	
	serial.SetUserData(port$)

    m.serial[port$] = serial
    
    return
    
End Sub


Sub ExecuteMediaStateCommands(zone As Object, state As Object)

    if type(state.cmds) = "roArray" then
        for each cmd in state.cmds
            m.ExecuteCmd(zone, cmd.name$, cmd.parameters)
        next
    endif

End Sub


Function GetVideoZone() As Object

    zone = invalid
    
    if type(m.sign) = "roAssociativeArray" then
        if type(m.sign.videoZone) = "roAssociativeArray" then
            return m.sign.videoZone
        endif
    endif
    
    return zone
    
End Function


Sub SetVideoVolume(parameter$ As String)

    volume% = int(val(parameter$))
    
    zone = m.GetVideoZone()
    if type(zone) = "roAssociativeArray" then
        if type(zone.videoPlayer) = "roVideoPlayer" then
            zone.videoPlayer.SetVolume(volume%)
            for i% = 0 to 5
                zone.videoChannelVolumes[i%] = volume%
            next
        endif
    endif

End Sub


Sub ChangeVideoVolume(channelMask% as Integer, delta% As Integer)

    zone = m.GetVideoZone()
    if type(zone) = "roAssociativeArray" then
        if type(zone.videoPlayer) = "roVideoPlayer" then
            m.ChangeChannelVolumes(zone.videoPlayer, zone.videoChannelVolumes, channelMask%, delta%)
        endif
    endif
    
End Sub


Sub IncrementVideoVolume(parameter$)

    m.ChangeVideoVolume(63, int(val(parameter$)))        

End Sub


Sub DecrementVideoVolume(parameter$)

    delta% = int(val(parameter$))
    m.ChangeVideoVolume(63, -delta%)        

End Sub


Sub SetVideoChannnelVolume(channelMask$ As String, volume$ As String)
    
    zone = m.GetVideoZone()
    if type(zone) = "roAssociativeArray" then
        if type(zone.videoPlayer) = "roVideoPlayer" then
            m.SetChannelVolumes(zone.videoPlayer, zone.videoChannelVolumes, int(val(channelMask$)), int(val(volume$)))
        endif
    endif
    
End Sub


Sub IncrementVideoChannnelVolumes(channelMask$, volumeDelta$)
    
    channelMask% = int(val(channelMask$))
    m.ChangeVideoVolume(channelMask%, int(val(volumeDelta$)))
    
End Sub


Sub DecrementVideoChannnelVolumes(channelMask$, volumeDelta$)

    channelMask% = int(val(channelMask$))
    delta% = int(val(volumeDelta$))
    m.ChangeVideoVolume(channelMask%, -delta%)

End Sub


Sub SetAudioVolume(parameter$ As String)

    volume% = int(val(parameter$))
    
    zone = m.GetVideoZone()
    if type(zone) = "roAssociativeArray" then
        if type(zone.audioPlayer) = "roAudioPlayer" then
            zone.audioPlayer.SetVolume(volume%)
            for i% = 0 to 5
                zone.audioChannelVolumes[i%] = volume%
            next
        endif
    endif

End Sub


Sub ChangeAudioVolume(channelMask% as Integer, delta% As Integer)

    zone = m.GetVideoZone()
    if type(zone) = "roAssociativeArray" then
        if type(zone.audioPlayer) = "roAudioPlayer" then
            m.ChangeChannelVolumes(zone.audioPlayer, zone.audioChannelVolumes, channelMask%, delta%)
        endif
    endif
    
End Sub


Sub SetAudioChannnelVolume(channelMask$ As String, volume$ As String)
    
    zone = m.GetVideoZone()
    if type(zone) = "roAssociativeArray" then
        if type(zone.audioPlayer) = "roAudioPlayer" then
            m.SetChannelVolumes(zone.audioPlayer, zone.audioChannelVolumes, int(val(channelMask$)), int(val(volume$)))
        endif
    endif
    
End Sub


Sub IncrementAudioVolume(parameter$)

    m.ChangeAudioVolume(63, int(val(parameter$)))        

End Sub


Sub DecrementAudioVolume(parameter$)

    delta% = int(val(parameter$))
    m.ChangeAudioVolume(63, -delta%)        

End Sub


Sub IncrementAudioChannnelVolumes(channelMask$, volumeDelta$)
    
    channelMask% = int(val(channelMask$))
    m.ChangeAudioVolume(channelMask%, int(val(volumeDelta$)))
    
End Sub


Sub DecrementAudioChannnelVolumes(channelMask$, volumeDelta$)

    channelMask% = int(val(channelMask$))
    delta% = int(val(volumeDelta$))
    m.ChangeAudioVolume(channelMask%, -delta%)

End Sub


Sub ChangeChannelVolumes(player As Object, channelVolumes As Object, channelMask% As Integer, delta% As Integer)

    for i% = 0 to 5
        mask% = 2 ^ i%
        if channelMask% and mask% then
            channelVolumes[i%] = channelVolumes[i%] + delta%
            if channelVolumes[i%] > 100 then
                channelVolumes[i%] = 100
            else if channelVolumes[i%] < 0 then
                channelVolumes[i%] = 0
            endif
            player.SetChannelVolumes(mask%, channelVolumes[i%])
print "SetChannelVolumes - mask = ";mask%;", volume = ";channelVolumes[i%]                  
        endif
    next
            
End Sub


Sub SetChannelVolumes(player As Object, channelVolumes As Object, channelMask% As Integer, volume% As Integer)

    for i% = 0 to 5
        mask% = 2 ^ i%
        if channelMask% and mask% then
            channelVolumes[i%] = volume%
            player.SetChannelVolumes(mask%, channelVolumes[i%])
print "SetChannelVolumes - mask = ";mask%;", volume = ";channelVolumes[i%]                  
        endif
    next
            
End Sub


Sub PauseVideo()

    if type(m.sign) = "roAssociativeArray" then
        if type(m.sign.videoZone) = "roAssociativeArray" then
            if type(m.sign.VideoZone.videoPlayer) = "roVideoPlayer" then
                m.sign.VideoZone.videoPlayer.Pause()
            endif
        endif
    endif

End Sub


Sub ResumeVideo()

    if type(m.sign) = "roAssociativeArray" then
        if type(m.sign.videoZone) = "roAssociativeArray" then
            if type(m.sign.VideoZone.videoPlayer) = "roVideoPlayer" then
                m.sign.VideoZone.videoPlayer.Resume()
            endif
        endif
    endif

End Sub


Sub SetPowerSaveMode(enablePowerSaveMode As Boolean)

    videoMode = CreateObject("roVideoMode")
    videoMode.SetPowerSaveMode(enablePowerSaveMode)
    videoMode = invalid

End Sub


Sub ExecuteCmd(zone As Object, command$ As String, parameters As Object)

    m.diagnostics.PrintDebug("ExecuteCmd " + command$)

    if command$ = "gpioOnCommand" then
    
        gpioNumber$ = parameters["gpioNumber"]
        m.diagnostics.PrintDebug("Turn on gpioNumber " + gpioNumber$)
        m.gpioPort.SetOutputState(int(val(gpioNumber$)), 1)
        
    else if command$ = "gpioOffCommand" then
    
        gpioNumber$ = parameters["gpioNumber"]
        m.diagnostics.PrintDebug("Turn off gpioNumber " + gpioNumber$)
        m.gpioPort.SetOutputState(int(val(gpioNumber$)), 0)

    else if command$ = "gpioSetStateCommand" then
    
        gpioState$ = parameters["stateValue"]
        m.diagnostics.PrintDebug("Set GPIO's to " + gpioState$)
        m.gpioPort.SetWholeState(int(val(gpioState$)))

    else if command$ = "mapDigitalOutputVideo" then
    
        m.MapDigitalOutput(zone.videoPlayer, parameters)
        
    else if command$ = "mapDigitalOutputAudio" then
    
        m.MapDigitalOutput(zone.audioPlayer, parameters)

    else if command$ = "setAudioOutputVideo" then
    
        m.SetAudioOutput(zone.videoPlayer, parameters)
        
    else if command$ = "setAudioOutputAudio" then

        m.SetAudioOutput(zone.audioPlayer, parameters)

    else if command$ = "setAudioModeVideo" then

        m.SetAudioMode(zone.videoPlayer, parameters)

    else if command$ = "setAudioModeAudio" then

        m.SetAudioMode(zone.audioPlayer, parameters)

    else if command$ = "mapStereoOutputVideo" then

        m.MapStereoOutput(zone.videoPlayer, parameters)

    else if command$ = "mapStereoOutputAudio" then

        m.MapStereoOutput(zone.audioPlayer, parameters)

    else if command$ = "setSpdifMuteVideo" then

        m.SetSpdifMute(zone.videoPlayer, parameters)

    else if command$ = "setSpdifMuteAudio" then

        m.SetSpdifMute(zone.audioPlayer, parameters)

    else if command$ = "setAnalogMuteVideo" then

        m.SetAnalogMute(zone.videoChannelVolumes, zone.videoPlayer, parameters)

    else if command$ = "setAnalogMuteAudio" then

        m.SetAnalogMute(zone.audioChannelVolumes, zone.audioPlayer, parameters)

    else if command$ = "setVideoVolume" then
    
        volume$ = parameters["volume"]
        m.diagnostics.PrintDebug("Set video volume to " + volume$)
        m.SetVideoVolume(volume$)

    else if command$ = "incrementVideoVolume" then
    
        volumeDelta$ = parameters["volumeDelta"]
        m.diagnostics.PrintDebug("Increment video volume by " + volumeDelta$)
        m.IncrementVideoVolume(volumeDelta$)

    else if command$ = "decrementVideoVolume" then
    
        volumeDelta$ = parameters["volumeDelta"]
        m.diagnostics.PrintDebug("Decrement video volume by " + volumeDelta$)
        m.DecrementVideoVolume(volumeDelta$)

    else if command$ = "setAudioVolume" then
    
        volume$ = parameters["volume"]
        m.diagnostics.PrintDebug("Set audio volume to " + volume$)
        m.SetAudioVolume(volume$)

    else if command$ = "incrementAudioVolume" then
    
        volumeDelta$ = parameters["volumeDelta"]
        m.diagnostics.PrintDebug("Increment audio volume by " + volumeDelta$)
        m.IncrementAudioVolume(volumeDelta$)

    else if command$ = "decrementAudioVolume" then
    
        volumeDelta$ = parameters["volumeDelta"]
        m.diagnostics.PrintDebug("Decrement audio volume by " + volumeDelta$)
        m.DecrementAudioVolume(volumeDelta$)

    else if command$ = "setVideoChannelVolumes" then
    
        channelMask$ = parameters["channel"]
        volume$ = parameters["volume"]
        m.diagnostics.PrintDebug("Set video channel volume: channel = " + channelMask$ + ", volume = " + volume$)
        m.SetVideoChannnelVolume(channelMask$, volume$)
        
    else if command$ = "incrementVideoChannelVolumes" then
    
        channelMask$ = parameters["channel"]
        volumeDelta$ = parameters["volumeDelta"]
        m.diagnostics.PrintDebug("Increment video channel volumes: channel = " + channelMask$ + ", volume delta = " + volumeDelta$)
        m.IncrementVideoChannnelVolumes(channelMask$, volumeDelta$)
        
    else if command$ = "decrementVideoChannelVolumes" then
            
        channelMask$ = parameters["channel"]
        volumeDelta$ = parameters["volumeDelta"]
        m.diagnostics.PrintDebug("Decrement video channel volumes: channel = " + channelMask$ + ", volume delta = " + volumeDelta$)
        m.DecrementVideoChannnelVolumes(channelMask$, volumeDelta$)
        
    else if command$ = "setAudioChannelVolumes" then
    
        channelMask$ = parameters["channel"]
        volume$ = parameters["volume"]
        m.diagnostics.PrintDebug("Set audio channel volume: channel = " + channelMask$ + ", volume = " + volume$)
        m.SetAudioChannnelVolume(channelMask$, volume$)
        
    else if command$ = "incrementAudioChannelVolumes" then
    
        channelMask$ = parameters["channel"]
        volumeDelta$ = parameters["volumeDelta"]
        m.diagnostics.PrintDebug("Increment audio channel volumes: channel = " + channelMask$ + ", volume delta = " + volumeDelta$)
        m.IncrementAudioChannnelVolumes(channelMask$, volumeDelta$)
        
    else if command$ = "decrementAudioChannelVolumes" then
            
        channelMask$ = parameters["channel"]
        volumeDelta$ = parameters["volumeDelta"]
        m.diagnostics.PrintDebug("Decrement audio channel volumes: channel = " + channelMask$ + ", volume delta = " + volumeDelta$)
        m.DecrementAudioChannnelVolumes(channelMask$, volumeDelta$)
        
    else if command$ = "sendSerialStringCommand" then
    
        port$ = parameters["port"]
        serialString$ = parameters["serialString"]

        m.diagnostics.PrintDebug("sendSerialStringCommand " + serialString$ + " to port " + port$)
        
        if type(m.serial) = "roAssociativeArray" then
            serial = m.serial[port$]
            if type(serial) = "roSerialPort" then
                serial.SendLine(serialString$)
            endif
        endif
        
    else if command$ = "sendSerialBlockCommand" then
    
        port$ = parameters["port"]
        serialString$ = parameters["serialString"]

        m.diagnostics.PrintDebug("sendSerialBlockCommand " + serialString$ + " to port " + port$)
        
        if type(m.serial) = "roAssociativeArray" then
            serial = m.serial[port$]
            if type(serial) = "roSerialPort" then
                serial.SendBlock(serialString$)
            endif
        endif
        
    else if command$ = "sendSerialByteCommand" then
    
        port$ = parameters["port"]
        byteValue$ = parameters["byteValue"]

        m.diagnostics.PrintDebug("sendSerialByteCommand " + byteValue$ + " to port " + port$)
        
        if type(m.serial) = "roAssociativeArray" then
            serial = m.serial[port$]
            if type(serial) = "roSerialPort" then
                serial.SendByte(int(val(byteValue$)))
            endif
        endif
        
    else if command$ = "sendSerialBytesCommand" then
    
        port$ = parameters["port"]
        byteValues$ = parameters["byteValues"]

        m.diagnostics.PrintDebug("sendSerialBytesCommand " + byteValues$ + " to port " + port$)
        
        if type(m.serial) = "roAssociativeArray" then
            serial = m.serial[port$]
            if type(serial) = "roSerialPort" then
                byteString$ = StripLeadingSpaces(byteValues$)
                if len(byteString$) > 0 then
                    commaPosition = -1
    	            while commaPosition <> 0	
    		            commaPosition = instr(1, byteString$, ",")
			            if commaPosition = 0 then
				            serial.SendByte(val(byteString$))					
			            else 
				            serial.SendByte(val(left(byteString$, commaPosition - 1)))
			            endif
			            byteString$ = mid(byteString$, commaPosition+1)
    	            end while
                endif
            endif
        endif

    else if command$ = "sendUDPCommand" then

        udpString$ = parameters["udpString"]
        m.diagnostics.PrintDebug("Send UDP command " + udpString$)
        m.udpSender.Send(udpString$)
    
    else if command$ = "sendIRRemote" then
    
        irRemoteOut$ = parameters["irRemoteOut"]
        
        m.diagnostics.PrintDebug("Send IR Remote " + irRemoteOut$)
        
        if type(m.remote) <> "roIRRemote" then
            m.remote = CreateObject("roIRRemote")
            m.remote.SetPort(m.msgPort)
        endif
        
        if type(m.remote) = "roIRRemote" then
            irRemoteOut% = int(val(irRemoteOut$))
            m.remote.Send("NEC", irRemoteOut%)
        endif

    else if command$ = "sendBP900Output" then
    
        buttonNumber$ = parameters["buttonNumber"]
        action$ = parameters["action"]
        
        if type(m.bp900Output) = "roControlPort" then
        
            m.diagnostics.PrintDebug("Apply action " + action$ + " to BP900 button " + buttonNumber$)
        
            buttonNumber% = int(val(buttonNumber$))
            
            if action$ = "on" then
                m.bp900Output.SetOutputState(buttonNumber%, 1)            
            else if action$ = "off" then
                m.bp900Output.SetOutputState(buttonNumber%, 0)            
            else if action$ = "fastBlink" then
                m.bp900Output.SetOutputValue(buttonNumber%, &h038e38c)
            else if action$ = "mediumBlink" then
                m.bp900Output.SetOutputValue(buttonNumber%, &h03f03e0)
            else if action$ = "slowBlink" then
                m.bp900Output.SetOutputValue(buttonNumber%, &h03ff800)
            endif
            
        endif        
        
    else if command$ = "synchronize" then

        synchronizeKeyword$ = parameters["synchronizeKeyword"]
        m.diagnostics.PrintDebug("Send synchronize command " + synchronizeKeyword$)
    
	    m.udpSender.Send("pre-" + synchronizeKeyword$)
	    
        preloadRequired = true
        if type(zone.preloadState) = "roAssociativeArray" then
            if zone.preloadedStateName$ = zone.preloadState.name$ then
                preloadRequired = false
            endif
        endif                                    

        ' currently only support preload / synchronizing with images and videos
        if preloadRequired then
            m.PreloadItem(zone, zone.preloadState)
	    endif
	    
	    sleep(300)
	    
	    ' m.udpSender.Send("ply-" + synchronizeKeyword$)
	    
	    if type(m.udpReceiver) = "roDatagramReceiver" then
	        udpReceiverExists = true
	        m.udpReceiver = 0
	    else
	        udpReceiverExists = false
	    endif
	    
	    m.WaitForSyncResponse(synchronizeKeyword$)
	    
	    if udpReceiverExists then
	        m.udpReceiver = CreateObject("roDatagramReceiver", m.udpReceivePort)
            m.udpReceiver.SetPort(m.msgPort)
	    endif

    else if command$ = "internalSynchronize" then

        synchronizeKeyword$ = parameters["synchronizeKeyword"]
        m.diagnostics.PrintDebug("Send internalSynchronize command " + synchronizeKeyword$)
    
        ' first send the internal sync preload command. all zones waiting on this command will start their preload
        internalSyncPreload = CreateObject("roAssociativeArray")
        internalSyncPreload["EventType"] = "InternalSyncPreloadEvent"
        internalSyncPreload["EventParameter"] = synchronizeKeyword$
        m.msgPort.PostMessage(internalSyncPreload)
    
        ' next, send a command that the master will receive. once received, it will tell the slaves to play
        internalSyncMasterPreload = CreateObject("roAssociativeArray")
        internalSyncMasterPreload["EventType"] = "InternalSyncMasterPreloadEvent"
        internalSyncMasterPreload["EventParameter"] = synchronizeKeyword$
        m.msgPort.PostMessage(internalSyncMasterPreload)

        preloadRequired = true
        if type(zone.preloadState) = "roAssociativeArray" then
            if zone.preloadedStateName$ = zone.preloadState.name$ then
                preloadRequired = false
            endif
        else
            preloadRequired = false
        endif                                    

        ' preload the item associated with the master
        if preloadRequired then
            m.PreloadItem(zone, zone.preloadState)
        endif
        
    else if command$ = "pauseVideoCommand" then
    
        m.diagnostics.PrintDebug("Pause video")
        m.PauseVideo()

    else if command$ = "resumeVideoCommand" then
    
        m.diagnostics.PrintDebug("Resume video")
        m.ResumeVideo()

    else if command$ = "enablePowerSaveMode" then
    
        m.diagnostics.PrintDebug("Enable Power Save Mode")
        m.SetPowerSaveMode(true)

    else if command$ = "disablePowerSaveMode" then
    
        m.diagnostics.PrintDebug("Disable Power Save Mode")
        m.SetPowerSaveMode(false)

    else if command$ = "pause" then
    
        pauseTime$ = parameters["pauseTime"]
        m.diagnostics.PrintDebug("Pause for " + pauseTime$ + " milliseconds")
        pauseTime% = int(val(pauseTime$))
        sleep(pauseTime%)

    endif
    
End Sub


Sub MapDigitalOutput(player As Object, parameters As Object)

    digitalOutput$ = parameters["mapping"]
    m.diagnostics.PrintDebug("Map digital output " + digitalOutput$)
    if type(player) <> "roInvalid" then
        player.MapDigitalOutput(int(val(digitalOutput$)))
    endif

End Sub


Sub SetAudioOutput(player As Object, parameters As Object)

    audioOutput$ = parameters["output"]
    m.diagnostics.PrintDebug("Set audio output " + audioOutput$)
    if type(player) <> "roInvalid" then
        player.SetAudioOutput(int(val(audioOutput$)))
    endif

End Sub


Sub SetAudioMode(player As Object, parameters As Object)

    audioMode$ = parameters["mode"]
    if audioMode$ <> "" then
        m.diagnostics.PrintDebug("Set audio mode " + audioMode$)
        if type(player) <> "roInvalid" then
            player.SetAudioMode(int(val(audioMode$)))
        endif
    endif
        
End Sub


Sub MapStereoOutput(player As Object, parameters As Object)  
    
    mapping$ = parameters["mapping"]
    m.diagnostics.PrintDebug("Map stereo output " + mapping$)
    
    mapping% = 0
    if mapping$ = "onboard-audio2" then
        mapping% = 1
    else if mapping$ = "onboard-audio3" then
        mapping% = 2
    endif
    
'        if m.sysInfo.expanderPresent then
'            mapping% = mapping% + 3
'        endif

    if type(player) <> "roInvalid" then
        player.MapStereoOutput(mapping%)
    endif

End Sub


Sub SetSpdifMute(player As Object, parameters As Object)  
    
    muteOn$ = parameters["mute"]
    m.diagnostics.PrintDebug("Set SPDIF Mute " + muteOn$)
    if type(player) <> "roInvalid" then
        player.SetSpdifMute(int(val(muteOn$)))
    endif

End Sub


Sub SetAnalogMute(channelVolumes As Object, player As Object, parameters As Object)  
    
    muteOn$ = parameters["mute"]
    m.diagnostics.PrintDebug("Set Analog Mute " + muteOn$)
    if type(player) <> "roInvalid" and type(channelVolumes) = "roArray" then
        muteOn% = int(val(muteOn$))
        if muteOn% = 0 then
            for i% = 0 to 5
                mask% = 2 ^ i%
                player.SetChannelVolumes(mask%, channelVolumes[i%])
            next
        else
            player.SetChannelVolumes(63, 0)
        endif
    endif

End Sub


Sub WaitForSyncResponse(parameter$ As String)

    udpReceiver = CreateObject("roDatagramReceiver", m.udpReceivePort)
    msgPort = CreateObject("roMessagePort")
    udpReceiver.SetPort(msgPort)

    m.udpSender.Send("ply-" + parameter$)
    
    while true
        msg = wait(0, msgPort)
        if type(msg) = "roDatagramEvent" then
            udpReceiver = 0
            if m.debuggingSynchro then m.udpSender.Send("master received ply loopback")
            return
        endif
    endwhile
    
End Sub


Sub LaunchMedia(zone As Object, state As Object)

    if type(state.imageItem) = "roAssociativeArray" then
    
        m.DisplayImage(zone, state, state.imageItem)
    
    else if type(state.videoItem) = "roAssociativeArray" then
    
        loopMode% = 1
        if type(state.videoEndEvent) = "roAssociativeArray" or type(state.synchronizeEvents) = "roAssociativeArray" or type(state.internalSynchronizeEvents) = "roAssociativeArray" then loopMode% = 0
        m.LaunchVideo(zone, state, state.videoItem, loopMode%)
    
    else if type(state.backgroundImageItem) = "roAssociativeArray" then
    
        m.LaunchBackgroundImage(zone, state.backgroundImageItem)
    
    else if type(state.liveVideoItem) = "roAssociativeArray" then
    
        m.LaunchLiveVideo(zone, state, state.liveVideoItem)
    
    else if type(state.signChannelItem) = "roAssociativeArray" then
    
        loopMode = true
        if type(state.signChannelEndEvent) = "roAssociativeArray" then loopMode = false
        m.LaunchSignChannel(zone, state, state.signChannelItem, loopMode)
    
    else if type(state.audioItem) = "roAssociativeArray" then
    
        loopMode% = 1
        if type(state.audioEndEvent) = "roAssociativeArray" then loopMode% = 0
        m.LaunchAudio(zone, state, state.audioItem, loopMode%)
        
    endif
        
    if type(state.mstimeoutEvent) = "roAssociativeArray" then
    
        timer = CreateObject("roTimer")
        timer.SetPort(m.msgPort)
        systemTime = CreateObject("roSystemTime")
        newTimeout = systemTime.GetLocalDateTime()
        newTimeout.AddMilliseconds(state.mstimeoutValue%)
        timer.SetDateTime(newTimeout)
        timer.Start()
        state.mstimeoutTimer = timer

    endif
    
End Sub


Sub LaunchBackgroundImage(zone as Object, backgroundImageItem As Object)

    file$ = backgroundImageItem.fileName$
    
    filePath$ = GetPoolFilePath(m.syncPoolFiles, file$)

    ok = zone.videoPlayer.PlayStaticImage(filePath$)

    if ok = 0 then
        m.diagnostics.PrintDebug("Error displaying file in LaunchBackgroundImage: " + file$ + ", " + filePath$)
    else
        m.diagnostics.PrintDebug("LaunchBackgroundImage: display file " + file$)
    endif   

End Sub


Sub SetVideoTimeCodeEvents(zone As Object, state As Object)

    zone.videoPlayer.ClearEvents()

    if type(state.videoTimeCodeEvents) = "roAssociativeArray" then
        for each eventNum in state.videoTimeCodeEvents
            m.AddVideoTimeCodeEvent(zone, state.videoTimeCodeEvents[eventNum].timeInMS%, int(val(eventNum)))                    
        next
    endif
    
End Sub


Sub LaunchVideo(zone As Object, state As Object, videoItem As Object, loopMode% As Integer)

    if m.debuggingSynchro then m.udpSender.Send("enter LaunchVideo")

    m.SetVideoTimeCodeEvents(zone, state)
    
    if type(zone.audioPlayer) = "roAudioPlayer" then
        zone.audioPlayer.Stop()
    endif
    
    m.StopSignChannelInZone(zone)
    
    file$ = videoItem.fileName$

    zone.videoPlayer.SetLoopMode(loopMode%)

    if type(videoItem.volume%) = "roInt" then
        zone.videoPlayer.SetVolume(videoItem.volume%)
    endif

    ' determine whether or not a preload has been performed
    preloaded = false
    if type(zone.preloadState) = "roAssociativeArray" then
        if zone.preloadedStateName$ = state.name$
            preloaded = true
        endif
    endif

    if preloaded then
    
        if m.debuggingSynchro then m.udpSender.Send("launch videoPlayer.Play")
        ok = zone.videoPlayer.Play()
        if ok=0 print "Error Playing (supposedly) Preloaded File: " ; file$ : stop
        zone.preloadState = 0
        zone.preloadedStateName$ = ""
        
        m.diagnostics.PrintDebug("LaunchVideo: play preloaded file " + file$ + ", loopMode = " + str(loopMode%))
        
    else   

        filePath$ = GetPoolFilePath(m.syncPoolFiles, file$)
        if m.debuggingSynchro then m.udpSender.Send("launch videoPlayer.PlayFile")
        ok = zone.videoPlayer.PlayFile(filePath$)
        if ok = 0 then
        
            m.diagnostics.PrintDebug("Error playing file in LaunchVideo: " + file$ + ", " + filePath$)
        
            videoPlaybackFailure = CreateObject("roAssociativeArray")
            videoPlaybackFailure["EventType"] = "VideoPlaybackFailureEvent"
            videoPlaybackFailure["NextState"] = ""
            videoPlaybackFailure["Zone"] = zone

            m.diagnostics.PrintDebug("VideoPlaybackFailure")
            
            if type(zone.activeState) = "roAssociativeArray" then
            
                if type(zone.activeState.videoEndEvent) = "roAssociativeArray" then
                
                    videoPlaybackFailure["NextState"] = m.GetNextStateName(zone, zone.activeState.videoEndEvent)

                    zone.lastEvent$ = "VideoEndEvent"

                endif
                
            endif

            m.msgPort.PostMessage(videoPlaybackFailure)
        
        else
            m.diagnostics.PrintDebug("LaunchVideo: play file " + file$)
        endif   
        
    endif
    
    if type(zone.imagePlayer) = "roImageWidget" then
    
        zone.imagePlayer.StopDisplay()
        m.SetTouchRegions(zone, state)
    
    endif

' playback logging
    m.LogPlayStart("video", file$, zone)

End Sub


Sub LaunchLiveVideo(zone As Object, state As Object, liveVideoItem As Object)

    m.diagnostics.PrintDebug("Enter LaunchLiveVideo")

    m.StopSignChannelInZone(zone)
    
    zone.videoPlayer.Stop()
    if type(zone.imagePlayer) = "roImageWidget" then
        zone.imagePlayer.StopDisplay()
    endif

    if type(liveVideoItem.volume%) = "roInt" then
        zone.videoPlayer.SetVolume(liveVideoItem.volume%)
    endif

    zone.videoPlayer.PlayEx(zone.videoInput)

    m.SetTouchRegions(zone, state)

' playback logging
    m.LogPlayStart("liveVideo", "", zone)

End Sub


Sub LaunchSignChannel(zone As Object, state As Object, signChannelItem As Object, loopMode As Object)

    m.diagnostics.PrintDebug("Enter LaunchSignChannel")
    
    m.StopSignChannelInZone(zone)
    
    if type(zone.videoPlayer) = "roVideoPlayer" then
        videoPlayer = zone.videoPlayer
    else
        videoPlayer = invalid
    endif
    
    if type(zone.audioPlayer) = "roAudioPlayer" then
        zone.audioPlayer.Stop()
    endif
    
    m.SetTouchRegions(zone, state)

' start SignChannel - current implementation does a complete initialization here. may need
' refactoring if this is really used as a playlist item (multiple in a presentation)
    if type(m.feedVideoDownloader) <> "roAssociativeArray" then
        m.feedVideoDownloader = newFeedVideoDownloader(m.msgPort)
    endif
    
    if type(m.feedPlayer) <> "roAssociativeArray" then
		if type(signChannelItem.slideTransition%) = "roInt" then
			slideTransition% = signChannelItem.slideTransition%
		else
			slideTransition% = 0
		endif
        m.feedPlayer = newFeedPlayer(zone.imagePlayer, videoPlayer, m.msgPort, m.feedVideoDownloader, loopMode, signChannelItem.rssURL$, slideTransition%, m.diagnostics)
        m.feedPlayer.FetchFeed()
        zone.feedPlayer = m.feedPlayer
    else
        m.feedPlayer.IsEnabled = true
    endif

End Sub



Sub LaunchAudio(zone As Object, state As Object, audioItem As Object, loopMode% As Integer)

    m.StopSignChannelInZone(zone)
    
    file$ = audioItem.fileName$

    m.diagnostics.PrintDebug("Enter LaunchAudio: " + file$ + ", loopMode = " + str(loopMode%))
    
    if type(zone.videoPlayer) = "roVideoPlayer" then
        zone.videoPlayer.StopClear()
    endif
    
    zone.audioPlayer.SetLoopMode(loopMode%)
    
    if type(audioItem.volume%) = "roInt" then
        zone.audioPlayer.SetVolume(audioItem.volume%)
    endif

    filePath$ = GetPoolFilePath(m.syncPoolFiles, file$)
    ok = zone.audioPlayer.PlayFile(filePath$)
    if ok=0 print "Error Playing File: " ; file$ : stop

    m.SetTouchRegions(zone, state)

' playback logging
    m.LogPlayStart("audio", file$, zone)

End Sub


Sub DisplayImage(zone As Object, state As Object, imageItem As Object)

    m.StopSignChannelInZone(zone)
    
    file$ = imageItem.fileName$

'    m.diagnostics.PrintDebug("Enter DisplayImage: " + file$)
    
    zone.imagePlayer.SetDefaultTransition(imageItem.slideTransition%)

    ' determine whether or not a preload has been performed
    preloaded = false
    if type(zone.preloadState) = "roAssociativeArray" then
        if zone.preloadedStateName$ = state.name$
            preloaded = true
            m.diagnostics.PrintDebug("Use preloaded file " + file$ + " in DisplayImage: ")
        endif
    endif

    filePath$ = GetPoolFilePath(m.syncPoolFiles, file$)
    
    if not preloaded then
        ok = zone.imagePlayer.PreloadFile(filePath$)
        if ok = 0 then
            m.diagnostics.PrintDebug("Error preloading file in DisplayImage: " + file$ + ", " + filePath$)
        else
            m.diagnostics.PrintDebug("Preloaded file in DisplayImage: " + file$)
        endif   
    endif
    
    m.diagnostics.PrintDebug("DisplayPreload in DisplayImage: " + file$)
    ' m.udpSender.Send("call imagePlayer.DisplayPreload() on " + file$)
    ok = zone.imagePlayer.DisplayPreload()
    if ok = 0 then
        m.diagnostics.PrintDebug("Error in DisplayPreload in DisplayImage: " + file$ + ", " + filePath$)
    endif
    
    zone.preloadState = 0
    zone.preloadedStateName$ = ""

    if type(zone.videoPlayer) = "roVideoPlayer" then
        zone.videoPlayer.StopClear()
    endif
    
    m.SetTouchRegions(zone, state)

' playback logging
    m.LogPlayStart("image", file$, zone)

End Sub


Sub LogPlayStart(itemType$ As String, fileName$ As String, zone As Object)

    if zone.playbackActive then
        zone.playbackEndTime$ = m.systemTime.GetLocalDateTime().GetString()
        m.LogPlay(zone)
    endif
    
    zone.playbackActive = true
    zone.playbackStartTime$ = m.systemTime.GetLocalDateTime().GetString()
    zone.playbackItemType$ = itemType$
    zone.playbackFileName$ = fileName$
    
End Sub


Sub LogPlayEnd(zone As Object)

    if not zone.playbackActive then return
    
    zone.playbackEndTime$ = m.systemTime.GetLocalDateTime().GetString()
    
    m.LogPlay(zone)
    
    zone.playbackActive = false
    
End Sub


Sub LogPlay(zone As Object)

    m.logging.WritePlaybackLogEntry(zone.name$, zone.playbackStartTime$, zone.playbackEndTime$, zone.playbackItemType$, zone.playbackFileName$)
        
End Sub


Sub LogEvent(eventType$ As String, eventData$ As String, zone As Object)

    m.logging.WriteEventLogEntry(zone.Name$, eventType$, eventData$)
    
End Sub


Sub LogDiagnostic(eventId$ As String, eventData$ As String)

    m.logging.WriteDiagnosticLogEntry(eventId$, eventData$)

End Sub


Sub SetTouchRegions(zone As Object, state As Object)

REM Display the cursor if there is a touch event active in this state
REM If there is only one touch event we assume that it is to exit and don't display the cursor

    if type(m.touchScreen) <> "roTouchScreen" return

    ' clear out all regions in the active zone
    
    stateTable = zone.activePlaylist.stateTable
    
    if type(zone.enabledRegions) = "roList" then    
		for each eventNum in zone.enabledRegions
			m.touchScreen.EnableRegion(eventNum, false)
		next
		zone.enabledRegions.Clear()
	endif
	
    numTouchRegions% = 0
    if type(state.touchEvents) = "roAssociativeArray" then
        for each eventNum in state.touchEvents
            m.touchScreen.EnableRegion(val(eventNum), true)
            zone.enabledRegions.AddTail(val(eventNum))
            numTouchRegions% = numTouchRegions% + 1
        next
    endif

    if m.sign.touchCursorDisplayMode$ = "auto" then
    
        if numTouchRegions% > 1 then

            m.touchScreen.EnableCursor(true)
            m.diagnostics.PrintDebug("Cursor enabled")
            
        else

            m.touchScreen.EnableCursor(false)
            m.diagnostics.PrintDebug("Cursor disabled")
            
        endif

    else if m.sign.touchCursorDisplayMode$ = "display" and numTouchRegions% > 0 then
    
        m.touchScreen.EnableCursor(true)
        m.diagnostics.PrintDebug("Cursor enabled")

    else
    
        m.touchScreen.EnableCursor(false)
        m.diagnostics.PrintDebug("Cursor disabled")

    endif
    
    return
    
End Sub


Sub AddVideoTimeCodeEvent(zone As Object, timeInMS% As Integer, eventNum% As Integer)

    if type(zone.videoPlayer) = "roVideoPlayer" then
        zone.videoPlayer.AddEvent(eventNum%, timeInMS%)
    endif
        
End Sub


Sub InitializeTouchScreen(zone As Object)

    if type(m.touchScreen) <> "roTouchScreen" then
        m.touchScreen = CreateObject("roTouchScreen")
        m.touchScreen.SetPort(m.msgPort)
        REM Puts up a cursor if a mouse is attached
        REM The cursor must be a 32 x 32 BMP
        REM The x,y position is the “hot spot” point
        m.touchScreen.SetCursorBitmap("cursor.bmp", 16, 16)
        
        videoMode = CreateObject("roVideoMode")
        resX = videoMode.GetResX()
        resY = videoMode.GetResY()
        videoMode = 0
        
        m.touchScreen.SetResolution(resX, resY)
        m.touchScreen.SetCursorPosition(resX / 2, resY / 2)
    endif

	if type(zone.enabledRegions) <> "roList" then
		zone.enabledRegions = CreateObject("roList")
	endif
	
    return
    
End Sub


Sub AddRectangularTouchRegion(zone As Object, touchEvent As Object, eventNum% As Integer)

    x% = touchEvent.x% + zone.x%
    y% = touchEvent.y% + zone.y%
    m.touchScreen.AddRectangleRegion(x%, y%, touchEvent.width%, touchEvent.height%, eventNum%)
    m.touchScreen.EnableRegion(eventNum%, false)

    if type(touchEvent.onBitmap$) = "roString" then
        onBitmapPath$ = GetPoolFilePath(m.syncPoolFiles, touchEvent.onBitmap$)
        offBitmapPath$ = GetPoolFilePath(m.syncPoolFiles, touchEvent.offBitmap$)
     	m.touchScreen.EnableRollOver(eventNum%, onBitmapPath$, offBitmapPath$, true, zone.imagePlayer)
		m.touchScreen.SetRollOverOrigin(eventNum%, touchEvent.rolloverXOrigin%, touchEvent.rolloverYOrigin%)
    endif
       
End Sub


Sub AddCircularTouchRegion()

End Sub


Sub AddRectangularRolloverTouchRegion()

End Sub


Sub AddCircularRolloverTouchRegion()

End Sub


Sub StopSignChannelInZone(zone As Object)

' only stop SignChannel if the zone provided matches the zone that SignChannel is using
    if type(m.feedPlayer) <> "roAssociativeArray" then return
    
    if type(zone.feedPlayer) <> "roAssociativeArray" then return
    
    m.StopSignChannel()
    
End Sub


Sub StopSignChannel()

' Two possible ways to stop the SignChannel.

' The first is to clear out the feedPlayer object. When restarting SignChannel,
' the feedPlayer object needs to be entirely recreated - the result is about a 7 second delay.
' The second is to continue grabbing images and downloading them. Then when it is reenabled, it just starts playing immediately
' The second approach might violate how SignChannel wants us to work - that is, when SignChannel is enabled, perhaps it should always start from the beginning

' Use first approach at the moment. Second approach would require more work - current implementation does not immediately display the image - it goes through the various timeouts.

' clear feedPlayer URL
    
'    m.feedPlayer.IsEnabled = false

' remove circular reference
    if type(m.feedPlayer) = "roAssociativeArray" then
		m.feedPlayer.cacheManager.Prune(m.feedPlayer.feed)
        if type(m.feedPlayer.feed) = "roAssociativeArray" then
            m.feedPlayer.feed.feedPlayer = invalid
        endif
        m.feedPlayer = invalid
        print "$$$$ destroy feedPlayer object"
    endif
    
' clear any preload states
    for each zone in m.sign.zones
        zone.preloadState = 0
        zone.preloadedStateName$ = ""
        zone.feedPlayer = 0
    next

End Sub


Sub DisplayNetworkTimers()

    if type(m.networking.networkTimerDownload) = "roAssociativeArray" then
        print "m.networking.networkTimerDownload.timer.GetIdentity() = ";m.networking.networkTimerDownload.timer.GetIdentity()
    endif
    
    if type(m.sign.rssTimerDownload) = "roAssociativeArray" then
        print "m.sign.rssTimerDownload.timer.GetIdentity() = ";m.sign.rssTimerDownload.timer.GetIdentity()
    endif

End Sub


Function HandleNetworkTimerEvent(msg As Object) As Integer

    if type(m.networking.networkTimerDownload) = "roAssociativeArray" and m.networking.networkTimerDownload.timer.GetIdentity() = msg.GetSourceIdentity() then

        m.diagnostics.PrintDebug("### Download")
        m.networking.StartSync("download")

        newTimeout = m.systemTime.GetLocalDateTime()

        if m.networking.networkTimerDownload.timerType = "TIMERTYPEPERIODIC" then
            newTimeout.AddSeconds(m.networking.networkTimerDownload.timerInterval)
        elseif m.networking.networkTimerDownload.timerType = "TIMERTYPERANGE" then
            newTimeout.AddSeconds(24*60*60) ' range timers are always once per day
        endif
        
        m.networking.networkTimerDownload.timer.SetDateTime(newTimeout)
        m.networking.networkTimerDownload.timer.Start()

        return 0
    
    else if type(m.sign) = "roAssociativeArray" then
        if type(m.sign.rssTimerDownload) = "roAssociativeArray" and type(m.sign.rssTimerDownload.timer) = "roTimer" then
            if m.sign.rssTimerDownload.timer.GetIdentity() = msg.GetSourceIdentity() then

                m.diagnostics.PrintTimestamp()
                m.diagnostics.PrintDebug("### Update RSS")

        ' walk through the zone list, and for each zone that has an RSS feed, update it.
                for each zone in m.sign.zones
                    playlist = zone.playlist
                    if type(playlist) = "roAssociativeArray" then
                        stateTable = playlist.stateTable
                        for each stateName in stateTable
                            state = stateTable[stateName]
                            if type(state.rssItem) = "roAssociativeArray" then
                                state.rssItem.rssURLXfer.GetToFile("tmp:/rss.xml")
                                state.rssItem.rssParser.ParseFile("tmp:/rss.xml")
                                UpdateRSSStrings(zone, state.rssItem)
                            endif
                        next
                    endif
                next

                newTimeout = m.systemTime.GetLocalDateTime()

                newTimeout.AddSeconds(m.sign.rssTimerDownload.timerInterval)
                
                m.sign.rssTimerDownload.timer.SetDateTime(newTimeout)
                m.sign.rssTimerDownload.timer.Start()
                
                return 0
                
            endif
        endif
    endif
    
End Function


Sub PrepareForRestart()

    m.diagnostics.PrintDebug("### Prepare for restart")

    ' clear touch screen object
    m.touchScreen = invalid

    if type(m.sign) = "roAssociativeArray" then
    
        ' clear all players
        for each zone in m.sign.zones
        
            if type(zone.videoPlayer) = "roVideoPlayer" then
                zone.videoPlayer = 0
            endif
            
            if type(zone.audioPlayer) = "roAudioPlayer" then
                zone.audioPlayer = 0
            endif
            
            if type(zone.imagePlayer) = "roImageWidget" then
                zone.imagePlayer = 0
            endif
            
            if type(zone.widget) = "roClockWidget" or type(zone.widget) = "roTextWidget" then
                zone.widget = 0
            endif
            
        next
        
        m.StopSignChannel()
    
    endif
    
End Sub


Sub StopPlayback()

    m.diagnostics.PrintDebug("### Stopping playback")

' SEAMLESS
'    if type(m.networking.networkTimerDownload) = "roAssociativeArray" then
'        if type(m.networking.networkTimerDownload.timer) = "roTimer" then
'            m.networking.networkTimerDownload.timer.Stop()
'        endif
'    endif
    
    if type(m.sign) = "roAssociativeArray" then
    
        ' stop and destroy all timers
        for each zone in m.sign.zones
            if type(zone.zoneTimer) = "roTimer" then
                zone.zoneTimer.Stop()
                zone.zoneTimer = 0
            endif
        next

        ' stop all players
        for each zone in m.sign.zones
        
            if type(zone.videoPlayer) = "roVideoPlayer" then
                zone.videoPlayer.StopClear()
            endif
            
            if type(zone.audioPlayer) = "roAudioPlayer" then
                zone.audioPlayer.Stop()
            endif
            
            if type(zone.imagePlayer) = "roImageWidget" then
                zone.imagePlayer.StopDisplay()
            endif
            
            if type(zone.widget) = "roClockWidget" or type(zone.widget) = "roTextWidget" then
                zone.widget.Hide()
            endif
            
        next
        
        m.StopSignChannel()    
    
    endif
    
End Sub


Sub ResetDownloadTimerToDoRetry()

    newTimeout = m.systemTime.GetLocalDateTime()

	if m.networking.numRetries% >= m.networking.maxRetries% then
	    m.diagnostics.PrintDebug("### reset_download_timer_to_do_retry - max retries attempted - wait until next regularly scheduled download.")
		m.networking.numRetries% = 0
        if m.networking.networkTimerDownload.timerType = "TIMERTYPEPERIODIC" then
            numSeconds% = m.networking.networkTimerDownload.timerInterval
        elseif m.networking.networkTimerDownload.timerType = "TIMERTYPERANGE" then
            numSeconds% = 24*60*60
        endif
	else
		m.networking.numRetries% = m.networking.numRetries% + 1
		newTimeout = m.systemTime.GetLocalDateTime()
		numSeconds% = m.networking.retryInterval% * m.networking.numRetries%
	    m.diagnostics.PrintDebug("### reset_download_timer_to_do_retry - wait " + stri(numSeconds%) + " seconds.")
	endif
	    
	newTimeout.AddSeconds(numSeconds%)
    m.networking.networkTimerDownload.timer.SetDateTime(newTimeout)
    m.networking.networkTimerDownload.timer.Start()

	m.networking.syncPool = invalid
	
End Sub


Function GetViewModeValue(viewModeSpec$ As String) As Integer

    viewMode% = 2
    
    if viewModeSpec$ = "Scale to Fill" then
        viewMode% = 0
    else if viewModeSpec$ = "Letterboxed and Centered" then
        viewMode% = 1
    endif
    
    return viewMode%
    
End Function


Function GetAudioOutputValue(audioOutputSpec$ As String) As Integer

    audioOutput% = 0
    
    if audioOutputSpec$ = "USB Audio" then
        audioOutput% = 1
    else if audioOutputSpec$ = "SPDIF Audio with Stereo PCM (HDMI Audio)" then
        audioOutput% = 2
    else if audioOutputSpec$ = "SPDIF Audio, Raw Multichannel" then
        audioOutput% = 3
    else if audioOutputSpec$ = "Analog Audio with Raw Multichannel on SPDIF" then
        audioOutput% = 4
    endif
    
    return audioOutput%
    
End Function


Function GetAudioModeValue(audioModeSpec$ As String) As Integer

    audioMode% = 0
    
    if audioModeSpec$ = "Multichannel Mixed Down to Stereo" then
        audioMode% = 1
    else if audioModeSpec$ = "No Audio" then
        audioMode% = 2
    endif
    
    return audioMode%
    
End Function


Function GetAudioMappingValue(audioMappingSpec$ As String) As Integer

    audioMapping% = 0
    
    if audioMappingSpec$ = "Audio-2" then
        audioMapping% = 1
    else if audioMappingSpec$ = "Audio-3" then
        audioMapping% = 2
    endif
    
    return audioMapping%
    
End Function


Function GetImageModeValue(imageModeSpec$ As String) As Integer

    imageMode% = 1
    
    if imageModeSpec$ = "Center Image" then
        imageMode% = 0
    else if imageModeSpec$ = "Scale to Fill and Crop" then
        imageMode% = 2
    else if imageModeSpec$ = "Scale to Fill" then
        imageMode% = 3
    endif
    
    return imageMode%
    
End Function


Function GetSlideTransitionValue(slideTransitionSpec$ As String) As Integer

    slideTransition% = 0
    
    if slideTransitionSpec$ = "Image wipe from top" then
        slideTransition% = 1
    else if slideTransitionSpec$ = "Image wipe from bottom" then
        slideTransition% = 2
    else if slideTransitionSpec$ = "Image wipe from left" then
        slideTransition% = 3
    else if slideTransitionSpec$ = "Image wipe from right" then
        slideTransition% = 4
    else if slideTransitionSpec$ = "Explode from center" then
        slideTransition% = 5
    else if slideTransitionSpec$ = "Explode from top left" then
        slideTransition% = 6
    else if slideTransitionSpec$ = "Explode from top right" then
        slideTransition% = 7
    else if slideTransitionSpec$ = "Explode from bottom left" then
        slideTransition% = 8
    else if slideTransitionSpec$ = "Explode from bottom right" then
        slideTransition% = 9
    else if slideTransitionSpec$ = "Venetian blinds - vertical" then
        slideTransition% = 10
    else if slideTransitionSpec$ = "Venetian blinds - horizontal" then
        slideTransition% = 11
    else if slideTransitionSpec$ = "Comb effect - vertical" then
        slideTransition% = 12
    else if slideTransitionSpec$ = "Comb effect - horizontal" then
        slideTransition% = 13
    else if slideTransitionSpec$ = "Fade to background color" then
        slideTransition% = 14
    else if slideTransitionSpec$ = "Fade to new image" then
        slideTransition% = 15
    else if slideTransitionSpec$ = "Slide from top" then
        slideTransition% = 16
    else if slideTransitionSpec$ = "Slide from bottom" then
        slideTransition% = 17
    else if slideTransitionSpec$ = "Slide from left" then
        slideTransition% = 18
    else if slideTransitionSpec$ = "Slide from right" then
        slideTransition% = 19
    endif
    
    return slideTransition%
    
End Function


Function CopyDateTime(dateTimeIn As Object) As Object

    dateTimeOut = CreateObject("roDateTime")
    dateTimeOut.SetYear(dateTimeIn.GetYear())
    dateTimeOut.SetMonth(dateTimeIn.GetMonth())
    dateTimeOut.SetDay(dateTimeIn.GetDay())
    dateTimeOut.SetHour(dateTimeIn.GetHour())
    dateTimeOut.SetMinute(dateTimeIn.GetMinute())
    dateTimeOut.SetSecond(dateTimeIn.GetSecond())
    dateTimeOut.SetMillisecond(dateTimeIn.GetMillisecond())
    
    return dateTimeOut
    
End Function


Function StripLeadingSpaces(inputString$ As String) As String

    while true
        if left(inputString$, 1)<>" " then return inputString$
        inputString$ = right(inputString$, len(inputString$)-1)
    endwhile

    return inputString$

End Function


Function ConvertToRemoteCommand(remoteCommand% As Integer) As String

	Dim remoteCommands[19]
	remoteCommands[0]="WEST"
	remoteCommands[1]="EAST"
	remoteCommands[2]="NORTH"
	remoteCommands[3]="SOUTH"
	remoteCommands[4]="SEL"
	remoteCommands[5]="EXIT"
	remoteCommands[6]="PWR"
	remoteCommands[7]="MENU"
	remoteCommands[8]="SEARCH"
	remoteCommands[9]="PLAY"
	remoteCommands[10]="FF"
	remoteCommands[11]="RW"
	remoteCommands[12]="PAUSE"
	remoteCommands[13]="ADD"
	remoteCommands[14]="SHUFFLE"
	remoteCommands[15]="REPEAT"
	remoteCommands[16]="VOLUP"
	remoteCommands[17]="VOLDWN"
	remoteCommands[18]="BRIGHT"

    if remoteCommand% < 0 or remoteCommand% > 18 return ""
    
    return remoteCommands[remoteCommand%]
    
End Function


REM *******************************************************
REM *******************************************************
REM ***************                    ********************
REM *************** DIAGNOSTICS OBJECT ********************
REM ***************                    ********************
REM *******************************************************
REM *******************************************************

REM
REM construct a new diagnostics BrightScript object
REM
Function newDiagnostics(sysFlags As Object) As Object

    diagnostics = CreateObject("roAssociativeArray")
    
    diagnostics.debug = sysFlags.debugOn
    diagnostics.logging = sysFlags.loggingOn
    diagnostics.autorunVersion$ = "unknown"
    diagnostics.customAutorunVersion$ = "unknown"
    diagnostics.firmwareVersion$ = "unknown"
    diagnostics.systemTime = CreateObject("roSystemTime")
    
    diagnostics.PrintDebug = PrintDebug
    diagnostics.PrintTimestamp = PrintTimestamp
    diagnostics.OpenLogFile = OpenLogFile
    diagnostics.CloseLogFile = CloseLogFile
    diagnostics.FlushOldLogFile = FlushOldLogFile
    diagnostics.WriteToLog = WriteToLog
    diagnostics.SetSystemInfo = SetSystemInfo
    diagnostics.RotateLogFiles = RotateLogFiles
    
    diagnostics.OpenLogFile()

    return diagnostics

End Function


Sub OpenLogFile()

    m.logFile = 0

    if not m.logging then return

    m.logFileLength = 0

    m.logFile = CreateObject("roReadFile", "log.txt")
    if type(m.logFile) = "roReadFile" then
        m.logFile.SeekToEnd()
        m.logFileLength = m.logFile.CurrentPosition()
        m.logFile = 0
    endif

    m.logFile = CreateObject("roAppendFile", "log.txt")
    if type(m.logFile)<>"roAppendFile" then
        print "unable to open log.txt"
        stop
    endif

    return

End Sub


Sub CloseLogFile()

    if not m.logging then return

    m.logFile.Flush()
    m.logFile = 0

    return

End Sub


Sub FlushOldLogFile()

    if not m.logging then return

    if m.logFileLength > 1000000 then
        print  "### - Rotate Log Files - ###"
        m.logFile.SendLine("### - Rotate Log Files - ###")
    endif

    m.logFile.Flush()

    if m.logFileLength > 1000000 then
        m.RotateLogFiles()
    endif

    return

End Sub


Sub WriteToLog(eventType$ As String, eventData$ As String, eventResponseCode$ As String, accountName$ As String)

    if not m.logging then return

    if m.debug then print "### write_event"

    ' write out the following info
    '   Timestamp, Device ID, Account Name, Event Type, Event Data, Response Code, Software Version, Firmware Version
    eventDateTime = m.systemTime.GetLocalDateTime()
    eventDataStr$ = eventDateTime + " " + accountName$ + " " + eventType$ + " " + eventData$ + " " + eventResponseCode$ + " autorun.brs " + m.autorunVersion$ + " " + m.firmwareVersion$
    if m.debug then print "eventDataStr$ = ";eventDataStr$
    m.logFile.SendLine(eventDataStr$)

    m.logFileLength = m.logFileLength + len(eventDataStr$) + 14

    m.FlushOldLogFile()

    return

End Sub


Sub RotateLogFiles()

    log3 = CreateObject("roReadFile", "log_3.txt")
    if type(log3)="roReadFile" then
        log3 = 0
		DeleteFile("log_3.txt")
    endif

    log2 = CreateObject("roReadFile", "log_2.txt")
    if type(log2)="roReadFile" then
        log2 = 0
        MoveFile("log_2.txt", "log_3.txt")
    endif

    m.logFile = 0
    MoveFile("log.txt", "log_2.txt")

    m.OpenLogFile()

    return

End Sub


Sub PrintDebug(debugStr$ As String)

    if type(m) <> "roAssociativeArray" then stop
    
    if m.debug then 

        print debugStr$

        if not m.logging then return

        m.logFile.SendLine(debugStr$)
        m.logFileLength = m.logFileLength + len(debugStr$) + 1
        m.FlushOldLogFile()

    endif

    return

End Sub


Sub PrintTimestamp()

    eventDateTime = m.systemTime.GetLocalDateTime()
    if m.debug then print eventDateTime.GetString()
    if not m.logging then return
    m.logFile.SendLine(eventDateTime)
    m.FlushOldLogFile()

    return

End Sub


Sub SetSystemInfo(sysInfo As Object, diagnosticCodes As Object)

    m.autorunVersion$ = sysInfo.autorunVersion$
    m.customAutorunVersion$ = sysInfo.customAutorunVersion$
    m.firmwareVersion$ = sysInfo.deviceFWVersion$
    m.deviceUniqueID$ = sysInfo.deviceUniqueID$
    m.deviceModel$ = sysInfo.deviceModel$
    m.deviceFamily$ = sysInfo.deviceFamily$
    m.modelSupportsWifi = sysInfo.modelSupportsWifi
    
    m.diagnosticCodes = diagnosticCodes
    
    return

End Sub


REM *******************************************************
REM *******************************************************
REM ***************                    ********************
REM *************** LOGGING OBJECT     ********************
REM ***************                    ********************
REM *******************************************************
REM *******************************************************

REM
REM construct a new logging BrightScript object
REM
Function newLogging() As Object

    logging = CreateObject("roAssociativeArray")
    
    logging.msgPort = m.msgPort
    logging.systemTime = m.systemTime
    logging.diagnostics = m.diagnostics
    
    logging.SetSystemInfo = SetSystemInfo

    logging.registrySection = CreateObject("roRegistrySection", "networking")
    if type(logging.registrySection) <> "roRegistrySection" then print "Error: Unable to create roRegistrySection":stop
    logging.CreateLogFile = CreateLogFile
    logging.MoveExpiredCurrentLog = MoveExpiredCurrentLog
    logging.MoveCurrentLog = MoveCurrentLog
    logging.InitializeLogging = InitializeLogging
    logging.ReinitializeLogging = ReinitializeLogging
    logging.InitializeCutoverTimer = InitializeCutoverTimer
    logging.WritePlaybackLogEntry = WritePlaybackLogEntry
    logging.WriteEventLogEntry = WriteEventLogEntry
    logging.WriteDiagnosticLogEntry = WriteDiagnosticLogEntry
    logging.PushLogFile = PushLogFile
    logging.CutoverLogFile = CutoverLogFile
    logging.HandleTimerEvent = HandleLoggingTimerEvent
    logging.PushLogFilesOnBoot = PushLogFilesOnBoot
    logging.OpenOrCreateCurrentLog = OpenOrCreateCurrentLog
    logging.DeleteExpiredFiles = DeleteExpiredFiles
    logging.DeleteOlderFiles = DeleteOlderFiles
    logging.FlushLogFile = FlushLogFile
    logging.logFile = invalid
    
    logging.uploadLogFolder = "logs"
    logging.uploadLogArchiveFolder = "archivedLogs"
    logging.uploadLogFailedFolder = "failedLogs"
    logging.logFileUpload = invalid
    
    logging.playbackLoggingEnabled = false
    logging.eventLoggingEnabled = false
    logging.diagnosticLoggingEnabled = false
    logging.uploadLogFilesAtBoot = false
    logging.uploadLogFilesAtSpecificTime = false
    logging.uploadLogFilesTime% = 0
    
    CreateDirectory("logs")
    CreateDirectory("currentLog")
    CreateDirectory("archivedLogs")
    CreateDirectory("failedLogs")

    return logging
    
End Function


Function CreateLogFile(logDateKey$ As String, logCounterKey$ As String) As Object

    dtLocal = m.systemTime.GetLocalDateTime()
    year$ = Right(stri(dtLocal.GetYear()), 2)
    month$ = StripLeadingSpaces(stri(dtLocal.GetMonth()))
    if len(month$) = 1 then
        month$ = "0" + month$
    endif
    day$ = StripLeadingSpaces(stri(dtLocal.GetDay()))
    if len(day$) = 1 then
        day$ = "0" + day$
    endif
    dateString$ = year$ + month$ + day$
    
    logDate$ = m.registrySection.Read(logDateKey$)
    logCounter$ = m.registrySection.Read(logCounterKey$)
    
    if logDate$ = "" or logCounter$ = "" then
        logCounter$ = "000"
    else if logDate$ <> dateString$ then
        logCounter$ = "000"
    endif
    logDate$ = dateString$
    
    localFileName$ = "BrightSign" + "Log." + m.deviceUniqueID$ + "-" + dateString$ + logCounter$ + ".log"

' at a later date, move this code to the point where the file has been uploaded successfully
    m.registrySection.Write(logDateKey$, logDate$)
    
    logCounter% = val(logCounter$)
    logCounter% = logCounter% + 1
    if logCounter% > 999 then
        logCounter% = 0
    endif
    logCounter$ = StripLeadingSpaces(stri(logCounter%))
    if len(logCounter$) = 1 then
        logCounter$ = "00" + logCounter$
    else if len(logCounter$) = 2 then
        logCounter$ = "0" + logCounter$
    endif
    m.registrySection.Write(logCounterKey$, logCounter$)
 
    fileName$ = "currentLog/" + localFileName$
    logFile = CreateObject("roCreateFile", fileName$)
    m.diagnostics.PrintDebug("Create new log file " + localFileName$)
    
    t$ = chr(9)
    
    ' version
    header$ = "BrightSignLogVersion"+t$+"1"
    logFile.SendLine(header$)
    
    ' serial number
    header$ = "SerialNumber"+t$+m.deviceUniqueID$
    logFile.SendLine(header$)
    
    ' group id
    if type(m.networking.currentSync) = "roSyncSpec" then
        header$ = "Account"+t$+m.networking.currentSync.LookupMetadata("server", "account")
        logFile.SendLine(header$)
        header$ = "Group"+t$+m.networking.currentSync.LookupMetadata("server", "group")
        logFile.SendLine(header$)
    endif
    
    ' timezone
    header$ = "Timezone"+t$+m.systemTime.GetTimeZone()
    logFile.SendLine(header$)

    ' timestamp of log creation
    header$ = "LogCreationTime"+t$+m.systemTime.GetLocalDateTime().GetString()
    logFile.SendLine(header$)
    
    ' ip address
    nc = CreateObject("roNetworkConfiguration", 0)
    if type(nc) = "roNetworkConfiguration" then
        currentConfig = nc.GetCurrentConfig()
        nc = invalid
        ipAddress$ = currentConfig.ip4_address
        header$ = "IPAddress"+t$+ipAddress$
        logFile.SendLine(header$)
    endif
    
    ' fw version
    header$ = "FWVersion"+t$+m.firmwareVersion$
    logFile.SendLine(header$)
    
    ' script version
    header$ = "ScriptVersion"+t$+m.autorunVersion$
    logFile.SendLine(header$)

    ' custom script version
    header$ = "CustomScriptVersion"+t$+m.customAutorunVersion$
    logFile.SendLine(header$)

    ' model
    header$ = "Model"+t$+m.deviceModel$
    logFile.SendLine(header$)

    logFile.AsyncFlush()
    
    return logFile
    
End Function


Sub MoveExpiredCurrentLog()

    dtLocal = m.systemTime.GetLocalDateTime()
    currentDate$ = StripLeadingSpaces(stri(dtLocal.GetDay()))
    if len(currentDate$) = 1 then
        currentDate$ = "0" + currentDate$
    endif

    listOfPendingLogFiles = MatchFiles("/currentLog", "*")
        
    for each file in listOfPendingLogFiles
    
        logFileDate$ = left(right(file, 9), 2)
    
        if logFileDate$ <> currentDate$ then
            sourceFilePath$ = "currentLog/" + file
            destinationFilePath$ = "logs/" + file
            CopyFile(sourceFilePath$, destinationFilePath$)
            DeleteFile(sourceFilePath$)
        endif
        
    next

End Sub


Sub MoveCurrentLog()

    listOfPendingLogFiles = MatchFiles("/currentLog", "*")
    for each file in listOfPendingLogFiles
        sourceFilePath$ = "currentLog/" + file
        destinationFilePath$ = "logs/" + file
        CopyFile(sourceFilePath$, destinationFilePath$)
        DeleteFile(sourceFilePath$)
    next
    
End Sub


Sub InitializeLogging(playbackLoggingEnabled As Boolean, eventLoggingEnabled As Boolean, diagnosticLoggingEnabled As Boolean, uploadLogFilesAtBoot As Boolean, uploadLogFilesAtSpecificTime As Boolean, uploadLogFilesTime% As Integer)

    m.DeleteExpiredFiles()
    
    m.playbackLoggingEnabled = playbackLoggingEnabled
    m.eventLoggingEnabled = eventLoggingEnabled
    m.diagnosticLoggingEnabled = diagnosticLoggingEnabled
    m.uploadLogFilesAtBoot = uploadLogFilesAtBoot
    m.uploadLogFilesAtSpecificTime = uploadLogFilesAtSpecificTime
    m.uploadLogFilesTime% = uploadLogFilesTime%

    m.loggingEnabled = playbackLoggingEnabled or eventLoggingEnabled or diagnosticLoggingEnabled
    m.uploadLogsEnabled = uploadLogFilesAtBoot or uploadLogFilesAtSpecificTime  
     
    if m.uploadLogFilesAtBoot then
        m.PushLogFilesOnBoot()
    endif
    
    m.MoveExpiredCurrentLog()

    if m.loggingEnabled then m.OpenOrCreateCurrentLog()
    
    m.InitializeCutoverTimer()
    
End Sub


Sub ReinitializeLogging(playbackLoggingEnabled As Boolean, eventLoggingEnabled As Boolean, diagnosticLoggingEnabled As Boolean, uploadLogFilesAtBoot As Boolean, uploadLogFilesAtSpecificTime As Boolean, uploadLogFilesTime% As Integer)

    if playbackLoggingEnabled = m.playbackLoggingEnabled and eventLoggingEnabled = m.eventLoggingEnabled and diagnosticLoggingEnabled = m.diagnosticLoggingEnabled and uploadLogFilesAtBoot = m.uploadLogFilesAtBoot and uploadLogFilesAtSpecificTime = m.uploadLogFilesAtSpecificTime and uploadLogFilesTime% = m.uploadLogFilesTime% then return
    
    if type(m.cutoverTimer) = "roTimer" then
        m.cutoverTimer.Stop()
        m.cutoverTimer = invalid
    endif

    m.playbackLoggingEnabled = playbackLoggingEnabled
    m.eventLoggingEnabled = eventLoggingEnabled
    m.diagnosticLoggingEnabled = diagnosticLoggingEnabled
    m.uploadLogFilesAtBoot = uploadLogFilesAtBoot
    m.uploadLogFilesAtSpecificTime = uploadLogFilesAtSpecificTime
    m.uploadLogFilesTime% = uploadLogFilesTime%

    m.loggingEnabled = playbackLoggingEnabled or eventLoggingEnabled or diagnosticLoggingEnabled
    m.uploadLogsEnabled = uploadLogFilesAtBoot or uploadLogFilesAtSpecificTime  

    if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" and m.loggingEnabled then
        m.OpenOrCreateCurrentLog()
    endif
            
    m.InitializeCutoverTimer()

End Sub


Sub InitializeCutoverTimer()

    if m.uploadLogFilesAtSpecificTime then
        hour% = m.uploadLogFilesTime% / 60
        minute% = m.uploadLogFilesTime% - (hour% * 60)
    else if not m.uploadLogsEnabled then
        hour% = 0
        minute% = 0
    endif
    
    if m.uploadLogFilesAtSpecificTime or not m.uploadLogsEnabled then
        m.cutoverTimer = CreateObject("roTimer")
        m.cutoverTimer.SetPort(m.msgPort)
        m.cutoverTimer.SetDate(-1, -1, -1)
        m.cutoverTimer.SetTime(hour%, minute%, 0)
        m.cutoverTimer.Start()    
    endif
    
End Sub


Sub DeleteExpiredFiles()

    ' delete any files that are more than 10 days old
    
    dtExpired = m.systemTime.GetLocalDateTime()
    dtExpired.SubtractSeconds(60 * 60 * 24 * 10)
    
    ' look in the following folders
    '   logs
    '   failedLogs
    '   archivedLogs
    
    m.DeleteOlderFiles("logs", dtExpired)
    m.DeleteOlderFiles("failedLogs", dtExpired)
    m.DeleteOlderFiles("archivedLogs", dtExpired)
    
End Sub


Sub DeleteOlderFiles(folderName$ As String, dtExpired As Object)

    listOfLogFiles = MatchFiles("/" + folderName$, "*")
        
    for each file in listOfLogFiles
    
        year$ = "20" + left(right(file,13), 2)
        month$ = left(right(file,11), 2)
        day$ = left(right(file, 9), 2)
        dtFile = CreateObject("roDateTime")
        dtFile.SetYear(int(val(year$)))
        dtFile.SetMonth(int(val(month$)))
        dtFile.SetDay(int(val(day$)))
               
        if dtFile < dtExpired then
            fullFilePath$ = "/" + folderName$ + "/" + file
            m.diagnostics.PrintDebug("Delete expired log file " + fullFilePath$)
            DeleteFile(fullFilePath$)
        endif
        
    next

End Sub


Sub FlushLogFile()

    if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" then return

    m.logFile.Flush()

End Sub


Sub WritePlaybackLogEntry(zoneName$ As String, startTime$ As String, endTime$ As String, itemType$ As String, fileName$ As String)

    if not m.playbackLoggingEnabled then return
    
    if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" then return

    t$ = chr(9) 
    m.logFile.SendLine("L=p"+t$+"Z="+zoneName$+t$+"S="+startTime$+t$+"E="+endTime$+t$+"I="+itemType$+t$+"N="+fileName$)
    m.logFile.AsyncFlush()

End Sub


Sub WriteEventLogEntry(zoneName$ As String, eventType$ As String, eventData$ As String)

    if not m.eventLoggingEnabled then return

    if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" then return

    timestamp$ = m.systemTime.GetLocalDateTime().GetString()

    t$ = chr(9) 
    m.logFile.SendLine("L=e"+t$+"Z="+zoneName$+t$+"T="+timestamp$+t$+"E="+eventType$+t$+"D="+eventData$)
    m.logFile.AsyncFlush()

End Sub


Sub WriteDiagnosticLogEntry(eventId$ As String, eventData$ As String)

    if not m.diagnosticLoggingEnabled then return

    if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" then return

    timestamp$ = m.systemTime.GetLocalDateTime().GetString()
    
    t$ = chr(9) 
    m.logFile.SendLine("L=d"+t$+"T="+timestamp$+t$+"I="+eventId$+t$+"D="+eventData$)
    m.logFile.AsyncFlush()
    
End Sub


Sub PushLogFile(forceUpload As Boolean)

    if not m.uploadLogsEnabled and not forceUpload then return
    
' files that failed to upload in the past were moved to a different folder. move them back to the appropriate folder so that the script can attempt to upload them again
    listOfFailedLogFiles = MatchFiles("/" + m.uploadLogFailedFolder, "*.log")
    for each file in listOfFailedLogFiles
        target$ = m.uploadLogFolder + "/" + file
        fullFilePath$ = m.uploadLogFailedFolder + "/" + file
        ok = MoveFile(fullFilePath$, target$)
    next

    m.networking.UploadLogFiles()
    
End Sub


Sub PushLogFilesOnBoot()

    m.MoveCurrentLog()
    m.PushLogFile(false)

End Sub


Sub HandleLoggingTimerEvent(msg As Object)

    m.CutoverLogFile(false)

    m.cutoverTimer.Start()

End Sub


Sub CutoverLogFile(forceUpload As Boolean)

    if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" then return

    m.logFile.Flush()
    m.MoveCurrentLog()
    m.logFile = m.CreateLogFile("ld", "lc")

    m.PushLogFile(forceUpload)
    
End Sub


Sub OpenOrCreateCurrentLog()

' if there is an existing log file for today, just append to it. otherwise, create a new one to use

    listOfPendingLogFiles = MatchFiles("/currentLog", "*")
    
    for each file in listOfPendingLogFiles
        fileName$ = "currentLog/" + file
        m.logFile = CreateObject("roAppendFile", fileName$)
        if type(m.logFile) = "roAppendFile" then
            m.diagnostics.PrintDebug("Use existing log file " + file)
            return
        endif
    next

    m.logFile = m.CreateLogFile("ld", "lc")
    
End Sub


REM *******************************************************
REM *******************************************************
REM ***************                    ********************
REM *************** NETWORKING OBJECT  ********************
REM ***************                    ********************
REM *******************************************************
REM *******************************************************

REM
REM construct a new networking BrightScript object
REM
Function newNetworking(parent As Object) As Object

    networking = CreateObject("roAssociativeArray")

    networking.systemTime = m.systemTime
    networking.diagnostics = m.diagnostics
    networking.logging = m.logging
    networking.msgPort = m.msgPort

    networking.InitializeNetworkDownloads = InitializeNetworkDownloads
    networking.StartSync = StartSync
    networking.URLEvent = URLEvent
    networking.URLXferEvent = URLXferEvent
    networking.URLDeviceDownloadProgressXferEvent = URLDeviceDownloadProgressXferEvent
    networking.URLDeviceDownloadXferEvent = URLDeviceDownloadXferEvent
    networking.URLTrafficDownloadXferEvent = URLTrafficDownloadXferEvent
    networking.URLEventXferEvent = URLEventXferEvent
    networking.URLDeviceErrorXferEvent = URLDeviceErrorXferEvent
    networking.URLTTKXferEvent = URLTTKXferEvent
    networking.PoolEvent = PoolEvent
    networking.SyncPoolProgressEvent = SyncPoolProgressEvent
        
    networking.DeviceDownloadItems = CreateObject("roArray", 8, true)
    networking.AddDeviceDownloadItem = AddDeviceDownloadItem
    networking.UploadDeviceDownload = UploadDeviceDownload

    networking.DeviceDownloadProgressItems = CreateObject("roArray", 8, true)
    networking.AddDeviceDownloadProgressItem = AddDeviceDownloadProgressItem
    networking.UploadDeviceDownloadProgressItems = UploadDeviceDownloadProgressItems
    networking.UploadDeviceDownloadProgressFileList = UploadDeviceDownloadProgressFileList
    
    networking.UploadTrafficDownload = UploadTrafficDownload
    
    networking.EventItems = CreateObject("roArray", 8, true)
    networking.AddEventItem = AddEventItem
    networking.UploadEvent = UploadEvent
        
    networking.DeviceErrorItems = CreateObject("roArray", 8, true)
    networking.AddDeviceErrorItem = AddDeviceErrorItem
    networking.UploadDeviceError = UploadDeviceError
    
    networking.TTKItems = CreateObject("roArray", 8, true)
    networking.AddTTKItem = AddTTKItem
    networking.UploadTTK = UploadTTK
    
    networking.AddUploadHeaders = AddUploadHeaders
    
    networking.SetSystemInfo = SetSystemInfo
    networking.AddMiscellaneousHeaders = AddMiscellaneousHeaders
    networking.SyncOnBoot = SyncOnBoot
    networking.RebootAfterEventsSent = RebootAfterEventsSent
    networking.WaitForTransfersToComplete = WaitForTransfersToComplete
    
    networking.GetRegistryValue = GetRegistryValue
    networking.GetURL = GetURL
    
    networking.deviceDownloadProgressUploadURL = CreateObject("roUrlTransfer")
    networking.deviceDownloadUploadURL = CreateObject("roUrlTransfer")
    networking.trafficDownloadUploadURL = CreateObject("roUrlTransfer")
    networking.eventUploadURL = CreateObject("roUrlTransfer")
    networking.deviceErrorUploadURL = CreateObject("roUrlTransfer")
    networking.ttkUploadURL = CreateObject("roUrlTransfer")
    
' logging
    networking.UploadLogFiles = UploadLogFiles
    networking.UploadLogFileHandler = UploadLogFileHandler
    networking.uploadLogFileURLXfer = CreateObject("roUrlTransfer")
    networking.uploadLogFileURLXfer.SetPort(networking.msgPort)
    networking.uploadLogFileURL$ = ""
    networking.uploadLogFolder = "logs"
    networking.uploadLogArchiveFolder = "archivedLogs"
    networking.uploadLogFailedFolder = "failedLogs"
    networking.enableLogDeletion = true

    networking.parent = parent

    networking.retryInterval% = 60
    networking.numRetries% = 0
    networking.maxRetries% = 3

	networking.fileDownloadFailureCount% = 0
	networking.maxFileDownloadFailures% = 3
	
    networking.POOL_EVENT_FILE_DOWNLOADED = 1
    networking.POOL_EVENT_FILE_FAILED = -1
    networking.POOL_EVENT_ALL_DOWNLOADED = 2
    networking.POOL_EVENT_ALL_FAILED = -2

    networking.SYNC_ERROR_CANCELLED = -10001
    networking.SYNC_ERROR_CHECKSUM_MISMATCH = -10002
    networking.SYNC_ERROR_EXCEPTION = -10003
    networking.SYNC_ERROR_DISK_ERROR = -10004
    networking.SYNC_ERROR_POOL_UNSATISFIED = -10005

    networking.URL_EVENT_COMPLETE = 1

	du = CreateObject("roStorageInfo", "./")
	if type(du) = "roStorageInfo" then
        networking.cardSizeInMB = du.GetSizeInMegabytes()
        du = invalid
    else
        networking.cardSizeInMB = 0
    endif

    return networking

End Function


Function InitializeNetworkDownloads() As Boolean

    ' determine whether or not to enable proxy mode support
	m.proxy_mode = false
	
    ' if caching is enabled, set parameter indicating whether downloads are only allowed from the cache
    m.downloadOnlyIfCached = false   
    
    ' combination of proxies and wireless not yet supported
    nc = CreateObject("roNetworkConfiguration", 0)
    if type(nc) = "roNetworkConfiguration" then
        if nc.GetProxy() <> "" then
	        m.proxy_mode = true
            registrySection = CreateObject("roRegistrySection", "networking")
            if type(registrySection) <> "roRegistrySection" then print "Error: Unable to create roRegistrySection":stop
            OnlyDownloadIfCached$ = registrySection.Read("OnlyDownloadIfCached")
            if OnlyDownloadIfCached$ = "true" then m.downloadOnlyIfCached = true
            registrySection = 0
        endif
    endif
    nc = 0

    ' Load up the current sync specification so we have it ready
	m.currentSync = CreateObject("roSyncSpec")
	if type(m.currentSync) <> "roSyncSpec" then return false
	if not m.currentSync.ReadFromFile("current-sync.xml") then
	    m.diagnostics.PrintDebug("### No current sync state available")
	    return false
	endif

    m.accountName$ = m.currentSync.LookupMetadata("server", "account")

    base$ = m.currentSync.LookupMetadata("client", "base")
    nextURL = m.GetURL(base$, m.currentSync.LookupMetadata("client", "next"))
	m.errorURL = m.GetURL(base$, m.currentSync.LookupMetadata("client", "error"))
	m.eventURL = m.GetURL(base$, m.currentSync.LookupMetadata("client", "event"))
    m.deviceDownloadProgressURL = m.GetURL(base$, m.currentSync.LookupMetadata("client", "devicedownloadprogress"))
	m.deviceDownloadURL = m.GetURL(base$, m.currentSync.LookupMetadata("client", "devicedownload"))
	m.trafficDownloadURL = m.GetURL(base$, m.currentSync.LookupMetadata("client", "trafficdownload"))
	m.deviceErrorURL = m.GetURL(base$, m.currentSync.LookupMetadata("client", "deviceerror"))
    m.uploadLogFileURL$ = m.GetURL(base$, m.currentSync.LookupMetadata("client", "uploadlogs"))
    m.ttkURL$ = m.GetURL(base$, m.currentSync.LookupMetadata("client", "ttk"))
    
    if m.deviceDownloadProgressURL <> "" then
        m.deviceDownloadProgressUploadURL.SetUrl(m.deviceDownloadProgressURL)
        m.deviceDownloadProgressUploadURL.SetPort(m.msgPort)
	    m.deviceDownloadProgressUploadURL.SetMinimumTransferRate(1,300)
    endif
    
    if m.deviceDownloadURL <> "" then
        m.deviceDownloadUploadURL.SetUrl(m.deviceDownloadURL)
        m.deviceDownloadUploadURL.SetPort(m.msgPort)
	    m.deviceDownloadUploadURL.SetMinimumTransferRate(1,300)
    endif
    
    if m.trafficDownloadURL <> "" then
        m.trafficDownloadUploadURL.SetUrl(m.trafficDownloadURL)
        m.trafficDownloadUploadURL.SetPort(m.msgPort)
	    m.trafficDownloadUploadURL.SetMinimumTransferRate(1,300)
    endif
    
    if m.uploadLogFileURL$ <> "" then
        m.uploadLogFileURLXfer.SetUrl(m.uploadLogFileURL$)
	    m.uploadLogFileURLXfer.SetMinimumTransferRate(1,300)
    endif

    m.eventUploadURL.SetUrl(m.eventURL)
    m.eventUploadURL.SetPort(m.msgPort)
    m.eventUploadURL.SetMinimumTransferRate(1,300)
    
    m.deviceErrorUploadURL.SetUrl(m.deviceErrorURL)
    m.deviceErrorUploadURL.SetPort(m.msgPort)
    m.deviceErrorUploadURL.SetMinimumTransferRate(1,300)
    
    if m.ttkURL$ <> "" then
		m.ttkUploadURL.SetUrl(m.ttkURL$)
		m.ttkUploadURL.SetPort(m.msgPort)
		m.ttkUploadURL.SetMinimumTransferRate(1,300)
    endif
    
	timezone = m.currentSync.LookupMetadata("client", "timezone")
    if timezone <> "" then
        m.systemTime.SetTimeZone(timezone)
    endif

    m.diagnostics.PrintTimestamp()
    m.diagnostics.PrintDebug("### Currently active sync list suggests next URL of " + nextURL)
    m.diagnostics.PrintDebug("### Errors to " + m.errorURL)
    m.diagnostics.PrintDebug("### Events to " + m.eventURL)
    
	if nextURL = "" then stop
	if m.errorURL = "" then stop
    if m.eventURL = "" then stop
    
    m.user$ = m.currentSync.LookupMetadata("server", "user")
    m.password$ = m.currentSync.LookupMetadata("server", "password")
    if m.user$ <> "" or m.password$ <> "" then
        m.setUserAndPassword = true
    else
        m.setUserAndPassword = false
    endif

' get net connect parameters and setup timer
    timeBetweenNetConnects$ = m.currentSync.LookupMetadata("client", "timeBetweenNetConnects")
    contentDownloadsRestricted = m.currentSync.LookupMetadata("client", "contentDownloadsRestricted")
    contentDownloadRangeStart = m.currentSync.LookupMetadata("client", "contentDownloadRangeStart")
    contentDownloadRangeLength = m.currentSync.LookupMetadata("client", "contentDownloadRangeLength")

    registrySection = CreateObject("roRegistrySection", "networking")
    if type(registrySection)<>"roRegistrySection" then print "Error: Unable to create roRegistrySection":stop
	
' if the values above are not found, try to retrieve them from the registry. for simple networking, only the
' initial sync spec will include these values
    if timeBetweenNetConnects$ = "" then
        timeBetweenNetConnects$ = m.GetRegistryValue(registrySection, "tbnc", "timeBetweenNetConnects")
        if timeBetweenNetConnects$ = "" then print "Error: timeBetweenNetConnects not found in either the sync spec or the registry":stop
        contentDownloadsRestricted = m.GetRegistryValue(registrySection, "cdr", "contentDownloadsRestricted")
        if contentDownloadsRestricted = "" then print "Error: contentDownloadsRestricted not set in registry":stop
        contentDownloadRangeStart = m.GetRegistryValue(registrySection, "cdrs", "contentDownloadRangeStart")
        contentDownloadRangeLength = m.GetRegistryValue(registrySection, "cdrl", "contentDownloadRangeLength")
    endif

' check for timeBetweenNetConnects override
    tbnco$ = registrySection.Read("tbnco")
	if tbnco$ <> "" then
		timeBetweenNetConnects$	= tbnco$
	endif    
    
    m.timeBetweenNetConnects% = val(timeBetweenNetConnects$)
    m.diagnostics.PrintDebug("### Time between net connects = " + timeBetweenNetConnects$)
    m.networkTimerDownload = CreateObject("roAssociativeArray")
    m.networkTimerDownload.timerType = "TIMERTYPEPERIODIC"
    m.networkTimerDownload.timerInterval = m.timeBetweenNetConnects%
    
    newTimer = CreateObject("roTimer")
    systemTime = CreateObject("roSystemTime")
    newTimeout = systemTime.GetLocalDateTime()
    newTimeout.AddSeconds(m.timeBetweenNetConnects%)
    newTimer.SetDateTime(newTimeout)
    newTimer.SetPort(m.msgPort)
    
    m.networkTimerDownload.timer = newTimer

' get time range for when net connects can occur
    if contentDownloadsRestricted = "yes" then
        m.contentDownloadsRestricted = true
        m.contentDownloadRangeStart% = val(contentDownloadRangeStart)
        m.contentDownloadRangeLength% = val(contentDownloadRangeLength)
        m.diagnostics.PrintDebug("### Content downloads are restricted to the time from " + contentDownloadRangeStart + " for " + contentDownloadRangeLength + " minutes.")
    else
        m.diagnostics.PrintDebug("### Content downloads are unrestricted")
        m.contentDownloadsRestricted = false
    endif
    
    didSync = false
    if m.SyncOnBoot() then
        m.StartSync("download")
        didSync = true
    endif

    return true

End Function


Function GetURL(base$ As String, urlFromSyncSpec$ As String) As String

    if instr(1, urlFromSyncSpec$, ":") > 0 then
        url$ = urlFromSyncSpec$
    else if urlFromSyncSpec$ = "" then
        url$ = ""
    else
        url$ = base$ + urlFromSyncSpec$
    endif
    
    return url$
    
End Function


Function GetRegistryValue(registrySection As Object, newRegistryKey$ As String, oldRegistryKey$ As String) As String
    
    value$ = registrySection.Read(newRegistryKey$)
    if value$ = "" then
        value$ = registrySection.Read(oldRegistryKey$)
    endif
    return value$

End Function


Sub StartSync(syncType$ As String)

' Call when you want to start a sync operation

    m.diagnostics.PrintTimestamp()
    
    m.diagnostics.PrintDebug("### start_sync " + syncType$)
    
	if type(m.syncPool) = "roSyncPool" then
' This should be improved in the future to work out
' whether the sync spec we're currently satisfying
' matches the one that we're currently downloading or
' not.
        m.diagnostics.PrintDebug("### sync already active so we'll let it continue")
        m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNC_ALREADY_ACTIVE, "")
		return
	endif

    if syncType$ = "cache" then
        if not m.proxy_mode then
            m.diagnostics.PrintDebug("### cache download requested but the BrightSign is not configured to use a cache server")
            return
        endif
    endif

	m.xfer = CreateObject("roUrlTransfer")
	m.xfer.SetPort(m.msgPort)
	
	m.syncType$ = syncType$

    m.diagnostics.PrintDebug("### xfer created - identity = " + stri(m.xfer.GetIdentity()) + " ###")

' We've read in our current sync. Talk to the server to get
' the next sync. Note that we use the current-sync.xml because
' we need to tell the server what we are _currently_ running not
' what we might be running at some point in the future.

    base$ = m.currentSync.LookupMetadata("client", "base")
    nextURL = m.GetURL(base$, m.currentSync.LookupMetadata("client", "next"))
    m.diagnostics.PrintDebug("### Looking for new sync list from " + nextURL)    
    m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_CHECK_CONTENT, nextURL)

	m.xfer.SetUrl(nextURL)
    if m.setUserAndPassword then m.xfer.SetUserAndPassword(m.user$, m.password$)
    m.xfer.SetMinimumTransferRate(10,240)
	m.xfer.SetHeaders(m.currentSync.GetMetadata("server"))

' Add presentation name to header
    if type(m.parent.sign) = "roAssociativeArray" then
        m.xfer.AddHeader("presentationName", m.parent.sign.name$)
    else
        m.xfer.AddHeader("presentationName", "none")
    endif
    
' Add device unique identifier, timezone
    m.xfer.AddHeader("DeviceID", m.deviceUniqueID$)
    m.xfer.AddHeader("DeviceModel", m.deviceModel$)
    m.xfer.AddHeader("DeviceFamily", m.deviceFamily$)
    m.xfer.AddHeader("DeviceFWVersion", m.firmwareVersion$)
    m.xfer.AddHeader("DeviceSWVersion", m.autorunVersion$)
    m.xfer.AddHeader("CustomAutorunVersion", m.customAutorunVersion$)
    m.xfer.AddHeader("timezone", m.systemTime.GetTimeZone())
    m.xfer.AddHeader("localTime", m.systemTime.GetLocalDateTime().GetString())

    m.AddMiscellaneousHeaders(m.xfer)
   
    if not m.xfer.AsyncGetToObject("roSyncSpec") then stop
'	if not m.xfer.AsyncGetToFile("tmp:new-sync.xml") then stop
'	if not m.xfer.AsyncGetToFile("tmp:download-sync.xml") then stop

    return
    
End Sub


Sub AddMiscellaneousHeaders(urlXfer As Object)

' Add card size
	du = CreateObject("roStorageInfo", "./")
	urlXfer.AddHeader("storage-size", str(du.GetSizeInMegabytes()))
	urlXfer.AddHeader("storage-fs", du.GetFileSystemType())

' Add estimated realized size
	tempPool = CreateObject("roSyncPool", "pool")
	tempSpec = CreateObject("roSyncSpec")
	if tempSpec.ReadFromFile("current-sync.xml") then
	    urlXfer.AddHeader("storage-current-used", str(tempPool.EstimateRealizedSizeInMegabytes(tempSpec, "./")))
	endif
	tempPool = 0
	tempSpec = 0

    return
    
End Sub


' Call when we get a URL event
Function URLEvent(msg As Object)

    eventHandled = false
    
    m.diagnostics.PrintTimestamp()
    
    m.diagnostics.PrintDebug("### url_event")

	if type (m.xfer) = "roUrlTransfer" then 
		if msg.GetSourceIdentity() = m.xfer.GetIdentity() then
	        m.URLXferEvent(msg)
	        eventHandled = true
	    endif
	endif
	
    if type (m.deviceDownloadProgressUploadURL) = "roUrlTransfer" then
	    if msg.GetSourceIdentity() = m.deviceDownloadProgressUploadURL.GetIdentity() then
	        m.URLDeviceDownloadProgressXferEvent(msg)
	        eventHandled = true
	    endif
    endif
    
    if type (m.deviceDownloadUploadURL) = "roUrlTransfer" then
	    if msg.GetSourceIdentity() = m.deviceDownloadUploadURL.GetIdentity() then
	        m.URLDeviceDownloadXferEvent(msg)
	        eventHandled = true
	    endif
    endif
    
    if type (m.trafficDownloadUploadURL) = "roUrlTransfer" then
	    if msg.GetSourceIdentity() = m.trafficDownloadUploadURL.GetIdentity() then
	        m.URLTrafficDownloadXferEvent(msg)
	        eventHandled = true
	    endif
    endif
    
    if type (m.eventUploadURL) = "roUrlTransfer" then
	    if msg.GetSourceIdentity() = m.eventUploadURL.GetIdentity() then
	        m.URLEventXferEvent(msg)
	        eventHandled = true
	    endif
    endif
    
    if type (m.deviceErrorUploadURL) = "roUrlTransfer" then
	    if msg.GetSourceIdentity() = m.deviceErrorUploadURL.GetIdentity() then
	        m.URLDeviceErrorXferEvent(msg)
	        eventHandled = true
	    endif
    endif
    
    if type (m.ttkUploadURL) = "roUrlTransfer" then
	    if msg.GetSourceIdentity() = m.ttkUploadURL.GetIdentity() then
	        m.URLTTKXferEvent(msg)
	        eventHandled = true
	    endif
    endif
    
    if type (m.uploadLogFileURLXfer) = "roUrlTransfer" then
	    if msg.GetSourceIdentity() = m.uploadLogFileURLXfer.GetIdentity() then
	        m.UploadLogFileHandler(msg)
	        eventHandled = true
	    endif
    endif
    
    return eventHandled
    
End Function


Sub URLDeviceDownloadProgressXferEvent(msg as Object)

    m.UploadDeviceDownloadProgressItems()

End Sub


Sub URLDeviceDownloadXferEvent(msg as Object)

    m.UploadDeviceDownload()
    
End Sub


Sub URLTrafficDownloadXferEvent(msg as Object)

	if msg.GetInt() = m.URL_EVENT_COMPLETE then
	    
        m.diagnostics.PrintDebug("###  URLTrafficDownloadXferEvent: " + stri(msg.GetResponseCode()))

	    if msg.GetResponseCode() = 200 then

            return
        
        else

            ok = m.UploadTrafficDownload(m.lastContentDownloaded#)
        
        endif
        
    endif
    
End Sub


Sub URLEventXferEvent(msg as Object)

    m.UploadEvent()
    
End Sub


Sub URLDeviceErrorXferEvent(msg as Object)

    m.UploadDeviceError()
    
End Sub


Sub URLTTKXferEvent(msg as Object)

    m.UploadTTK()
    
End Sub


Sub AddDeviceDownloadProgressItem(fileItem As Object, currentFilePercentage$ As String, status$ As String)

    ' Make sure the array doesn't get too big.
    while m.DeviceDownloadProgressItems.Count() > 50
        m.DeviceDownloadProgressItems.Shift()
    end while

    deviceDownloadProgressItem = CreateObject("roAssociativeArray")
    deviceDownloadProgressItem.name$ = fileItem.name
    deviceDownloadProgressItem.hash$ = fileItem.hash
    deviceDownloadProgressItem.size$ = fileItem.size
    deviceDownloadProgressItem.currentFilePercentage$ = currentFilePercentage$
    deviceDownloadProgressItem.status$ = status$
    deviceDownloadProgressItem.utcTime$ = m.systemTime.GetUtcDateTime().GetString()

    m.DeviceDownloadProgressItems.push(deviceDownloadProgressItem)

    m.UploadDeviceDownloadProgressItems()
    
End Sub


Sub AddDeviceDownloadItem(downloadEvent$ As String, fileName$ As String, downloadData$ As String)
    
    ' Make sure the array doesn't get too big.
    while m.DeviceDownloadItems.Count() > 50
        m.DeviceDownloadItems.Shift()
    end while

    deviceDownloadItem = CreateObject("roAssociativeArray")
    deviceDownloadItem.downloadEvent$ = downloadEvent$
    deviceDownloadItem.fileName$ = fileName$
    deviceDownloadItem.downloadData$ = downloadData$
    m.DeviceDownloadItems.push(deviceDownloadItem)

    m.UploadDeviceDownload()
    
End Sub


Sub AddEventItem(eventType$ As String, eventData$ As String, eventResponseCode$ As String)
    
    ' Make sure the array doesn't get too big.
    while m.EventItems.Count() > 50
        m.EventItems.Shift()
    end while

    eventItem = CreateObject("roAssociativeArray")
    eventItem.eventType$ = eventType$
    eventItem.eventData$ = eventData$
    eventItem.eventResponseCode$ = eventResponseCode$
    m.EventItems.push(eventItem)

    m.UploadEvent()
    
End Sub


Sub AddDeviceErrorItem(event$ As String, name$ As String, failureReason$ As String, responseCode$ As String)
    
    ' Make sure the array doesn't get too big.
    while m.DeviceErrorItems.Count() > 50
        m.DeviceErrorItems.Shift()
    end while

    deviceErrorItem = CreateObject("roAssociativeArray")
    deviceErrorItem.event$ = event$
    deviceErrorItem.name$ = name$
    deviceErrorItem.failureReason$ = failureReason$
    deviceErrorItem.responseCode$ = responseCode$
    m.DeviceErrorItems.push(deviceErrorItem)

    m.UploadDeviceError()
    
End Sub


Sub AddTTKItem(event$ As String, voltage$ As String, current$ As String)

    ' Make sure the array doesn't get too big.
    while m.TTKItems.Count() > 100
        m.TTKItems.Shift()
    end while

	ttkItem = CreateObject("roAssociativeArray")
	ttkItem.event$ = event$
	ttkItem.voltage$ = voltage$
	ttkItem.current$ = current$
	m.TTKItems.push(ttkItem)
		
	if event$ = "chargingStarted" or event$ = "chargerRemoved" or event$ = "batteryFull" or event$ = "batteryEmpty" or m.TTKItems.Count() > 2 then
		m.UploadTTK()
	endif
	
End Sub


Sub UploadTTK()

    m.diagnostics.PrintDebug("### UploadTTK")

' verify that there is content to upload
    if m.TTKItems.Count() = 0 then return
    
' if a transfer is in progress, return
	if not m.ttkUploadURL.SetUrl(m.ttkURL$) then
        m.diagnostics.PrintDebug("### UploadTTK - upload already in progress")
        if m.TTKItems.Count() > 100 then
            m.diagnostics.PrintDebug("### UploadTTK - clear items from queue")
            m.TTKItems.Clear()
        endif        
		return 
	end if

' generate the XML and upload the data
    root = CreateObject("roXMLElement")
    
    root.SetName("TTKBatch")

    for each ttkItem in m.TTKItems
    
        item = root.AddBodyElement()
        item.SetName("ttk")

        elem = item.AddElement("event")
        elem.SetBody(ttkItem.event$)
    
        elem = item.AddElement("voltage")
        elem.SetBody(ttkItem.voltage$)
    
        elem = item.AddElement("current")
        elem.SetBody(ttkItem.current$)
    
    next

    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })

' prepare the upload    
    contentDisposition$ = GetContentDisposition("UploadTTK.xml")
    m.AddUploadHeaders(m.ttkUploadURL, contentDisposition$)

	ok = m.ttkUploadURL.AsyncPostFromString(xml)
    if not ok then
        m.diagnostics.PrintDebug("### UploadTTK - AsyncPostFromString failed")
    else
		' clear out DeviceErrorItems - no big deal if the post fails
		m.TTKItems.Clear()
    endif
    
End Sub


Sub UploadEvent()

    m.diagnostics.PrintDebug("### UploadEvent")

' verify that there is content to upload
    if m.EventItems.Count() = 0 then return
    
' if a transfer is in progress, return
	if not m.eventUploadURL.SetUrl(m.eventURL) then
        m.diagnostics.PrintDebug("### UploadEvent - upload already in progress")
        if m.EventItems.Count() > 50 then
            m.diagnostics.PrintDebug("### UploadEvent - clear items from queue")
            m.EventItems.Clear()
        endif        
		return 
	end if

' generate the XML and upload the data
    root = CreateObject("roXMLElement")
    
    root.SetName("EventBatch")

    for each eventItem in m.EventItems
    
        item = root.AddBodyElement()
        item.SetName("event")

        elem = item.AddElement("eventType")
        elem.SetBody(eventItem.eventType$)
    
        elem = item.AddElement("eventData")
        elem.SetBody(eventItem.eventData$)
    
        elem = item.AddElement("eventResponseCode")
        elem.SetBody(eventItem.eventResponseCode$)
    
    next

    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })

' prepare the upload    
    contentDisposition$ = GetContentDisposition("UploadEvent.xml")
    m.AddUploadHeaders(m.eventUploadURL, contentDisposition$)

	ok = m.eventUploadURL.AsyncPostFromString(xml)
    if not ok then
        m.diagnostics.PrintDebug("### UploadEvent - AsyncPostFromString failed")
	else
		' clear out EventItems - no big deal if the post fails
		m.EventItems.Clear()
	endif
        
End Sub


Sub UploadDeviceError()

    m.diagnostics.PrintDebug("### UploadDeviceError")

' verify that there is content to upload
    if m.DeviceErrorItems.Count() = 0 then return
    
' if a transfer is in progress, return
	if not m.deviceErrorUploadURL.SetUrl(m.deviceErrorURL) then
        m.diagnostics.PrintDebug("### UploadDeviceError - upload already in progress")
        if m.DeviceErrorItems.Count() > 50 then
            m.diagnostics.PrintDebug("### UploadDeviceError - clear items from queue")
            m.DeviceErrorItems.Clear()
        endif        
		return 
	end if

' generate the XML and upload the data
    root = CreateObject("roXMLElement")
    
    root.SetName("DeviceErrorBatch")

    for each deviceErrorItem in m.DeviceErrorItems
    
        item = root.AddBodyElement()
        item.SetName("deviceError")

        elem = item.AddElement("event")
        elem.SetBody(deviceErrorItem.event$)
    
        elem = item.AddElement("name")
        elem.SetBody(deviceErrorItem.name$)
    
        elem = item.AddElement("failureReason")
        elem.SetBody(deviceErrorItem.failureReason$)
    
        elem = item.AddElement("responseCode")
        elem.SetBody(deviceErrorItem.responseCode$)
    
    next

    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })

' prepare the upload    
    contentDisposition$ = GetContentDisposition("UploadDeviceError.xml")
    m.AddUploadHeaders(m.deviceErrorUploadURL, contentDisposition$)

	ok = m.deviceErrorUploadURL.AsyncPostFromString(xml)
    if not ok then
        m.diagnostics.PrintDebug("### UploadDeviceError - AsyncPostFromString failed")
    else
		' clear out DeviceErrorItems - no big deal if the post fails
		m.DeviceErrorItems.Clear()
    endif
    
End Sub


Function UploadTrafficDownload(contentDownloaded# As Double) As Boolean

    if m.trafficDownloadURL = "" then
        m.diagnostics.PrintDebug("### UploadTrafficDownload - trafficDownloadURL not set, return")
        return false
    else
        m.diagnostics.PrintDebug("### UploadTrafficDownload")
    endif
    
' if a transfer is in progress, return
	if not m.trafficDownloadUploadURL.SetUrl(m.trafficDownloadURL) then
        m.diagnostics.PrintDebug("### UploadTrafficDownload - upload already in progress")
		return false
	end if

    m.lastContentDownloaded# = contentDownloaded#
    
' convert contentDownloaded# to contentDownloaded in KBytes which can be stored in an integer
	contentDownloaded% = contentDownloaded# / 1024

	m.trafficDownloadUploadURL.SetHeaders(m.currentSync.GetMetadata("server"))
    m.trafficDownloadUploadURL.AddHeader("DeviceID", m.deviceUniqueID$)
' remove the following line after the server code is installed.    
    m.trafficDownloadUploadURL.AddHeader("contentDownloaded", StripLeadingSpaces(stri(m.contentDownloaded#)))    
    m.trafficDownloadUploadURL.AddHeader("contentDownloadedInKBytes", StripLeadingSpaces(stri(contentDownloaded%)))    
    m.trafficDownloadUploadURL.AddHeader("DeviceFWVersion", m.firmwareVersion$)
    m.trafficDownloadUploadURL.AddHeader("DeviceSWVersion", m.autorunVersion$)
    m.trafficDownloadUploadURL.AddHeader("CustomAutorunVersion", m.customAutorunVersion$)
    m.trafficDownloadUploadURL.AddHeader("timezone", m.systemTime.GetTimeZone())
    m.trafficDownloadUploadURL.AddHeader("utcTime", m.systemTime.GetUtcDateTime().GetString())

	ok = m.trafficDownloadUploadURL.AsyncPostFromString("UploadTrafficDownload")
	if not ok then
        m.diagnostics.PrintDebug("### UploadTrafficDownload - AsyncPostFromString failed")
	endif	

    return ok

End Function


Sub UploadDeviceDownloadProgressFileList()

    if m.deviceDownloadProgressURL = "" then
        m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressFileList - deviceDownloadProgressURL not set, return")
        return
    else
        m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressFileList")
    endif

' need to deal with the situation where there is already a transfer in progress
' for example, the array below shouldn't get cleared if it is getting transferred
    m.DeviceDownloadProgressItems.Clear()

' generate the XML and upload the data
    root = CreateObject("roXMLElement")
    
    root.SetName("DeviceDownloadProgress")

    for each fileToDownloadKey in m.filesToDownload
    
        fileToDownload = m.filesToDownload.Lookup(fileToDownloadKey)

        item = root.AddBodyElement()
        item.SetName("fileInSyncSpec")

        elem = item.AddElement("name")
        elem.SetBody(fileToDownload.name$)
    
        elem = item.AddElement("size")
        elem.SetBody(fileToDownload.size$)
    
        elem = item.AddElement("hash")
        elem.SetBody(fileToDownload.hash$)
    
        elem = item.AddElement("currentFilePercentage")
        elem.SetBody(fileToDownload.currentFilePercentage$)
    
        elem = item.AddElement("status")
        elem.SetBody(fileToDownload.status$)
    
        elem = item.AddElement("utcTime")
        elem.SetBody(fileToDownload.utcTime$)
    
    next

    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })

' prepare the upload    
    contentDisposition$ = GetContentDisposition("UploadDeviceDownloadProgress.xml")
    m.AddUploadHeaders(m.deviceDownloadProgressUploadURL, contentDisposition$)

	ok = m.deviceDownloadProgressUploadURL.AsyncPostFromString(xml)
    if not ok then
        m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressFileList - AsyncPostFromString failed")
    endif
    
End Sub


Sub UploadDeviceDownloadProgressItems()

    if m.deviceDownloadProgressURL = "" then
        m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems - deviceDownloadProgressURL not set, return")
        return
    else
        m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems")
    endif

' verify that there is content to upload
    if m.DeviceDownloadProgressItems.Count() = 0 then return
    
' if a transfer is in progress, return
	if not m.deviceDownloadProgressUploadURL.SetUrl(m.deviceDownloadProgressURL) then
        m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems - upload already in progress")
        if m.DeviceDownloadProgressItems.Count() > 50 then
            m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems - clear items from queue")
            m.DeviceDownloadProgressItems.Clear()
        endif        
		return 
	end if

' generate the XML and upload the data
    root = CreateObject("roXMLElement")
    
    root.SetName("DeviceDownloadProgressItems")

    for each deviceDownloadProgressItem in m.DeviceDownloadProgressItems
    
        item = root.AddBodyElement()
        item.SetName("deviceDownloadProgressItem")

        elem = item.AddElement("name")
        elem.SetBody(deviceDownloadProgressItem.name$)
    
        elem = item.AddElement("hash")
        elem.SetBody(deviceDownloadProgressItem.hash$)
    
        elem = item.AddElement("size")
        elem.SetBody(deviceDownloadProgressItem.size$)
    
        elem = item.AddElement("currentFilePercentage")
        elem.SetBody(deviceDownloadProgressItem.currentFilePercentage$)
    
        elem = item.AddElement("status")
        elem.SetBody(deviceDownloadProgressItem.status$)
        
        elem = item.AddElement("utcTime")
        elem.SetBody(deviceDownloadProgressItem.utcTime$)
        
    next

    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })

' prepare the upload    
    contentDisposition$ = GetContentDisposition("UploadDeviceDownloadProgressItems.xml")
    m.AddUploadHeaders(m.deviceDownloadProgressUploadURL, contentDisposition$)

	ok = m.deviceDownloadProgressUploadURL.AsyncPostFromString(xml)
    if not ok then
        m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems - AsyncPostFromString failed")
    else
		' clear out DeviceDownloadProgressItems - no big deal if the post fails - not necessarily true!
		m.DeviceDownloadProgressItems.Clear()
    endif
     
End Sub


Sub UploadDeviceDownload()

    if m.deviceDownloadURL = "" then
        m.diagnostics.PrintDebug("### UploadDeviceDownload - deviceDownloadURL not set, return")
        return
    else
        m.diagnostics.PrintDebug("### UploadDeviceDownload")
    endif

' verify that there is content to upload
    if m.DeviceDownloadItems.Count() = 0 then return
    
' if a transfer is in progress, return
	if not m.deviceDownloadUploadURL.SetUrl(m.deviceDownloadURL) then
        m.diagnostics.PrintDebug("### UploadDeviceDownload - upload already in progress")
        if m.DeviceDownloadItems.Count() > 50 then
            m.diagnostics.PrintDebug("### UploadDeviceDownload - clear items from queue")
            m.DeviceDownloadItems.Clear()
        endif        
		return 
	end if

' generate the XML and upload the data
    root = CreateObject("roXMLElement")
    
    root.SetName("DeviceDownloadBatch")

    for each deviceDownloadItem in m.DeviceDownloadItems
    
        item = root.AddBodyElement()
        item.SetName("deviceDownload")

        elem = item.AddElement("downloadEvent")
        elem.SetBody(deviceDownloadItem.downloadEvent$)
    
        elem = item.AddElement("fileName")
        elem.SetBody(deviceDownloadItem.fileName$)
    
        elem = item.AddElement("downloadData")
        elem.SetBody(deviceDownloadItem.downloadData$)
    
    next

    xml = root.GenXML({ indent: " ", newline: chr(10), header: true })

' prepare the upload    
    contentDisposition$ = GetContentDisposition("UploadDeviceDownload.xml")
    m.AddUploadHeaders(m.deviceDownloadUploadURL, contentDisposition$)

	ok = m.deviceDownloadUploadURL.AsyncPostFromString(xml)
    if not ok then
        m.diagnostics.PrintDebug("### UploadDeviceDownload - AsyncPostFromString failed")
    else
		' clear out DeviceDownloadItems - no big deal if the post fails
		m.DeviceDownloadItems.Clear()
    endif
    
End Sub


Sub AddUploadHeaders(url As Object, contentDisposition$)

'    url.SetHeaders({})
    url.SetHeaders(m.currentSync.GetMetadata("server"))

' Add device unique identifier, timezone
    url.AddHeader("DeviceID", m.deviceUniqueID$)
    
    url.AddHeader("DeviceModel", m.deviceModel$)
    url.AddHeader("DeviceFamily", m.deviceFamily$)
    url.AddHeader("DeviceFWVersion", m.firmwareVersion$)
    url.AddHeader("DeviceSWVersion", m.autorunVersion$)
    url.AddHeader("CustomAutorunVersion", m.customAutorunVersion$)
    
    url.AddHeader("utcTime", m.systemTime.GetUtcDateTime().GetString())

    url.AddHeader("Content-Type", "application/octet-stream")
    
    url.AddHeader("Content-Disposition", contentDisposition$)

End Sub


Function GetContentDisposition(file As String) As String

'Content-Disposition: form-data; name="file"; filename="UploadPlaylog.xml"

    contentDisposition$ = "form-data; name="
    contentDisposition$ = contentDisposition$ + chr(34)
    contentDisposition$ = contentDisposition$ + "file"
    contentDisposition$ = contentDisposition$ + chr(34)
    contentDisposition$ = contentDisposition$ + "; filename="
    contentDisposition$ = contentDisposition$ + chr(34)
    contentDisposition$ = contentDisposition$ + file
    contentDisposition$ = contentDisposition$ + chr(34)

    return contentDisposition$
    
End Function


Sub URLXferEvent(msg As Object)

	if msg.GetSourceIdentity() = m.xfer.GetIdentity() then
	    if msg.GetInt() = m.URL_EVENT_COMPLETE then
		    xferInUse = false
		    if msg.GetResponseCode() = 200 then
		    
                m.newSync = msg.GetObject()
		    			    
                m.diagnostics.PrintDebug("### Spec received from server")
                
                ' check for a forced reboot
                forceReboot$ = LCase(m.newSync.LookupMetadata("client", "forceReboot"))
                if forceReboot$ = "true" then
                    m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "FORCE REBOOT")
                    m.logging.FlushLogFile()
			        a=RebootSystem()
                    stop
                endif
                
                ' check for forced log upload
                forceLogUpload$ = LCase(m.newSync.LookupMetadata("client", "forceLogUpload"))
                if forceLogUpload$ = "true" then
                    m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "FORCE LOG UPLOAD")
                    m.logging.CutoverLogFile(true)
                endif
                
			    if m.newSync.EqualTo(m.currentSync) then
                    m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "NO")
                    m.diagnostics.PrintDebug("### Server has given us a spec that matches current-sync. Nothing more to do.")
                    m.AddDeviceDownloadItem("SyncSpecUnchanged", "", "")
				    m.newSync = invalid
					m.numRetries% = 0
				    return
			    endif
                badReadySync = CreateObject("roSyncSpec")
			    if badReadySync.ReadFromFile("bad-sync.xml") then
				    if m.newSync.EqualTo(badReadySync) then
                        m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "BAD SYNC")
                        m.diagnostics.PrintDebug("### Server has given us a spec that matches bad-sync. Nothing more to do.")
					    badReadySync = invalid
					    m.newSync = invalid
						m.numRetries% = 0
					    return
				    endif
			    endif

                m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "YES")

                m.syncPoolFiles = CreateObject("roSyncPoolFiles", "pool", m.newSync)
                listOfDownloadFiles = m.newSync.GetFileList("download")
                
                fileInPoolStatus = CreateObject("roAssociativeArray")
			    tmpSyncPool = CreateObject("roSyncPool", "pool")
			    if type(tmpSyncPool) = "roSyncPool" then
                    fileInPoolStatus = tmpSyncPool.QueryFiles(m.newSync)
                endif
                
                m.filesToDownload = CreateObject("roAssociativeArray")
                m.chargeableFiles = CreateObject("roAssociativeArray")
                
                for each downloadFile in listOfDownloadFiles
                
                    if not m.filesToDownload.DoesExist(downloadFile.hash) then
                        fileToDownload = CreateObject("roAssociativeArray")
                        fileToDownload.name$ = downloadFile.name
                        fileToDownload.size$ = downloadFile.size
                        fileToDownload.hash$ = downloadFile.hash
                        fileToDownload.utcTime$ = m.systemTime.GetUtcDateTime().GetString()
                        
                        fileToDownload.currentFilePercentage$ = ""
                        fileToDownload.status$ = ""

                        ' check to see if this file is already in the pool (and therefore doesn't need to be downloaded)
                        if fileInPoolStatus.DoesExist(downloadFile.name) then
                            fileInPool = fileInPoolStatus.Lookup(downloadFile.name)
                            if fileInPool then
                                fileToDownload.currentFilePercentage$ = "100"
                                fileToDownload.status$ = "ok"
                            endif
                        endif
                        
                        m.filesToDownload.AddReplace(downloadFile.hash, fileToDownload)
                    endif
                    
                    if type(downloadFile.chargeable) = "roString" then
                        if lcase(downloadFile.chargeable) = "yes" then
                            m.chargeableFiles[downloadFile.name] = true
                        endif
                    endif
                    
                next
                                
                m.UploadDeviceDownloadProgressFileList()
                m.AddDeviceDownloadItem("SyncSpecDownloadStarted", "", "")
                                                        
		        m.contentDownloaded# = 0#

' Retrieve logging information from the sync spec
                playbackLoggingEnabled = false
                b$ = LCase(m.newSync.LookupMetadata("client", "playbackLoggingEnabled"))
                if b$ = "yes" then playbackLoggingEnabled = true

                eventLoggingEnabled = false
                b$ = LCase(m.newSync.LookupMetadata("client", "eventLoggingEnabled"))
                if b$ = "yes" then eventLoggingEnabled = true

                diagnosticLoggingEnabled = false
                b$ = LCase(m.newSync.LookupMetadata("client", "diagnosticLoggingEnabled"))
                if b$ = "yes" then diagnosticLoggingEnabled = true

                uploadLogFilesAtBoot = false
                b$ = LCase(m.newSync.LookupMetadata("client", "uploadLogFilesAtBoot"))
                if b$ = "yes" then uploadLogFilesAtBoot = true

                uploadLogFilesAtSpecificTime = false
                b$ = LCase(m.newSync.LookupMetadata("client", "uploadLogFilesAtSpecificTime"))
                if b$ = "yes" then uploadLogFilesAtSpecificTime = true

                uploadLogFilesTime% = 0
                uploadLogFilesTime$ = m.newSync.LookupMetadata("client", "uploadLogFilesTime")
                if uploadLogFilesTime$ <> "" then uploadLogFilesTime% = int(val(uploadLogFilesTime$))
                
                m.parent.logging.ReinitializeLogging(playbackLoggingEnabled, eventLoggingEnabled, diagnosticLoggingEnabled, uploadLogFilesAtBoot, uploadLogFilesAtSpecificTime, uploadLogFilesTime%)

' Retrieve unit name, description from sync spec and write to registry if it's changed
                registrySection = CreateObject("roRegistrySection", "networking")
                if type(registrySection)<>"roRegistrySection" then print "Error: Unable to create roRegistrySection":stop

                unitNameFromRegistry$ = m.GetRegistryValue(registrySection, "un", "unitName")
                unitNamingMethodFromRegistry$ = m.GetRegistryValue(registrySection, "unm", "unitNamingMethod")
                unitDescriptionFromRegistry$ = m.GetRegistryValue(registrySection, "ud", "unitDescription")

                unitName$ = m.newSync.LookupMetadata("client", "unitName")
                unitNamingMethod$ = m.newSync.LookupMetadata("client", "unitNamingMethod")
                unitDescription$ = m.newSync.LookupMetadata("client", "unitDescription")

                if unitName$ <> unitNameFromRegistry$ then registrySection.Write("un", unitName$)
                if unitNamingMethod$ <> unitNamingMethodFromRegistry$ then registrySection.Write("unm", unitNamingMethod$)
                if unitDescription$ <> unitDescriptionFromRegistry$ then registrySection.Write("ud", unitDescription$)
                
' Retrieve latest network configuration information from sync spec
                useWireless$ = m.newSync.LookupMetadata("client", "useWireless")
                if not m.modelSupportsWifi then useWireless$ = "no"
                
                if useWireless$ = "yes" then
                    ssid$ = m.newSync.LookupMetadata("client", "ssid")
                    passphrase$ = m.newSync.LookupMetadata("client", "passphrase")
                    networkConfigurationIndex% = 1
                else
                    networkConfigurationIndex% = 0
                endif

' if useDHCP is not set, don't touch the networking configuration (likely that the BrightSign is using simple networking)
                useDHCP = m.newSync.LookupMetadata("client", "useDHCP")
                if useDHCP <> "" then
                    nc = CreateObject("roNetworkConfiguration", networkConfigurationIndex%)
                    if type(nc) = "roNetworkConfiguration" then
                        if useDHCP = "no" then
                        
                            staticIPAddress$ = m.newSync.LookupMetadata("client", "staticIPAddress")
                            subnetMask$ = m.newSync.LookupMetadata("client", "subnetMask")
                            broadcast$ = m.newSync.LookupMetadata("client", "broadcast")
                            gateway$ = m.newSync.LookupMetadata("client", "gateway")
                            dns1$ = m.newSync.LookupMetadata("client", "dns1")
                            dns2$ = m.newSync.LookupMetadata("client", "dns2")
                            dns3$ = m.newSync.LookupMetadata("client", "dns3")
                            
                            nc.SetIP4Address(staticIPAddress$)
                            nc.SetIP4Netmask(subnetMask$)
                            nc.SetIP4Broadcast(broadcast$)
                            nc.SetIP4Gateway(gateway$)
                            if dns1$ <> "" then nc.AddDNSServer(dns1$)
                            if dns2$ <> "" then nc.AddDNSServer(dns2$)
                            if dns3$ <> "" then nc.AddDNSServer(dns3$)
                            
                        else
                        
                            nc.SetDHCP()
                        
                        endif
                        
                        if useWireless$ = "yes" then
                            nc.SetWiFiESSID(ssid$)
                            nc.SetObfuscatedWifiPassphrase(passphrase$)
                        endif

                        timeServer$ = m.newSync.LookupMetadata("client", "timeServer")
                        nc.SetTimeServer(timeServer$)
                        success = nc.Apply()
                        nc = invalid
                        
                        ' if a device is setup to not use wireless, ensure that wireless is not used
                        if networkConfigurationIndex% = 0 and m.modelSupportsWifi then
                            nc = CreateObject("roNetworkConfiguration", 1)
                            if type(nc) = "roNetworkConfiguration" then
                                nc.SetDHCP()
                                nc.SetWiFiESSID("")
                                nc.SetObfuscatedWifiPassphrase("")
                                nc.Apply()
                            endif
                        endif
                        
                        if not success then
                            m.diagnostics.PrintDebug("### roNetworkConfiguration.Apply failure.")
                        else
                            ' save parameters to the registry
                            if useDHCP = "no" then
                            
                                if m.GetRegistryValue(registrySection, "dhcp", "useDHCP") <> "no" then registrySection.Write("dhcp", "no")
                                
                                staticIPAddressFromRegistry$ = m.GetRegistryValue(registrySection, "sip", "staticIPAddress")
                                subnetMaskFromRegistry$ = m.GetRegistryValue(registrySection, "sm", "subnetMask")
                                gatewayFromRegistry$ = m.GetRegistryValue(registrySection, "gw", "gateway")
                                broadcastFromRegistry$ = m.GetRegistryValue(registrySection, "bc", "broadcast")
                                dns1FromRegistry$ = m.GetRegistryValue(registrySection, "d1", "dns1")
                                dns2FromRegistry$ = m.GetRegistryValue(registrySection, "d2", "dns2")
                                dns3FromRegistry$ = m.GetRegistryValue(registrySection, "d3", "dns3")

                                if staticIPAddressFromRegistry$ <> staticIPAddress$ then registrySection.Write("sip", staticIPAddress$)
                                if subnetMaskFromRegistry$ <> subnetMask$ then registrySection.Write("sm", subnetMask$)
                                if gatewayFromRegistry$ <> gateway$ then registrySection.Write("gw", gateway$)
                                if broadcastFromRegistry$ <> broadcast$ then registrySection.Write("bc", broadcast$)
                                if dns1FromRegistry$ <> dns1$ then registrySection.Write("d1", dns1$)
                                if dns2FromRegistry$ <> dns2$ then registrySection.Write("d2", dns2$)
                                if dns3FromRegistry$ <> dns3$ then registrySection.Write("d3", dns3$)
                            
                            else
                            
                                if m.GetRegistryValue(registrySection, "dhcp", "useDHCP") <> "yes" then registrySection.Write("dhcp", "yes")
                            
                            endif
                            
                            useWirelessFromRegistry$ = m.GetRegistryValue(registrySection, "wifi", "wifi")
                            ssidFromRegistry$ = m.GetRegistryValue(registrySection, "ss", "ss")
                            passphraseFromRegistry$ = m.GetRegistryValue(registrySection, "pp", "pp")

                            if useWirelessFromRegistry$ <> useWireless$ then registrySection.Write("wifi", useWireless$)
                            if useWireless$ = "yes" then
                                if ssidFromRegistry$ <> ssid$ then registrySection.Write("ss", ssid$)
                                if passphraseFromRegistry$ <> passphrase$ then registrySection.Write("pp", passphrase$)
                            endif
                            
                            timeServerFromRegistry$ = m.GetRegistryValue(registrySection, "ts", "timeServer")
                            if timeServerFromRegistry$ <> timeServer$ then registrySection.Write("ts", timeServer$)
                        
                        endif
                    else
                	    m.diagnostics.PrintDebug("Unable to create roNetworkConfiguration - index = " + stri(networkConfigurationIndex%))
                    endif
                endif
                
' Retrieve latest net connect spec information from sync spec
                timeBetweenNetConnects$ = m.newSync.LookupMetadata("client", "timeBetweenNetConnects")
                
                if timeBetweenNetConnects$ <> "" then
                
					' check for timeBetweenNetConnects override
					tbnco$ = registrySection.Read("tbnco")
					if tbnco$ <> "" then
						timeBetweenNetConnects$	= tbnco$
					endif    
    
                    ' if the timeBetweenNetConnects has changed, restart the timer
                    newTimeBetweenNetConnects% = val(timeBetweenNetConnects$)
                    if newTimeBetweenNetConnects% <> m.timeBetweenNetConnects% then
                        m.networkTimerDownload.timer.Stop()
                        m.timeBetweenNetConnects% = newTimeBetweenNetConnects%
                        m.diagnostics.PrintDebug("### Time between net connects has changed to: " + timeBetweenNetConnects$)
                        m.networkTimerDownload.timerInterval = m.timeBetweenNetConnects%
                        systemTime = CreateObject("roSystemTime")
                        newTimeout = systemTime.GetLocalDateTime()
                        newTimeout.AddSeconds(m.timeBetweenNetConnects%)
                        m.networkTimerDownload.timer.SetDateTime(newTimeout)
                        m.networkTimerDownload.timer.Start()
                        systemTime = 0
                    else
                        m.diagnostics.PrintDebug("### Time between net connects = " + timeBetweenNetConnects$)
                    endif
                    
                endif
                
                contentDownloadsRestricted = m.newSync.LookupMetadata("client", "contentDownloadsRestricted")
                if contentDownloadsRestricted = "yes" then
                    m.contentDownloadsRestricted = true
                    contentDownloadRangeStart = m.newSync.LookupMetadata("client", "contentDownloadRangeStart")
                    m.contentDownloadRangeStart% = val(contentDownloadRangeStart)
                    contentDownloadRangeLength = m.newSync.LookupMetadata("client", "contentDownloadRangeLength")
                    m.contentDownloadRangeLength% = val(contentDownloadRangeLength)
                    m.diagnostics.PrintDebug("### Content downloads are restricted to the time from " + contentDownloadRangeStart + " for " + contentDownloadRangeLength + " minutes.")
                else
                    m.diagnostics.PrintDebug("### Content downloads are unrestricted")
                    m.contentDownloadsRestricted = false
                endif
                
' Only proceed with sync list download if the current time is within the range of allowed times for content downloads
                if m.contentDownloadsRestricted then
                    systemTime = CreateObject("roSystemTime")
                    currentTime = systemTime.GetLocalDateTime()
                    systemTime = 0
                    elapsedMinutes% = currentTime.GetHour() * 60 + currentTime.GetMinute()
                    startOfRange% = m.contentDownloadRangeStart%
                    endOfRange% = startOfRange% + m.contentDownloadRangeLength%

					notInDownloadWindow = false
					if endOfRange% <= (24 * 60) then
						if not(elapsedMinutes% >= startOfRange% and elapsedMinutes% <= endOfRange%) then
							notInDownloadWindow = true
						endif
					else
						if not(((elapsedMinutes% >= startOfRange%) and (elapsedMinutes% < (24 * 60))) or (elapsedMinutes% < (endOfRange% - (24 * 60)))) then
							notInDownloadWindow = true
						endif
					endif

					if notInDownloadWindow then
						m.diagnostics.PrintDebug("### Not in window to download content")
	                    m.AddDeviceDownloadItem("SyncSpecUnchanged", "", "")
						m.newSync = 0
						m.numRetries% = 0
						return
					endif
										                    
                endif

' Log the start of sync list download
                m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_DOWNLOAD_START, "")
                m.AddEventItem("StartSyncListDownload", m.newSync.GetName(), "")

			    m.syncPool = CreateObject("roSyncPool", "pool")
			    m.syncPool.SetPort(m.msgPort)
                if m.setUserAndPassword then m.syncPool.SetUserAndPassword(m.user$, m.password$)
                m.syncPool.SetMinimumTransferRate(1000,900)
                m.syncPool.SetHeaders(m.newSync.GetMetadata("server"))
                m.syncPool.AddHeader("DeviceID", m.deviceUniqueID$)
				m.syncPool.AddHeader("DeviceModel", m.deviceModel$)
                m.syncPool.SetFileProgressIntervalSeconds(15)
                
' clear file download failure count                
				m.fileDownloadFailureCount% = 0
				
' this error implies that the current sync list is corrupt - go back to sync list in registry and reboot - no need to retry
' do this by deleting autorun.brs and rebooting
			    if not m.syncPool.ProtectFiles(m.currentSync, 0) then ' don't allow download to delete current files
                    m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNCPOOL_PROTECT_FAILURE, m.syncPool.GetFailureReason())
                    m.logging.FlushLogFile()
                    DeleteFile("autorun.brs")
                    m.diagnostics.PrintDebug("### ProtectFiles failed: " + m.syncPool.GetFailureReason())
				    m.newSync = 0
                    m.AddDeviceErrorItem("deviceError", m.currentSync.GetName(), "ProtectFilesFailure: " + m.syncPool.GetFailureReason(), "")
		            msg = wait(10000, 0)   ' wait for either a timeout (10 seconds) or a message indicating that the post was complete
			        a=RebootSystem()
' implies dodgy XML, or something is already running. could happen if server sends down bad xml.
			    else
                    if m.proxy_mode then
				        m.syncPool.AddHeader("Roku-Cache-Request", "Yes")
				    endif
				    
			        if m.syncType$ = "download" then
			            if m.downloadOnlyIfCached then m.syncPool.AddHeader("Cache-Control", "only-if-cached")
			            if not m.syncPool.AsyncDownload(m.newSync) then
			                m.parent.ResetDownloadTimerToDoRetry()
                            m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_IMMEDIATE_FAILURE, m.syncPool.GetFailureReason())
			                m.diagnostics.PrintTimestamp()
                            m.diagnostics.PrintDebug("### AsyncDownload failed: " + m.syncPool.GetFailureReason())
                            newSyncName$ = "Unknown sync name"
                            if type(m.newSync) = "roSyncSpec" and type(m.newSync.GetName()) = "roString" then
								newSyncName$ = m.newSync.GetName()
							endif
                            m.AddDeviceErrorItem("deviceError", newSyncName$, "AsyncDownloadFailure: " + m.syncPool.GetFailureReason(), "")
				            m.newSync = 0
				        endif
				    else
				        m.syncPool.AsyncSuggestCache(m.newSync)
				    endif
			    endif
		    else if msg.GetResponseCode() = 404 then
                m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_NO_SYNCSPEC_AVAILABLE, "404")
                m.diagnostics.PrintDebug("### Server has no sync list for us: " + str(msg.GetResponseCode()))
' The server has no new sync for us. That means if we have one lined up then we should destroy it.
		    else
    ' retry - server returned something other than a 200 or 404
				m.parent.ResetDownloadTimerToDoRetry()
                m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_RETRIEVE_SYNCSPEC_FAILURE, str(msg.GetResponseCode()))
                m.diagnostics.PrintDebug("### Failed to download sync list: " + str(msg.GetResponseCode()))
                m.AddDeviceErrorItem("deviceError", "", "Failed to download sync list", str(msg.GetResponseCode()))
		    endif
	    else
	    	m.diagnostics.PrintDebug("### Progress URL event - we don't know about those.")
	    endif

	else
        m.parent.ResetDownloadTimerToDoRetry()
	    m.diagnostics.PrintDebug("### url_event from beyond this world: " + stri(msg.GetSourceIdentity()) + ", " + str(msg.GetResponseCode()) + ", " + str(msg.GetInt()))
        m.AddDeviceErrorItem("deviceError", "", "url_event from beyond this world", "")
	endif
	
	return

End Sub


Sub SyncPoolProgressEvent(msg As Object)

    m.diagnostics.PrintDebug("### File download progress " + msg.GetFileName() + str(msg.GetCurrentFilePercentage()))

    m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_FILE_DOWNLOAD_PROGRESS, msg.GetFileName() + chr(9) + str(msg.GetCurrentFilePercentage()))

    if msg.GetCurrentFilePercentage() < 100 then
        m.AddDeviceDownloadItem("File download progress", msg.GetFileName(), str(msg.GetCurrentFilePercentage()))
    endif

    fileIndex% = msg.GetFileIndex()
    fileItem = m.newSync.GetFile("download", fileIndex%)
    m.AddDeviceDownloadProgressItem(fileItem, str(msg.GetCurrentFilePercentage()), "ok")

'    fileIndex% = msg.GetFileIndex()
'    listOfDownloadFiles = m.newSync.GetFileList("download")
'    fileItem = listOfDownloadFiles[fileIndex%]
'    m.AddDeviceDownloadProgressItem(fileItem, str(msg.GetCurrentFilePercentage()), "ok")
      
End Sub


' Call when we get a sync event
Function PoolEvent(msg As Object) As Integer
    m.diagnostics.PrintTimestamp()
    m.diagnostics.PrintDebug("### pool_event")
	if type(m.syncPool) <> "roSyncPool" then
        m.diagnostics.PrintDebug("### pool_event but we have no object")
		return 0
	endif
	if msg.GetSourceIdentity() = m.syncPool.GetIdentity() then
		if (msg.GetEvent() = m.POOL_EVENT_FILE_DOWNLOADED) then
            m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_FILE_DOWNLOAD_COMPLETE, msg.GetName())
            m.diagnostics.PrintDebug("### File downloaded " + msg.GetName())
            m.AddDeviceDownloadItem("File downloaded", msg.GetName(), "")
            
            ' see if the user should be charged for this download
            if m.chargeableFiles.DoesExist(msg.GetName()) then
                filePath$ = m.syncPoolFiles.GetPoolFilePath(msg.GetName())            
                file = CreateObject("roReadFile", filePath$)
                if type(file) = "roReadFile" then
                    file.SeekToEnd()
			        m.contentDownloaded# = m.contentDownloaded# + file.CurrentPosition()
                    m.diagnostics.PrintDebug("### File size " + str(file.CurrentPosition()))
                endif
                file = invalid
            endif

		elseif (msg.GetEvent() = m.POOL_EVENT_FILE_FAILED) then
            m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_FILE_DOWNLOAD_FAILURE, msg.GetName() + chr(9) + msg.GetFailureReason())
            m.diagnostics.PrintDebug("### File failed " + msg.GetName() + ": " + msg.GetFailureReason())
            m.AddDeviceErrorItem("FileDownloadFailure", msg.GetName(), msg.GetFailureReason(), str(msg.GetResponseCode()))
            
            ' log this error to the download progress handler
            fileIndex% = msg.GetFileIndex()
            fileItem = m.newSync.GetFile("download", fileIndex%)
            if type(fileItem) = "roAssociativeArray" then
                m.AddDeviceDownloadProgressItem(fileItem, "-1", msg.GetFailureReason())
            endif

			m.fileDownloadFailureCount% = m.fileDownloadFailureCount% + 1
			if m.fileDownloadFailureCount% >= m.maxFileDownloadFailures% then
	            m.diagnostics.PrintDebug("### " + stri(m.maxFileDownloadFailures%) + " file download failures - set timer for retry.")
	            m.syncPool.AsyncCancel()
			    m.parent.ResetDownloadTimerToDoRetry()
				m.syncPool = invalid
			endif
			                      
            ' old style - get the fileItem
'            fileItem = invalid
'            listOfDownloadFiles = m.newSync.GetFileList("download")
'            for each fileItem in listOfDownloadFiles
'                if fileItem["name"] = msg.GetName()
'                    exit for
'                endif
'            next

'            if type(fileItem) = "roAssociativeArray" then
'                m.AddDeviceDownloadProgressItem(fileItem, "-1", msg.GetFailureReason())
'            endif
            
		elseif (msg.GetEvent() = m.POOL_EVENT_ALL_DOWNLOADED) then

            m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_DOWNLOAD_COMPLETE, "")		
            m.diagnostics.PrintDebug("### All files downloaded")
            m.AddDeviceDownloadItem("All files downloaded", "", "")

' capture total content downloaded
            m.diagnostics.PrintDebug("### Total content downloaded = " + str(m.contentDownloaded#))
            ok = m.UploadTrafficDownload(m.contentDownloaded#)
            if ok then m.contentDownloaded# = 0#
            
' Log the end of sync list download
            m.AddEventItem("EndSyncListDownload", m.newSync.GetName(), str(msg.GetResponseCode()))

' Clear retry count
			m.numRetries% = 0
    
' Save to current-sync.xml then do cleanup
		    if not m.newSync.WriteToFile("current-sync.xml") then stop
            timezone = m.newSync.LookupMetadata("client", "timezone")
            if timezone <> "" then
                m.systemTime.SetTimeZone(timezone)
            endif

            m.diagnostics.PrintTimestamp()
            m.diagnostics.PrintDebug("### DOWNLOAD COMPLETE")
            
            m.syncPoolFiles = CreateObject("roSyncPoolFiles", "pool", m.newSync)
            if type(m.syncPoolFiles) <> "roSyncPoolFiles" then stop
            
            autorunFile$ = GetPoolFilePath(m.syncPoolFiles, "autorun.brs")
            if autorunFile$ = "" then stop

            autoscheduleFile$ = GetPoolFilePath(m.syncPoolFiles, "autoschedule.xml")
            if autoscheduleFile$ = "" then stop
            
            resourcesFile$ = GetPoolFilePath(m.syncPoolFiles, "resources.txt")

            success = CopyFile(autorunFile$, "autorun.brs")
            if not success then stop
            
            ' see if the autorun file has changed
            autorunFileChanged = false
            currentSyncPoolFiles = CreateObject("roSyncPoolFiles", "pool", m.currentSync)
            if type(currentSyncPoolFiles) <> "roSyncPoolFiles" then stop
            oldAutorunFile$ = currentSyncPoolFiles.GetPoolFilePath("autorun.brs")
            if autorunFile$ <> oldAutorunFile$ then
                m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SCRIPT_DOWNLOAD, "")		
                autorunFileChanged = true
            endif
            
            success = CopyFile(autoscheduleFile$, "autoschedule.xml")
            if not success then stop

            if resourcesFile$ <> "" then
                success = CopyFile(resourcesFile$, "resources.txt")
                if not success then stop
            endif

            DeleteFile("bad-sync.xml")

            updateFile$ = GetPoolFilePath(m.syncPoolFiles, "update.rok")
            if updateFile$ <> "" then
                success = MoveFile(updateFile$, "update.rok")
                if not success then stop
                m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_FIRMWARE_DOWNLOAD, "")		
                m.diagnostics.PrintDebug("### FW update found - reboot")
                m.AddEventItem("DownloadComplete - FW update found", m.newSync.GetName(), "")
                m.RebootAfterEventsSent()
            endif

            if autorunFileChanged then
                m.diagnostics.PrintDebug("### new autorun.brs found - reboot")
                m.AddEventItem("DownloadComplete - new autorun.brs found", m.newSync.GetName(), "")
                m.RebootAfterEventsSent()
            endif
            
			m.newSync = invalid
			m.syncPool = invalid

            ' reset m.currentSync (could the script just do m.currentSync = m.newSync earlier?)
            
	        m.currentSync = CreateObject("roSyncSpec")
	        if type(m.currentSync) <> "roSyncSpec" then return false
	        if not m.currentSync.ReadFromFile("current-sync.xml") then stop

            m.diagnostics.PrintTimestamp()
            m.diagnostics.PrintDebug("### return from PoolEvent")
            
            return -1

		else if (msg.GetEvent() = m.POOL_EVENT_ALL_FAILED) then
		    if m.syncType$ = "download" then
			    m.parent.ResetDownloadTimerToDoRetry()
                m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_FAILURE, msg.GetFailureReason())		
                m.diagnostics.PrintDebug("### Sync failed: " + msg.GetFailureReason())
                m.AddDeviceErrorItem("POOL_EVENT_ALL_FAILED", "", msg.GetFailureReason(), str(msg.GetResponseCode()))

                ' capture total content downloaded
                m.diagnostics.PrintDebug("### Total content downloaded = " + str(m.contentDownloaded#))
                ok = m.UploadTrafficDownload(m.contentDownloaded#)
                if ok then m.contentDownloaded# = 0#
            else
                m.diagnostics.PrintDebug("### Proxy mode sync complete")
            endif
			m.newSync = 0
			m.syncPool = 0
		endif
	else
        m.diagnostics.PrintDebug("### pool_event from beyond this world: " + stri(msg.GetSourceIdentity()))
	endif
	
	return 0

End Function


Sub UploadLogFiles()

    if m.uploadLogFileURL$ = "" then return
    
' if a transfer is in progress, return
    m.diagnostics.PrintDebug("### Upload " + m.uploadLogFolder)
	if not m.uploadLogFileURLXfer.SetUrl(m.uploadLogFileURL$) then
        m.diagnostics.PrintDebug("### Upload " + m.uploadLogFolder + " - upload already in progress")
		return 
	end if

' see if there are any files to upload
    listOfLogFiles = MatchFiles("/" + m.uploadLogFolder, "*.log")
    if listOfLogFiles.Count() = 0 then return
    
' upload the first file    
    for each file in listOfLogFiles
        m.diagnostics.PrintDebug("### UploadLogFiles " + file + " to " + m.uploadLogFileURL$)
        fullFilePath = m.uploadLogFolder + "/" + file
                
        contentDisposition$ = GetContentDisposition(file)
        m.AddUploadHeaders(m.uploadLogFileURLXfer, contentDisposition$)
        ok = m.uploadLogFileURLXfer.AsyncPostFromFile(fullFilePath)
        if not ok then
	        m.diagnostics.PrintDebug("### UploadLogFiles - AsyncPostFromFile failed")
        else
			m.logFileUpload = fullFilePath
			m.logFile$ = file
			return
        endif
    next
    
End Sub


Sub UploadLogFileHandler(msg As Object)
	    	    
    if msg.GetResponseCode() = 200 then

        if type(m.logFileUpload) = "roString" then
            m.diagnostics.PrintDebug("###  UploadLogFile XferEvent - successfully uploaded " + m.logFileUpload)
            if m.enableLogDeletion then
                DeleteFile(m.logFileUpload)
            else
                target$ = m.uploadLogArchiveFolder + "/" + m.logFile$
                ok = MoveFile(m.logFileUpload, target$)
            endif
            m.logFileUpload = invalid		    
        endif
        
    else
        
        if type(m.logFileUpload) = "roString" then
            m.diagnostics.PrintDebug("### Failed to upload log file " + m.logFileUpload + ", error code = " + str(msg.GetResponseCode()))

            ' move file so that the script doesn't try to upload it again immediately
            target$ = m.uploadLogFailedFolder + "/" + m.logFile$
            ok = MoveFile(m.logFileUpload, target$)

        endif

        m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_LOGFILE_UPLOAD_FAILURE, str(msg.GetResponseCode()))
        
	endif
	
	m.UploadLogFiles()
		
End Sub


Function SendEventCommon(eventType$ As String, eventData$ As String, eventResponseCode$ As String) As String

return ""

    m.diagnostics.PrintDebug("### send_event")
	m.eventURL.SetUrl(m.currentSync.LookupMetadata("client", "event"))
	m.eventURL.SetHeaders(m.currentSync.GetMetadata("server"))
    m.eventURL.AddHeader("DeviceID", m.deviceUniqueID$)
    m.eventURL.AddHeader("DeviceModel", m.deviceModel$)
    m.eventURL.AddHeader("DeviceFamily", m.deviceFamily$)
    m.eventURL.AddHeader("DeviceFWVersion", m.firmwareVersion$)
    m.eventURL.AddHeader("DeviceSWVersion", m.autorunVersion$)
    m.eventURL.AddHeader("CustomAutorunVersion", m.customAutorunVersion$)
    
    e1$ = m.eventURL.Escape(eventType$)
    e2$ = m.eventURL.Escape(eventData$)
    e3$ = m.eventURL.Escape(eventResponseCode$)
    
    eventStr$ = "EventType=" + e1$ + "&EventData=" + e2$ + "&ResponseCode=" + e3$

    return eventStr$

End Function


Sub WaitForTransfersToComplete()

    if m.trafficDownloadURL <> "" then
        ' check to see if the trafficUpload call has been processed - if not, wait 5 seconds
        if not m.trafficDownloadUploadURL.SetUrl(m.trafficDownloadURL) then
            m.diagnostics.PrintDebug("### RebootAfterEventsSent - traffic upload still in progress - wait")
            sleep(5000)
            m.diagnostics.PrintDebug("### RebootAfterEventsSent - proceed after waiting 5 seconds for traffic upload to complete")
        else
            m.diagnostics.PrintDebug("### RebootAfterEventsSent - traffic upload must be complete - proceed")
        end if
    endif
    
    if m.deviceDownloadProgressURL <> "" then
        ' check to see if the device download progress call has been processed - if not, wait 5 seconds
	    if not m.deviceDownloadProgressUploadURL.SetUrl(m.deviceDownloadProgressURL) then
            sleep(5000)
            m.diagnostics.PrintDebug("### RebootAfterEventsSent - proceed after waiting 5 seconds for device download progress item upload to complete")
        else
            m.diagnostics.PrintDebug("### RebootAfterEventsSent - device download progress item upload must be complete - proceed")
        end if
    endif
    
    if m.deviceDownloadURL <> "" then
        ' check to see if the device download call has been processed - if not, wait 5 seconds
	    if not m.deviceDownloadUploadURL.SetUrl(m.deviceDownloadURL) then
            sleep(5000)
            m.diagnostics.PrintDebug("### RebootAfterEventsSent - proceed after waiting 5 seconds for device download upload to complete")
        else
            m.diagnostics.PrintDebug("### RebootAfterEventsSent - device download upload must be complete - proceed")
        end if
    endif

End Sub


Sub RebootAfterEventsSent()

    ' temporary
    sleep(2000)
    
    m.WaitForTransfersToComplete()
    
	m.UploadDeviceDownloadProgressItems()    
	m.UploadDeviceDownload()
    
    m.WaitForTransfersToComplete()
    
    RebootSystem()

End Sub


Function SyncOnBoot() As Boolean

	groupName = m.currentSync.LookupMetadata("server", "group")
    if groupName = "LocalTest" then return true
    
    registrySection = CreateObject("roRegistrySection", "networking")
    if type(registrySection)<>"roRegistrySection" then print "Error: Unable to create roRegistrySection":stop
    
    SyncOnBoot$ = registrySection.Read("SyncOnBoot")
    if SyncOnBoot$ = "true" then return true

    return false

End Function


Function GetColor(colorAttrs As Object) As Integer

    alpha$ = colorAttrs["a"]
    alpha% = val(alpha$)
    red$ = colorAttrs["r"]
    red% = val(red$)
    green$ = colorAttrs["g"]
    green% = val(green$)
    blue$ = colorAttrs["b"]
    blue% = val(blue$)
    
    color_spec% = (alpha%*256*256*256) + (red%*256*256) + (green%*256) + blue%
    return color_spec%

End Function


Function NewGlobalVariables() As Object

    globalVariables = CreateObject("roAssociativeArray")
    globalVariables.language$ = "eng"
    
    return globalVariables
    
End Function


REM *******************************************************
REM *******************************************************
REM ***************                    ********************
REM *************** UTILITY FUNCTIONS  ********************
REM ***************                    ********************
REM *******************************************************
REM *******************************************************

Function IsImageFile(inputString$ As String) As Boolean

    if len(inputString$) < 5 then
        return false
    endif
    
    tk$ = ucase(right(inputString$, 4))
    if tk$=".BMP" or tk$=".PNG" or tk$=".JPG" then return true
    return false
    
End Function


REM *******************************************************
REM *******************************************************
REM ***************                    ********************
REM *************** DIAGNOSTIC CODES   ********************
REM ***************                    ********************
REM *******************************************************
REM *******************************************************

Function newDiagnosticCodes() As Object

    diagnosticCodes = CreateObject("roAssociativeArray")
    
    diagnosticCodes.EVENT_STARTUP                               = "1000"
    diagnosticCodes.EVENT_SYNCSPEC_RECEIVED                     = "1001"
    diagnosticCodes.EVENT_DOWNLOAD_START                        = "1002"
    diagnosticCodes.EVENT_FILE_DOWNLOAD_START                   = "1003"
    diagnosticCodes.EVENT_FILE_DOWNLOAD_COMPLETE                = "1004"
    diagnosticCodes.EVENT_DOWNLOAD_COMPLETE                     = "1005"
    diagnosticCodes.EVENT_READ_SYNCSPEC_FAILURE                 = "1006"
    diagnosticCodes.EVENT_RETRIEVE_SYNCSPEC_FAILURE             = "1007"
    diagnosticCodes.EVENT_NO_SYNCSPEC_AVAILABLE                 = "1008"
    diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_IMMEDIATE_FAILURE   = "1009"
    diagnosticCodes.EVENT_FILE_DOWNLOAD_FAILURE                 = "1010"
    diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_FAILURE             = "1011"
    diagnosticCodes.EVENT_SYNCPOOL_PROTECT_FAILURE              = "1012"
    diagnosticCodes.EVENT_LOGFILE_UPLOAD_FAILURE                = "1013"
    diagnosticCodes.EVENT_SYNC_ALREADY_ACTIVE                   = "1014"
    diagnosticCodes.EVENT_CHECK_CONTENT                         = "1015"
    diagnosticCodes.EVENT_FILE_DOWNLOAD_PROGRESS                = "1016"
    diagnosticCodes.EVENT_FIRMWARE_DOWNLOAD                     = "1017"
    diagnosticCodes.EVENT_SCRIPT_DOWNLOAD                       = "1018"
    
    return diagnosticCodes
    
End Function


REM *******************************************************
REM *******************************************************
REM ***************                    ********************
REM *************** SIGNCHANNEL        ********************
REM ***************                    ********************
REM *******************************************************
REM *******************************************************

REM ==================================================
REM           FeedVideoDownloader Object
REM ==================================================
REM 
REM 
Function newFeedVideoDownloader(msgPort As Object) As Object

    fvd = { name:"VideoDownloader" }
    
	fvd.videoDownloader = CreateObject("roUrlTransfer")
	fvd.videoDownloader.SetPort(msgPort)
    
    fvd.HandleURLEvent = HandleVideoDownloaderURLEvent
    fvd.StartDownload = StartDownload
    fvd.asyncVideoDownloadInProgress = false
    
    return fvd
    
End Function


Function StartDownload(url As String, fname As String) As Object

	m.videoDownloader.SetUrl( url )
	m.videoDownloader.SetMinimumTransferRate(1000,10)
	rv = m.videoDownloader.AsyncGetToFile( fname)
	if rv = FALSE then
		print "AsyncGetToFile for video item download fails with " + m.videoDownloader.GetFailureReason()
	else
		m.asyncVideoDownloadInProgress = true
	end if
	
	return rv
				
End Function


Sub HandleVideoDownloaderURLEvent(event As Object, feedPlayer As Object)

	print "URL EVENT "; event
	REM print "URL EVENT CODE: ";event.GetResponseCode()
	REM print "URL EVENT STATUS: ";event.GetInt()
	REM print "URL SOURCE";event.GetSourceIdentity()

	eventId = event.GetSourceIdentity()

	if eventId = m.videoDownloader.GetIdentity() then
		print "URL - DOWNLOAD VIDEO"
		eventStatus = event.GetInt()
		if eventStatus < 0 then 
			print "Error on dowloading item"
		else if eventStatus = 2 then
			print "DOWNLOAD in PROGRESS"
		else if eventStatus = 1 then
			print "DOWNLOAD COMPLETE"
			m.asyncVideoDownloadInProgress = false
			if type(feedPlayer) = "roAssociativeArray" and feedPlayer.IsEnabled then
			    if type(feedPlayer.feed) = "roAssociativeArray" and not feedPlayer.feed.atLoadEnd then
			        feedPlayer.LoadNextItem()
			    endif
			endif
		end if
	endif
	
End Sub


REM ==================================================
REM           FeedPlayer Object
REM ==================================================
REM 
REM   - manages Feed lifetime
REM
REM   - manages Feed object lifetimes
REM   - reaches into Feed object to get FeedItems to display
REM   - reaches into FeedItems to fetch supporting feedItem assets ( bad? )
REM 
Function newFeedPlayer(imagePlayer As Object, videoPlayer As Object, msgPort As Object, videoDownloader As Object, loopMode As Object, rssURL$ As String, slideTransition% As Integer, diagnostics As Object) As Object
	fp = { name:"FeedPlayer" }
	fp.feed					= Invalid
	fp.newFeed				= Invalid
	fp.currentItem			= Invalid
	fp.currentLoadItem		= Invalid
	fp.feedHost				= GetHost()
	fp.imageCacheDir		= "/storage/sd/"
	fp.loadFailed			= FALSE
	fp.loadInProgress		= FALSE
	fp.lastLoadAttempt      = 0
	fp.cacheManager			= newCacheManager()
	fp.deviceInfo			= CreateObject("roDeviceInfo")
'	fp.videoMode			= CreateObject("roVideoMode")
'	fp.systemTime			= CreateObject("roSystemTime")
	fp.imageTimer			= CreateObject("roTimer")
	fp.imageRetryTimer		= CreateObject("roTimer")
	fp.feedTimer			= CreateObject("roTimer")

    fp.diagnostics          = diagnostics
    
    fp.reachedEnd           = FALSE
    
	fp.endCycleEvent = CreateObject("roAssociativeArray")
	fp.endCycleEvent["EventType"] = "EndCycleEvent"

	fp.loadEvent = CreateObject("roAssociativeArray")
	fp.loadEvent["EventType"] = "LoadEvent"

    fp.IsEnabled = true
    fp.InitialLoadComplete = false
  
	fp.FeedSwap = pFeedPlayer_FeedSwap

	fp.GetFeedURI = pFeedPlayer_GetFeedURI
    fp.mport = msgPort
    fp.imagePlayer = imagePlayer
	' fp.imagePlayer.SetDefaultMode(1)
	fp.imagePlayer.SetDefaultTransition(slideTransition%)

    fp.loopMode = loopMode
    fp.rssURL$ = rssURL$

	fp.videoPlayer = videoPlayer
	' fp.videoPlayer.SetPort (fp.mport)
	' fp.videoPlayer.SetViewMode(2)

    fp.videoDownloader = videoDownloader
    
	fp.feedTransfer = CreateObject("roUrlTransfer")
	fp.feedTransfer.SetMinimumTransferRate( 200, 10 )
	fp.feedTransfer.SetPort( fp.mport )

	fp.imageTimer.SetPort(fp.mport)
	fp.imageRetryTimer.SetPort(fp.mport)
	fp.feedTimer.SetPort(fp.mport)

	fp.feedCacheDir = "sd:/feed_cache/"
	CreateDirectory(fp.feedCacheDir)
	fp.feedCache = fp.feedCacheDir + "fmfeed.xml"

	fp.createTime       = fp.deviceInfo.GetDeviceUpTime()

	helper_FeedPlayerMethodInit( fp )

	REM
	REM Set this once and only once
	REM 
	fp.feedTransfer.SetUrl( fp.GetUrl() )

	return fp
End Function


Sub helper_FeedPlayerMethodInit( fp As Object ) 

	fp.HandleUrlEvent           = pFeedPlayer_HandleUrlEvent
	fp.HandleVideoEvent		    = pFeedPlayer_HandleVideoEvent
	fp.HandleScriptEvent	    = pfeedPlayer_HandleScriptEvent
	fp.PostSignChannelEndEvent	= pfeedPlayer_PostSignChannelEndEvent
	fp.FetchFeed                = pFeedPlayer_FetchFeed
	fp.FetchFeedNoAsync	        = pFeedPlayer_FetchFeedNoAsync
	fp.ParseFeed                = pFeedPlayer_ParseFeed
	fp.GetTime                  = pFeedPlayer_GetTime
	fp.DisplayNextItem          = pFeedPlayer_DisplayNextItem
	fp.LoadNextItem		        = pFeedPlayer_LoadNextItem
	fp.isImage			        = pFeedPlayer_isImage
	fp.SetFeed                  = pFeedPlayer_SetFeed
	fp.DisplayItem              = pFeedPlayer_DisplayItem
	fp.HandleTimerEvent         = pFeedPlayer_HandleTimerEvent
	fp.GetURL                   = pFeedPlayer_GetURL
	fp.LoadFeedFile             = pFeedPlayer_LoadFeedFile
	fp.PreloadItem		        = pfeedPlayer_preloadItem
	fp.systemTime               = CreateObject("roSystemTime")
	fp.AddSecsToTimer		    = pFeedPlayer_AddSecsToTimer
	fp.PopulateFeedItems	    = pFeedPlayer_PopulateFeedItems

	fp.model = fp.deviceInfo.GetModel()
	REM fp.model = "MZ210"

End Sub

Function pFeedPlayer_GetTime() As Integer
	return m.deviceInfo.GetDeviceUptime()
End Function

REM ============================================
REM pFeedPlayer_HandleUrlEvent
REM ============================================
REM
REM Member in FeedPlayer object
REM
REM Callback handler for asynchronous RSS feed
REM download
REM 
REM If download successful new feed is parsed
REM and ( pending successful parse ) swapped in
REM as current feed.
REM
REM -------------------------------------------

Function pFeedPlayer_HandleUrlEvent( event As Object ) As void

    m.diagnostics.PrintDebug("FeedPlayer - URL EVENT")

	eventId = event.GetSourceIdentity()

	if type(m.feed) = "roAssociativeArray" and eventId = m.feed.imageDownloader.GetIdentity() then
		REM This is an event from downloading an image			
        m.diagnostics.PrintDebug("URL - DOWNLOAD IMAGE")
		eventStatus = event.GetInt()
		if eventStatus < 0 then 
            m.diagnostics.PrintDebug("Error downloading item")
		else if eventStatus = 2 then
            m.diagnostics.PrintDebug("DOWNLOAD in PROGRESS")
		else if eventStatus = 1 then
            m.diagnostics.PrintDebug("DOWNLOAD COMPLETE")
			m.feed.asyncImageDownloadInProgress = false
			if not m.feed.atLoadEnd then
				m.LoadNextItem()
			end if
		end if
	else if eventId = m.feedTransfer.GetIdentity() then
        m.diagnostics.PrintDebug("URL - FEED TRANSFER")
		If event.GetInt() = 2 then
			REM load still in progress
		else 
			m.loadInProgress = FALSE
			if event.GetResponseCode() <> 200 then
				m.LoadFailed = TRUE
			else if not m.LoadFeedFile() then
				m.LoadFailed = TRUE
			else
				m.LoadFailed = FALSE
			end If
			if not m.LoadFailed then
				if not m.InitialLoadComplete then
	                m.LoadNextItem()
        	        m.DisplayNextItem()
				    m.InitialLoadComplete = true
					m.addSecsToTimer (m.feed.ttlminutes * 60, m.feedTimer)				    
				endif
			else if not m.InitialLoadComplete then
				ok = m.LoadFeedFile()
				if not ok then
					m.FetchFeed()
				else
					m.LoadFailed = false
	                m.LoadNextItem()
        	        m.DisplayNextItem()					
					m.addSecsToTimer (m.feed.ttlminutes * 60, m.feedTimer)	
				endif
			endif
		end if
	endif
 
End Function

Function pfeedPlayer_HandleScriptEvent( event As Object ) As void

	if type(event["EventType"]) = "roString" then
		if event["EventType"] = "EndCycleEvent" then
            m.diagnostics.PrintDebug("********************** End of Cycle Event ****************")
			if m.newFeed <> Invalid Then
				m.feedSwap()
			else
                m.diagnostics.PrintDebug("No new feed available")
			end if 
		else if event["EventType"] = "LoadEvent" then
            m.diagnostics.PrintDebug("********************** Load Event ***************")
			if not m.feed.atLoadEnd then 
                m.diagnostics.PrintDebug("************************* Load next item ******")
				m.LoadNextItem()
			else
                m.diagnostics.PrintDebug("*********  At load end, not loading anymore *********")
			end if
		else 
            m.diagnostics.PrintDebug("********************** Unexpected Script Event:" + event["EventType"])
		end if
	else
        m.diagnostics.PrintDebug("************************** Unexpected Event type:" + type(event["EventType"]))
	end if
End Function


Sub pfeedPlayer_PostSignChannelEndEvent()

    if not m.loopMode then
        m.diagnostics.PrintDebug("Post end of feed message")
        endOfFeed = CreateObject("roAssociativeArray")
        endOfFeed["EventType"] = "SignChannelEndEvent"
        m.mport.PostMessage(endOfFeed)
        m.IsEnabled = false
    endif
    
End Sub


Function pFeedPlayer_LoadFeedFile() As Boolean
	REM print "Loading from cache=";m.feedCache
	text = ReadAsciiFile(m.feedCache)
	feed = m.ParseFeed( text )
	If feed = Invalid then
        m.diagnostics.PrintDebug("Feed Parse Failed")
		rv = FALSE
	else
		m.SetFeed( feed )
		rv = TRUE
	End If

	return rv
End Function

Function pFeedPlayer_HandleVideoEvent( evt As Object ) As Void

    m.diagnostics.PrintDebug("*******************Video Event *************")
	
	if evt.GetInt() = 3 then
		REM Video Event 3 means the video has started, so we need to clear the image
        m.diagnostics.PrintDebug("******************  Val = 3  ****************")
' clear image plane after launching the video		
'        if type(m.imagePlayer) = "roImageWidget" then
'		    m.imagePlayer.StopDisplay()
'		endif
	else if evt.GetInt() = 8 then
		REM Video Event 8 means the video has ended, so display the next item
        m.diagnostics.PrintDebug("******************  Val = 8  ****************")

		if m.reachedEnd then
		    m.PostSignChannelEndEvent()
		endif
	
		m.CurrentItem.DurationExpired = True
		m.DisplayNextItem()
	end if

End Function

Function pFeedPlayer_HandleTimerEvent( event As Object ) As Void

	eventId = event.GetSourceIdentity()

	if eventId = m.imageTimer.GetIdentity() then
        m.diagnostics.PrintDebug("********************** Image Timer Event *********************")
    	
    	if m.reachedEnd then
	        m.PostSignChannelEndEvent()
	    endif
	
		REM In case the first image of a new feed fails to display, the CurrentItem would 
		m.CurrentItem.DurationExpired = TRUE
		m.DisplayNextItem()
	else if eventId = m.imageRetryTimer.GetIdentity() then
        m.diagnostics.PrintDebug("********************** Image Retry Timer Event ****************")
		m.DisplayNextItem()
	else if eventId = m.feedTimer.GetIdentity() then
        m.diagnostics.PrintDebug("********************** Feed Timer Event ********************")
		if not m.fetchfeed() then
            m.diagnostics.PrintDebug("Fetch Failed ********************* Retry in 30 seconds")
		    m.addSecsToTimer( 30, m.feedTimer)
		else
            m.diagnostics.PrintDebug("Fetch Succeeded ****************************************")
            m.diagnostics.PrintDebug("Uptime =" + stri(m.deviceInfo.GetDeviceUptime()))
			m.addSecsToTimer( m.feed.ttlMinutes * 60, m.feedTimer)
		End if
	else
        m.diagnostics.PrintDebug("Got Timer event with no Timer associated with it" + stri(eventId))
	end if
 
End Function

Function pFeedPlayer_GetURL() As String
    if m.rssURL$ = "" then
        return "http://" + m.feedHost + m.GetFeedUri()
    else
        return m.rssURL$
    endif
End Function

Function pFeedPlayer_FetchFeed() As Boolean
	REM print "In Fetch Feed.....*****************************************"
	REM print "***********************************************************"
    m.diagnostics.PrintDebug("FETCH Feed" + m.systemTime.GetUtcDateTime().GetString())
	m.lastLoadAttempt = m.GetTime()
	REM print "pFeedPlayer_FetchFeed lastLoadAttempt=";m.lastLoadAttempt
	m.feedTransfer.SetUrl(m.GetUrl())
    m.diagnostics.PrintDebug("Fetching Url=" + m.feedTransfer.GetUrl() + " FeedCache=" + m.feedCache)
	rv =  m.feedTransfer.AsyncGetToFile(m.feedCache)
	REM print "AsyncGetToFile returns ";rv
	if rv = FALSE then
        m.diagnostics.PrintDebug("AsyncGetToFile fails with " + m.feedTransfer.GetFailureReason())
		m.loadFailed = TRUE
		m.loadInProgress = TRUE
		REM Wait 30 seconds and try to load the feed again
		m.addSecsToTimer (30, m.feedTimer)
	else
		m.loadFailed = FALSE
		m.loadInProgress = TRUE	
   end if
   
   return rv
End Function

Function pFeedPlayer_FeedSwap() As Void
	If m.newFeed = Invalid Then
        m.diagnostics.PrintDebug("Trying to swap feeds, but new feed is Invalid!")
	else
        m.diagnostics.PrintDebug("pFeedPlayer_FeedSwap: performing feed swap")
		m.feed = m.newFeed
		m.feed.SetStartDisplayTime( m.GetTime() )
        m.diagnostics.PrintDebug("************** Swapping Feed - Resetting Timer ****************")
		m.addSecsToTimer (m.feed.ttlMinutes * 60, m.feedTimer)
		REM Delete all unused cached files
		m.cacheManager.Prune(m.feed)
		REM Start the loading of the items in the new feed
		m.LoadNextItem()
		m.newFeed = invalid
	end If
End Function


Function pFeedPlayer_FetchFeedNoAsync() As Boolean
    REM Get a feed synchronously...

	url = m.GetURL()
    m.diagnostics.PrintDebug("Setting url to " + url)
	m.lastLoadAttempt = m.GetTime()
	m.feedTransfer.SetUrl(url)
	m.feedTransfer.SetMinimumTransferRate(200, 10 )
	rv =  m.feedTransfer.GetToFile( m.feedCache )
	if rv <> 200 then
        m.diagnostics.PrintDebug("FetchFeedNoAsync roUrlTransfer.GetToFile failed:" + stri(rv))
		rv = false
	else
        m.diagnostics.PrintDebug("roUrlTransfer.GetToFile succeeded:" + stri(rv))
		rv = m.LoadFeedFile()
	end if

   return rv
End Function


Function pFeedPlayer_AddSecsToTimer(seconds as Integer, timer as object) As void
	newTimeout = m.systemTime.GetLocalDateTime()
	newTimeout.AddSeconds(seconds)
	timer.SetDateTime(newTimeout)
	timer.Start()
End Function  


REM ==========================================
REM   pFeedPlayer_DisplayNextItem
REM ==========================================
REM
REM Member in FeedPlayer object
REM
REM Advance to and display next item in feed
REM once current item has finished its duration period
REM
REM ------------------------------------------

Function pFeedPlayer_DisplayNextItem() As Boolean

	rv = false

	REM print "into Display next item*******"

	If m.feed = Invalid then
		REM print "player has no feed loaded"
	else If type(m.feed) <> "roAssociativeArray" then
        m.diagnostics.PrintDebug("Feed is not associative array")
	else If m.currentItem <> Invalid and not m.CurrentItem.DurationExpired Then
		REM print "item not expired yet...returning from display next item"
        m.diagnostics.PrintDebug("****************************************************************************")
        m.diagnostics.PrintDebug("***************************  SHould NEVER GET HERE ************************")
	else
		item = m.feed.GetNextItem()
		if item = invalid
            m.diagnostics.PrintDebug("Item invalid after GetNextItem in DisplayNextItem, feed corruption")
			return rv
		endif

        m.diagnostics.PrintDebug("DISPLAY NEXT - Item Index =" + stri(m.feed.currentItemIdx))

		if m.isImage(item) then	
			If m.DisplayItem(item ) then
				m.currentItem = item
				m.feed.numTriesToDisplay = 0
				m.CurrentItem.DurationExpired = FALSE
				duration = m.CurrentItem.duration
				m.AddSecsToTimer(duration, m.imageTimer)
				REM post a message to indicate the end of the cycle
				if m.feed.atEnd then 
'				    m.PostSignChannelEndEvent()
                    m.reachedEnd = TRUE
					m.mport.PostMessage (m.endCycleEvent)
				end if
				rv = True
			else
				REM keep track of the number of times we have tried to display this item
				m.feed.numTriesToDisplay = m.feed.numTriesToDisplay + 1 

				REM if we have tried less than 5 times to display this image, go back to prev item as to try this one again
				if m.feed.numTriesToDisplay < 5 then
					m.feed.GoToPrevItem()
                    m.diagnostics.PrintDebug("DISPLAY NEXT - retry =" + stri(m.feed.numTriesToDisplay) + "   Item Index =" + stri(m.feed.currentItemIdx))
					REM Retry in 2 seconds
					m.AddSecsToTimer(2, m.imageRetryTimer)
				else
                    m.diagnostics.PrintDebug("******************** TRIED 5 TIMES AND Still Not there  ********")
					m.currentItem = item
					m.feed.numTriesToDisplay = 0
					if m.feed.atEnd then 
				        m.PostSignChannelEndEvent()
						m.mport.PostMessage (m.endCycleEvent)
					end if
					REM Item did not load, go to the next item
					m.AddSecsToTimer(1, m.imageTimer)
				end if
			End If
		else
            m.diagnostics.PrintDebug("*****************    PLAY VIDEO  ********************")
			If m.DisplayItem(item ) then
				m.currentItem = item
                m.diagnostics.PrintDebug("******   Video playing")
				m.CurrentItem.DurationExpired = false
				REM post a message to indicate the end of the cycle
				if m.feed.atEnd then
'				    m.PostSignChannelEndEvent()
                    m.reachedEnd = TRUE
					m.mport.PostMessage (m.endCycleEvent)
				end if
				rv = TRUE
			else
				REM Item did not load, go to the next item
				if m.feed.atEnd then 
				    m.PostSignChannelEndEvent()
					m.mport.PostMessage (m.endCycleEvent)
				end if
				m.currentItem = item
				m.AddSecsToTimer(1, m.imageTimer)
			End if
		End if
	end if
	
	REM Preload the next item if it is an image so that the transition to the image is as fast as possible
	item = m.feed.GetNextItem()
	m.preloadItem ( item)
	m.feed.GoToPrevItem()

	return rv
End Function



Function pfeedPlayer_preloadItem (item As Object ) As void
	ext = item.GetExtension()
	fname = m.cacheManager.Get(item.guid + ext )
  
	If fname <> invalid then
	
        if not m.IsEnabled then
            return
        endif
	
		if m.isImage(item) then
			if not item.preloaded then 	
                if type(m.imagePlayer) = "roImageWidget" then
				    If not m.imagePlayer.PreloadFile( fname ) then
                        m.diagnostics.PrintDebug("FROM_CACHE: Preload from cache failed" + fname)
					    item.preloaded = false
				    Else
                        m.diagnostics.PrintDebug("FROM_CACHE: Preload from file succeeded: " + fname)
					    item.preloaded = true
				    End If
		        else
                    m.diagnostics.PrintDebug("Image preload failed - the current layout does not contain an image zone")
		        endif
			end if
		End if
	End if
End Function



Function pFeedPlayer_isImage( item As Object ) As Boolean
	REM Default is the item is an image
	rv = TRUE

	if item.type = "video/mpeg" then
		rv = false
	endif

   return rv
End Function


REM ====================================================
REM  pFeedPlayer_DisplayItem
REM
REM  method in FeedPlayer object
REM
REM  Display feed item on screen for <duration> seconds
REM    - fetches content for item
REM    - if content not fetched skips, and lets
REM      FeedPlayer.DisplayNextItem select next 
REM      item to display
REM ====================================================

Function pFeedPlayer_DisplayItem( item As Object ) As Boolean

	rv = false 
	If item = Invalid Then
        m.diagnostics.PrintDebug("Invalid item passed to DisplayItem")
	else
		ext = item.GetExtension()
        m.diagnostics.PrintDebug("Item GUID =" + item.guid)
		fname = m.cacheManager.Get(item.guid + ext )
  
		If fname <> invalid then
		
            if not m.IsEnabled then
                return true
            endif
		
			if m.isImage(item) then
			
                if type(m.imagePlayer) = "roImageWidget" then

				    if not item.preloaded then 	
					    If not m.imagePlayer.PreloadFile( fname ) then
                            m.diagnostics.PrintDebug("FROM_CACHE: Preload from cache failed " + fname)
						    rv = false
					    Else
                            m.diagnostics.PrintDebug("FROM_CACHE: Preload from file succeeded: " + fname)
						    item.preloaded = true
					    End If
				    end if
         
				    If item.preloaded and m.imagePlayer.DisplayPreload() then
    				
                        if type(m.videoPlayer) = "roVideoPlayer" then
                            m.videoPlayer.StopClear()
                        endif
    				
                        m.diagnostics.PrintDebug("FROM_CACHE: Display from cache succeeded " + fname)
					    item.preloaded = false
					    item.SetDisplayStart( m.GetTime() )
					    rv = True
				    Else
                        m.diagnostics.PrintDebug("FROM_CACHE: Display from file failed or not preloaded: " + fname)
				    End If
				    
				else
				
                    m.diagnostics.PrintDebug("Image display failed - the current layout does not contain an image zone")
				
				endif
				
			else
                m.diagnostics.PrintDebug("starting video play")
				REM if m.videoplayer.PlayFile("artbeats_red_m1080p.ts") then
                if type(m.videoPlayer) = "roVideoPlayer" then
				    if m.videoPlayer.PlayFile(fname) then
					    rv = true
                        if type(m.imagePlayer) = "roImageWidget" then
		                    m.imagePlayer.StopDisplay()
					    endif
				    else
                        m.diagnostics.PrintDebug("**************  Video Player -> PlayFile FAILED!!!!")
				    end if
				else
                    m.diagnostics.PrintDebug("Video playback failed - the current layout does not contain a video zone")
				endif
			end if			
		else
            m.diagnostics.PrintDebug("FROM_CACHE: Item has no cache entry: " + item.guid)
		end if
	end if

	return rv
End Function

Function pFeedPlayer_LoadNextItem() As void	
	REM Get the next item to load
	item = m.feed.GetNextLoadItem()

	REM print "********************** Into LoadNextItem ****************"
    m.diagnostics.PrintDebug("Loading NEXT - Item Index =" + stri(m.feed.currentLoadItemIdx))

' EMPTY FEED changes			
    if type(item) <> "roAssociativeArray" then
	    m.PostSignChannelEndEvent()
        return
    endif
    
	ext = item.GetExtension()
	REM print "Item GUID =";item.guid
	REM print "Item ext =";ext
	fname = m.cacheManager.Get(item.guid + ext )
  
	REM If the file name is invalid it means the file is not in the cache
	If fname = invalid then
		REM Set up async download of item, indicate the download has started 
		REM print "setting up async transfer"
		
		if m.isImage(item) then 
			REM Check to make sure we have not already started another image download
			if m.feed.asyncImageDownloadInProgress = false then
				REM print "item indicates download has not started - ";item.url
				fname = m.cacheManager.cachedir + item.guid + ext
				m.feed.imageDownloader.SetUrl( item.url )
				m.feed.imageDownloader.SetMinimumTransferRate(1000,10)
				rv = m.feed.imageDownloader.AsyncGetToFile( fname)
				if rv = FALSE then
                    m.diagnostics.PrintDebug("AsyncGetToFile for image item download fails with " + m.feed.imageDownloader.GetFailureReason())
					REM Kick the load process to start loading the next item
					m.mport.PostMessage (m.loadEvent)
				else
					m.feed.asyncImageDownloadInProgress = true
					m.currentLoadItem = item
				end if
			End if
		else
			REM Check to make sure we have not already started another video download
			if m.videoDownloader.asyncVideoDownloadInProgress = false then
				fname = m.cacheManager.cachedir + item.guid + ext
				rv = m.videoDownloader.StartDownload(item.url, fname)
				if rv then
				    m.currentLoadItem = item
				endif
				REM Kick the load process since loading video may take a long time
				m.mport.PostMessage (m.loadEvent)
			endif
		end if			
	else
		REM The current item is already cached on the sd card so 
		REM go to the next item by bumping the load timer unless we are at then end of the feed
		if not m.feed.atLoadEnd then 
            m.diagnostics.PrintDebug("***********  Item in cache, not at load end, trigger timer ******")
			m.mport.PostMessage (m.loadEvent)
		else
            m.diagnostics.PrintDebug("*********  Item in cache, AT load end, don't trigger timer *********")
		end if
	end if 
End Function

REM ====================================================
REM            pFeedPlayer_SetFeed
REM ====================================================
REM
REM Set accessor w very basic sanity check
REM

Function pFeedPlayer_SetFeed( feed As Object ) As Void
	if feed = invalid then
		dpb("ERROR: Attempt to set invalid object as feed", feed )
' EMPTY FEED changes			
'	else if feed.items.Count() < 1 then
'		dpb("ERROR: Attempt to set empty feed as current feed", feed )
	else
		m.newFeed = feed
		If m.feed = invalid then
			m.feed = m.newFeed
			m.feed.SetStartDisplayTime(m.GetTime())
			m.newFeed = invalid
		end if
	end If

End Function

REM =======================================================
REM              ParseFeed
REM =======================================================
REM
REM Input: RSS XML document as String
REM Return:
REM    feedObject if XML parses to a feed with one or more valid feed items
REM    invalid if XML not parseable or feed contains no valid feed items
REM

Function pFeedPlayer_ParseFeed( xml As String ) As Object

    feed = invalid
	feedDoc=CreateObject("roXMLElement")
	if not feedDoc.Parse(xml)
        m.diagnostics.PrintDebug("Parse failed")
		feed = invalid
	else
		feed = newFeed( feedDoc, m )
		m.PopulateFeedItems( feed, feedDoc )
		if feed.items = invalid  then
			feed = invalid
' EMPTY FEED changes			
'		else if feed.items.Count() = 0 then
'			feed = invalid
		else 
			feed.ListItems()
		end if
	end if

	return feed

End Function


REM
REM =======================================================
REM                     Feed Object
REM =======================================================
REM

Function newFeed( xmlDoc As Object, feedPlayer As Object ) As Object
	feed = { ttlMinutes:15, currentItemIdx:-1, overscanFactor:1.0, numTriesToDisplay:0, unregistered:false,currentLoadItemIdx:-1, atEnd:False, atLoadEnd:False }
	feed.items = CreateObject("roArray", 0, TRUE )
	feed.playtime = invalid
	feed.loadTime = feedPlayer.GetTime() 
	feed.GetTTLSeconds = pFeed_GetTTLSeconds
	feed.GetNextItem = pFeed_GetNextItem
	feed.GetNextLoadItem = pFeed_GetNextLoadItem
	feed.GoToPrevItem = pFeed_GoToPrevItem
	feed.SetTTLMinutes = pFeed_SetTTLMinutes
	feed.ListItems = pFeed_ListItems
	feed.GetPlaytimeSeconds = pFeed_GetPlaytimeSeconds
	feed.SetStartDisplayTime = pFeed_SetStartDisplayTime
	feed.CycleComplete = pFeed_CycleComplete
	feed.imageDownloader = CreateObject("roUrlTransfer")
	feed.imageDownloader.SetPort(feedPlayer.mport)
	feed.asyncImageDownloadInProgress = false
	feed.startDisplayTime = invalid
    feed.feedPlayer = feedPlayer
    feed.diagnostics = feedPlayer.diagnostics

	return feed
End Function

Function pFeed_SetStartDisplayTime(t As Integer) As Void
	m.startDisplayTime = t
End Function

Function pFeed_GetPlaytimeSeconds( ) As Integer
	If m.playtime = invalid Then
		return m.GetTTLSeconds()
	else
		return m.playtime
	end if
End Function


Function pFeed_GetTTLSeconds() As Integer
	return m.ttlMinutes * 60
End Function

Sub pFeedPlayer_PopulateFeedItems( feed as Object, feedDoc as Object )

	REM Ideally should be getList returning a list
	REM to be used in caller as feed.items = getFeedItems(...)
	REM Experimenting around that now to find cause of 
	REM interpreter/OS crash

	for each elt in feedDoc.GetBody().Peek().GetBody()
		name = elt.GetName()

		if name = "ttl" then
			feed.SetTTLMinutes( elt.GetBody() )
		else if name = "frameuserinfo:playtime" Then
			feed.playtime = Val(elt.GetBody())
		else if name = "frameuserinfo:unregistered" then
			unregstate = elt.GetBody()
			if unregstate = "TRUE" then
				feed.unregistered = true
			else
				feed.unregistered = false
			endif
		else if name = "title" then
			feed.title = elt.GetBody()
		else if elt.GetName() = "item" then
			item = newFeedItem(elt)
			feed.items.Push( item )
		end if
	next

End Sub

Function pFeed_SetTTLMinutes( ttl As String ) As Void
	if ttl = invalid or Val(ttl) <= 0 then
		m.ttlMinutes = 15 
	else if Val(ttl) < 2 then
		m.ttlMinutes = 2
	else
		m.ttlMinutes = Val(ttl)
	end if
End Function

Function pFeed_GetNextItem() As Object
	item = invalid
	m.atEnd = False

	If m.items = invalid Then
        m.diagnostics.PrintDebug("No item list")
		m.atEnd = True
	else if m.items.IsEmpty() Then
        m.diagnostics.PrintDebug("Item list empty")
		m.atEnd = True
	else
		m.currentItemIdx = m.currentItemIdx + 1

		If m.currentItemIdx >= m.items.Count() then
			if m.items.count() = 1 then
				m.atEnd = true
			end if
			m.currentItemIdx = 0
		else if m.currentItemIdx = m.items.Count() - 1 then
			m.atEnd = True
		End If

		item =  m.items.GetEntry( m.currentItemIdx )
	end if

	return item
End Function

Function pFeed_GetNextLoadItem() As Object

	If m.items = invalid Then
        m.diagnostics.PrintDebug("No item list")
		m.atLoadEnd = True
		return invalid
	End If

	If m.items.IsEmpty() Then
        m.diagnostics.PrintDebug("Item list empty")
		m.atLoadEnd = True
		return invalid
	End If

	m.currentLoadItemIdx = m.currentLoadItemIdx + 1

	If m.currentLoadItemIdx >= m.items.Count() then
		if m.items.count() = 1 then
			m.atEnd = true
		end if
		m.currentLoadItemIdx = 0
	End If

	If m.currentLoadItemIdx = m.items.Count() - 1 then
		m.atLoadEnd = True
	Else
		m.atLoadEnd = False
	End If

	rv =  m.items.GetEntry( m.currentLoadItemIdx )

	return rv

End Function


Function pFeed_GoToPrevItem() As void

   REM make sure the item list is valid and that the list is not empty
   If m.items = invalid Then
      m.diagnostics.PrintDebug("No item list")
      m.atEnd = True
      return 
   else if m.items.IsEmpty() Then
      m.diagnostics.PrintDebug("Item list empty")
      m.atEnd = True
      return
   End If

   REM set the current index to the previous item
   m.currentItemIdx = m.currentItemIdx - 1

   REM if decreasing the current index made it negative (cause we were at 0), go to the end of the list
   If m.currentItemIdx < 0 then
      m.currentItemIdx = m.items.Count() - 1
   End If
   
   REM set the at End flags to represent the current condition after decreasing the index
   If m.currentItemIdx = m.items.Count() - 1 then
      m.atEnd = True
   Else
      m.atEnd = False
   End If

   return

End Function




Function pFeed_CycleComplete() As Boolean
    If m.items = invalid Then
        m.diagnostics.PrintDebug("pFeed_CycleComplete() Item list is null, returning True")
        return True
    End If
    If m.items.IsEmpty() Then
        m.diagnostics.PrintDebug("pFeed_CycleComplete() Item list is empty, returning True")
        return True
    End If

    If m.atEnd Then
        return True
    End If

    return False

End Function


Function pFeed_ListItems() As Void
  For each item in m.items
  Next
End Function

REM
REM =================================================
REM        FeedItem Object
REM =================================================
REM
REM New XML api not available in HD2000 Beta at time
REM of this writing.  REM Should convert to that once
REM its confirmed working on this platform.
REM

Function newFeedItem( xml as Object ) As Object
   REM item = { atEnd:False, durationSeconds: 60, url:"no_url", category:"no_category", thumbnail:"no_thumbnail", title:"no_title", displayStart:0, downloadStarted:false, atLoadEnd:False }
   item = { durationSeconds: 60, url:"no_url", category:"no_category", thumbnail:"no_thumbnail", title:"no_title", displayStart:0, preloaded:false}
   for each elt in xml.GetBody()
      name = elt.GetName()
      if name = "guid" then
         item.guid = elt.GetBody()
      else if name = "title" then
         item.title = elt.GetBody()
      else if name = "media:content" then
         item.url= elt.GetAttributes()["url"]
         item.type = elt.GetAttributes()["type"]
         item.duration = helper_GetDuration( elt )
      else if name = "media:thumbnail" then
         item.thumbnail = elt.GetAttributes()["url"]
      else if name = "guid" then
         item.guid = elt.GetBody()
      else if name = "category" then
         item.category = elt.GetBody()
      end if
   next
   item.DurationExpired = TRUE
   item.SetDisplayStart = pFeedItem_SetDisplayStart
   item.GetDisplayStart = pFeedItem_GetDisplayStart
   item.GetExtension = pFeedItem_GetExtension
   
   ' fixup item.guid as needed
    if type(item.guid) = "roString" then
        item.guid = CleanGuid(item.guid)
    else
        item.guid = StripLeadingSpaces(str(rnd(100000)))
    endif
   
   return item
End Function


Function CleanGuid(guidIn As String) As String
    charsToReplace = [ "/", ":", ",", ".", "&", "=", "?"]
    guid = guidIn
    for each charToReplace in charsToReplace
        index = 1
        while index <> 0
            index = instr(1, guid, charToReplace)
            if index <> 0 then
                part1 = ""
                if (index - 1) > 0 then
                    part1 = mid(guid, 0, index - 1)
                endif
                part2 = ""
                if (len(guid) - index) > 0 then
                    part2 = mid(guid, index + 1, len(guid) - index)
                endif
                guid = part1 + "-" + part2
            endif
        end while
    next
    return guid
End Function


Function pFeedItem_GetExtension() as String
   REM print "in getextension"
   if m.type = "image/jpeg" then
      REM print "type image/jpg"
      return ".jpg"
   else if m.type = "image/png" then
       REM print "type .png"
	   return ".png"
   else if m.type = "image/gif" then
	   REM print "type .gif"
       return ".gif"
   else if m.type = "video/mpeg" then
	   REM print "type  .mp4"
	   return ".mp4"
   endif
'   m.diagnostics.PrintDebug("returning default .jpg")
   return ".jpg"
End Function

Function pFeedItem_GetDisplayStart() As Integer
   return m.displayStart
End Function

Function pFeedItem_SetDisplayStart( t As Integer ) As Void

   If t = Invalid Then
'     m.diagnostics.PrintDebug("Invalid value for SetDisplayStart")
     return
   End If

   If t <= 0 then
'     m.diagnostics.PrintDebug("Set Display Start time is less than Zero - Invalid")
     return
   End If

   if t < m.displayStart then
'     m.diagnostics.PrintDebug("SetDisplayStart received value less then current display start")
     return
   end if

   m.displayStart = t

End Function

REM ================================================================
REM          helper_GetDuration
REM ================================================================
REM
REM Get duration attribute of a media:content sub element 
REM of RSS Item element.  Duration is number of seconds for
REM image to be displayed on screen.
REM
REM If no duration found sets default of 15.
REM If duration < 5 seconds sets minimum of 5
REM

Function helper_GetDuration( contentElement As Object ) As Integer

   duration = contentElement.GetAttributes()["duration"]

   if duration = Invalid then
       return 15
   end if

   duration = Val(duration)

   if duration < 5 then
      duration = 5
   end if

   return duration

End Function


'=========================================================
'                Cache Manager
'=========================================================

Function newCacheManager() As Object
   rv = {}
   rv.cachedir = "sd:/cache/"
   CreateDirectory("sd:/cache/")
   rv.Get = pCacheManager_Get
   rv.Prune = pCacheManager_Prune
   return rv
End Function


Function pCacheManager_Get(fname as String) As Object
	files = MatchFiles( m.cachedir, fname)
	if files.Count() > 0 then
		rv = m.cachedir + files[0]
		REM print "CACHE_HIT: File " + fname
		return rv
	else
		REM print "CACHE_MISS: No file " + fname 
		return Invalid
	end if
End Function

Function pCacheManager_Prune(feed as Object) As Object

	files = MatchFiles(m.cachedir, "*")
'    m.diagnostics.PrintDebug("File count in dir " + m.cachedir + stri(files.Count()))
	for each file in files
		found = false
		if type(feed) = "roAssociativeArray" then	
			for i=0 to feed.items.Count() - 1
				item = feed.items.GetEntry(i)
				ext = item.GetExtension()
				itemFileName = item.guid + ext 
				if itemFileName = file or ((len(file) > 4) and (right(file,4) = ".tmp")) then 
					found = true
				end if
			next
		endif
		if not found then
'            m.diagnostics.PrintDebug("FILE DELETED:" + file)
			DeleteFile( m.cachedir + file )
		end if
	next 
End Function

REM This should be updated as new revisions are made to this script
Function GetRevision() As String
	s = "$Revision: 1001 $"
	start = instr(1, s, " ")
	s=Right(s, len(s)-start)
	fin = instr(1,s," ")
	return Left(s, fin-1)
End Function   

Function GetHost() As String
	return "rss.signchannel.com"
End Function

Function pFeedPlayer_GetFeedUri() As String
	REM return "/productId=MZ" + m.model + "/frameId=" + m.deviceInfo.GetDeviceUniqueId() + "/version=" + GetRevision() 
   
	REM return "/productId=RK" + "MZ210" + "/frameId=" + m.deviceInfo.GetDeviceUniqueId() + "/version=" + GetRevision()
'	if m.model = "MZ210" then
'		productID = "RKMZ210"
'	else
'		productID = "RK" + m.model
'	end if
'	firmwareString = box(str(m.firmwareID)).trim()
'	return "/productId=" + productID + "/frameId=" + m.deviceInfo.GetDeviceUniqueId() + "/firmware=" + firmwareString
	
'	return "/productId=RK" + m.deviceInfo.GetModel() + "/frameId=" + m.deviceInfo.GetDeviceUniqueId() + "/version=" + GetSignChannelRevision() + "/resx=" + resx + "/resy=" + resy
	return "/productId=RK" + m.model + "/frameId=" + m.deviceInfo.GetDeviceUniqueId() + "/version=" + GetSignChannelRevision()

End Function

Function GetSignChannelRevision() As String
  s = "$Revision: 315 $"
  start = instr(0, s, " ")
  s=Right(s, len(s)-start)
  fin = instr(0,s," ")
  return Left(s, fin-1)
End Function                          


