Library "autoplugins.brs" 'region Main Sub Main() autorunVersion$ = "10.0.104" 'Bacon in development customAutorunVersion$ = "10.0.0" ' create global registry section to be used throughout script globalAA = GetGlobalAA() globalAA.registrySection = CreateObject("roRegistrySection", "networking") if type(globalAA.registrySection) <> "roRegistrySection" then stop end if globalAA.msgPort = CreateObject("roMessagePort") globalAA.msgPort.SetWatchdogTimeout(60) modelObject = CreateObject("roDeviceInfo") InitializeSysInfo(modelObject, autorunVersion$, customAutorunVersion$) InitSupervisorSupport() LoadRegistrySettings() InitializeSyncSpecAndSettings() debugParams = EnableDebugging() sysFlags = { } sysFlags.debugOn = debugParams.serialDebugOn sysFlags.systemLogDebugOn = debugParams.systemLogDebugOn sysInfo = globalAA.sysInfo nc = invalid videoMode = GetVideoMode() if type(videoMode) = "roVideoMode" then sysInfo.numberOfVideoPlanes = videoMode.getDecoderModes().count() ' HACK, due to current implementation of getDecoderModes() if sysInfo.deviceModel$ = "XD1034" or sysInfo.deviceModel$ = "XD1033" or sysInfo.deviceModel$ = "XD233" or sysInfo.deviceModel$ = "XD234" then sysInfo.numberOfVideoPlanes = 2 endif ' GetScreenModes is a function introduced in Series 5 players. ' Therefore we need to check if it's supported before use to avoid errors in lower series. if findMemberFunction(videoMode, "GetScreenModes") <> invalid then ' OS supports multi screen screenModes = videoMode.GetScreenModes() for each screen in screenModes edid = videoMode.GetEdidIdentity(screen.name) UpdateEdidValues(edid, sysInfo, screen.name) next end if ' always update HDMI without "-n" suffixes Edid variables to maintain backwards compatibility edid = videoMode.GetEdidIdentity(true) UpdateEdidValues(edid, sysInfo, "") edid = invalid videoMode = invalid endif ' determine if the storage device is writable du = CreateObject("roStorageInfo", "./") if du.IsReadOnly() then sysInfo.storageIsWriteProtected = true else sysInfo.storageIsWriteProtected = false end if systemTime = CreateObject("roSystemTime") InitRemoteSnapshots(sysInfo) CheckFirmwareVersion(modelObject, sysInfo, systemTime) diagnosticCodes = newDiagnosticCodes() RunBsp(sysFlags, sysInfo, diagnosticCodes, systemTime) end sub Sub InitializeSysInfo(modelObject as object, autorunVersion$ as string, customAutorunVersion$ as string) as object sysInfo = { } sysInfo.autorunVersion$ = autorunVersion$ sysInfo.customAutorunVersion$ = customAutorunVersion$ sysInfo.deviceUniqueID$ = modelObject.GetDeviceUniqueId() sysInfo.deviceFWVersion$ = modelObject.GetVersion() sysInfo.deviceFWVersionNumber% = modelObject.GetVersionNumber() sysInfo.deviceModel$ = modelObject.GetModel() sysInfo.deviceFamily$ = modelObject.GetFamily() sysInfo.enableLogDeletion = true sysInfo.ipAddressWired$ = "Invalid" nc = CreateObject("roNetworkConfiguration", 0) if type(nc) = "roNetworkConfiguration" then currentConfig = nc.GetCurrentConfig() if type(currentConfig) = "roAssociativeArray" then if currentConfig.ip4_address <> "" then sysInfo.ipAddressWired$ = currentConfig.ip4_address end if end if end if sysInfo.modelSupportsWifi = false sysInfo.ipAddressWireless$ = "Invalid" nc = CreateObject("roNetworkConfiguration", 1) if type(nc) = "roNetworkConfiguration" then currentConfig = nc.GetCurrentConfig() if type(currentConfig) = "roAssociativeArray" then sysInfo.modelSupportsWifi = true if currentConfig.ip4_address <> "" then sysInfo.ipAddressWireless$ = currentConfig.ip4_address end if end if end if sysInfo["sessionGuid$"] = "" GetGlobalAA().sysInfo = sysInfo end sub Function GetVideoMode() as object return CreateObject("roVideoMode") end function ' Note that the check only needed for series 4 and older models ' Series 5 and newer models should always support BSN.cloud so no need to check Sub CheckFirmwareVersion(modelObject as object, sysInfo as object, systemTime as object) ' check to see whether or not the current firmware meets the minimum compatibility requirements ' note that the return value for the GetVersionNumber() method does not include any additional version numbers after the first three ' the calculation is major*65536 + minor*256 + build versionNumber% = modelObject.GetVersionNumber() if sysInfo.deviceFamily$ = "pantera" then minVersionNumber% = 524407 minVersion$ = "8.0.119" else if sysInfo.deviceFamily$ = "pagani" then minVersionNumber% = 524407 minVersion$ = "8.0.119" else if sysInfo.deviceFamily$ = "impala" then minVersionNumber% = 524407 minVersion$ = "8.0.119" else if sysInfo.deviceFamily$ = "malibu" then minVersionNumber% = 524407 minVersion$ = "8.0.119" else if sysInfo.deviceFamily$ = "tiger" then minVersionNumber% = 524407 minVersion$ = "8.0.119" else ' no supported devices should hit this else minVersionNumber% = 524407 minVersion$ = "8.0.119" end if if versionNumber% < minVersionNumber% then osUpgradeMsg$ = "Firmware needs to be upgraded to " + minVersion$ + " or greater" videoMode = CreateObject("roVideoMode") if type(videoMode) = "roVideoMode" then resX = videoMode.GetResX() resY = videoMode.GetResY() videoMode = invalid r = CreateObject("roRectangle", 0, resY / 2 - resY / 64, resX, resY / 32) twParams = { } twParams.LineCount = 1 twParams.TextMode = 2 twParams.Rotation = 0 twParams.Alignment = 1 tw = CreateObject("roTextWidget", r, 1, 2, twParams) tw.PushString(osUpgradeMsg$) tw.Show() else ' for a model that doesn't support video, print the message instead systemLog = CreateObject("roSystemLog") systemLog.SendLine(osUpgradeMsg$) end if if GetActiveSettings().deviceScreenShotsEnabled then sleep(1000) ' sleep here ensures that graphics makes it to the screen before the snapshot is taken TakeSnapshot(systemTime, "") end if sleep(120000) RebootSystem() end if end sub ' setup snapshot capability as early as possible Sub InitRemoteSnapshots(sysInfo as object) globalAA = GetGlobalAA() ok = CreateDirectory("snapshots") ' fails if storage is write protected or formatted as ntfs, but ignore return value at this point ' generate list of snapshots currently on card globalAA.listOfSnapshotFiles = MatchFiles("/snapshots/", "*.jpg") BubbleSortFileNames(globalAA.listOfSnapshotFiles) end sub ' sort in ascending order from oldest to newest Sub BubbleSortFileNames(fileNames as object) if type(fileNames) = "roList" then n = fileNames.Count() while n <> 0 newn = 0 for i = 1 to (n - 1) if fileNames[i - 1] > fileNames[i] then k = fileNames[i] fileNames[i] = fileNames[i - 1] fileNames[i - 1] = k newn = i end if next n = newn end while end if end sub Sub UpdateEdidValues(edid as object, sysInfo as object, videoConnector$ as string) suffix$ = "$" if videoConnector$ <> "" then suffix$ = "_" + videoConnector$ + "$" if type(edid) = "roAssociativeArray" then sysInfo["edidMonitorSerialNumber"+suffix$] = edid.serial_number_string sysInfo["edidYearOfManufacture"+suffix$] = edid.year_of_manufacture sysInfo["edidMonitorName"+suffix$] = edid.monitor_name sysInfo["edidManufacturer"+suffix$] = edid.manufacturer sysInfo["edidUnspecifiedText"+suffix$] = edid.text_string sysInfo["edidSerialNumber"+suffix$] = StripLeadingSpaces(stri(edid.serial_number)) sysInfo["edidManufacturerProductCode"+suffix$] = edid.product sysInfo["edidWeekOfManufacture"+suffix$] = edid.week_of_manufacture else sysInfo["edidMonitorSerialNumber"+suffix$] = "" sysInfo["edidYearOfManufacture"+suffix$] = "" sysInfo["edidMonitorName"+suffix$] = "" sysInfo["edidManufacturer"+suffix$] = "" sysInfo["edidUnspecifiedText"+suffix$] = "" sysInfo["edidSerialNumber"+suffix$] = "" sysInfo["edidManufacturerProductCode"+suffix$] = "" sysInfo["edidWeekOfManufacture"+suffix$] = "" end if end sub Sub DisplayErrorScreen(msg1$ as string, msg2$ as string, msToWaitBeforeRebooting as integer) videoMode = CreateObject("roVideoMode") if type(videoMode) = "roVideoMode" then resX = videoMode.GetResX() resY = videoMode.GetResY() videoMode = invalid r = CreateObject("roRectangle", 0, 0, resX, resY) twParams = { } twParams.LineCount = 1 twParams.TextMode = 2 twParams.Rotation = 0 twParams.Alignment = 1 tw = CreateObject("roTextWidget", r, 1, 2, twParams) tw.PushString("") tw.Show() r = CreateObject("roRectangle", 0, resY / 2 - resY / 32, resX, resY / 32) tw = CreateObject("roTextWidget", r, 1, 2, twParams) tw.PushString(msg1$) tw.Show() r2 = CreateObject("roRectangle", 0, resY / 2, resX, resY / 32) tw2 = CreateObject("roTextWidget", r2, 1, 2, twParams) tw2.PushString(msg2$) tw2.Show() end if globalAA = GetGlobalAA() if type(globalAA.bsp) = "roAssociativeArray" then if type(globalAA.bsp.msgPort) = "roMessagePort" then globalAA.bsp.msgPort.SetWatchdogTimeout(0) end if if GetActiveSettings().deviceScreenShotsEnabled then sleep(1000) ' sleep here ensures that graphics makes it to the screen before the snapshot is taken activePresentation$ = "" if globalAA.bsp.activePresentation$ <> invalid then activePresentation$ = globalAA.bsp.activePresentation$ end if TakeSnapshot(globalAA.bsp.systemTime, activePresentation$) end if end if msgPort = CreateObject("roMessagePort") msg = wait(msToWaitBeforeRebooting, msgPort) RebootSystem() end sub Sub DisplayStorageDeviceLockedMessage() DisplayErrorScreen("The attached storage device is write protected.", "Remove it, enable writing, and reboot the device.", 0) end sub Function EnableDebugging() as object debugParams = { } debugParams.serialDebugOn = GetActiveSyncSpecSettings().enableSerialDebugging debugParams.systemLogDebugOn = GetActiveSyncSpecSettings().enableSystemLogDebugging return debugParams end function Sub WriteRegistrySetting(key$ as string, value$ as string) globalAA = GetGlobalAA() globalAA.registrySection.Write(key$, value$) end sub Sub LoadRegistrySettings() globalAA = GetGlobalAA() globalAA.registrySettings = {} ReadCachedRegistrySettings(globalAA.registrySection, globalAA.registrySettings) ' read legacy settings even if the supervisor manages the settings - but don't use them unless required ReadLegacyRegistrySettings(globalAA.registrySection, globalAA.registrySettings) end sub Sub MergeLegacyRegistrySettingsIntoGlobalSettings(settings as object) registrySection = GetGlobalAA().registrySection settings.setupType = registrySection.Read("sut") settings.unitName = registrySection.Read("un") settings.unitDescription = registrySection.Read("ud") settings.unitNamingMethod = registrySection.Read("unm") settings.lwsConfig = registrySection.Read("nlws") settings.lwsUserName = registrySection.Read("nlwsu") settings.lwsPassword = registrySection.Read("nlwsp") settings.lwsEnableUpdateNotifications = GetBoolFromString(registrySection.Read("nlwseun"), true) settings.inheritNetworkProperties = registrySection.Read("inp") end sub Sub AddRegistrySettingsNotInStandaloneSyncSpecIntoGlobalSettings(registrySection as object, settings as object) ' new supervisor, standalone- invoked on startup ' bacon standalone publish does not include device screen shot parameters ' bacon network does not support editing individual device screen shot parameteters settings.deviceScreenShotsEnabled = GetValidBool(registrySection.Read("enableRemoteSnapshot"), false) settings.deviceScreenShotsInterval = GetIntFromString(registrySection.Read("remoteSnapshotInterval")) settings.deviceScreenShotsCountLimit = GetIntFromString(registrySection.Read("remoteSnapshotMaxImages")) settings.deviceScreenShotsQuality = GetIntFromString(registrySection.Read("remoteSnapshotJpegQualityLevel")) settings.deviceScreenShotsOrientation = registrySection.Read("remoteSnapshotOrientation") end sub ' Registry settings that are used by both settings and non settings code; i.e., not known to supervisor Sub ReadCachedRegistrySettings(registrySection as object, registrySettings as object) registrySettings.setupSplashScreenEnabled = registrySection.Read("susse") registrySettings.OnlyDownloadIfCached$ = registrySection.Read("OnlyDownloadIfCached") registrySettings.timeBetweenNetConnects$ = registrySection.Read("tbnc") registrySettings.logDate$ = registrySection.Read("ld") registrySettings.logCounter$ = registrySection.Read("lc") registrySettings.idleScreenColor$ = registrySection.Read("isc") if registrySettings.idleScreenColor$ = "" then registrySettings.idleScreenColor$ = "FF000000" end if registrySettings.usbContentUpdatePassword$ = registrySection.Read("uup") registrySettings.brightWallName$ = registrySection.Read("brightWallName") registrySettings.brightWallScreenNumber$ = registrySection.Read("brightWallScreenNumber") registrySettings.lastBSNConnectionTime = registrySection.Read("lastBSNConnectionTime") 'Read persistent beacon data registrySettings.beacon1 = registrySection.Read("beacon1") registrySettings.beacon2 = registrySection.Read("beacon2") 'Open datagramSocket for autorun and bootstrap to communicate registrySettings.daUdpSocketPort$ = registrySection.Read("daUdpSocketPort") registrySettings.wsUdpSocketPort$ = registrySection.Read("wsUdpSocketPort") end sub Sub RunBsp(sysFlags as object, sysInfo as object, diagnosticCodes as object, systemTime as object) globalAA = GetGlobalAA() msgPort = globalAA.msgPort BSP = newBSP(sysFlags, msgPort, systemTime) BSP.globalVariables = NewGlobalVariables() BSP.svcPort = CreateObject("roControlPort", "BrightSign") BSP.svcPort.SetUserData("BrightSign") BSP.svcPort.SetPort(msgPort) BSP.svcPortIdentity = stri(BSP.svcPort.GetIdentity()) di = CreateObject("roDeviceInfo") if di.HasFeature("GPIO Connector") then BSP.controlPort = BSP.svcPort BSP.controlPortIdentity = BSP.svcPortIdentity else BSP.controlPort = CreateObject("roControlPort", "Expander-0-GPIO") if IsControlPort(BSP.controlPort) then BSP.controlPort.SetUserData("Expander-0-GPIO") BSP.controlPortIdentity = stri(BSP.controlPort.GetIdentity()) BSP.controlPort.SetPort(msgPort) else BSP.controlPort = { } BSP.controlPortIdentity = "" end if end if BSP.sh = CreateObject("roStorageHotplug") BSP.sh.SetPort(msgPort) BSP.nh = CreateObject("roNetworkHotplug") BSP.nh.SetPort(msgPort) BSP.diskMonitor = CreateObject("roDiskMonitor") BSP.diskMonitor.SetPort(msgPort) BSP.videoMode = GetVideoMode() if type(BSP.videoMode) = "roVideoMode" then BSP.videoMode.SetPort(msgPort) endif registrySettings = globalAA.registrySettings if globalAA.ccloud = invalid then ' open datagramSocket for autorun and bootstrap to communicate ' create object for bootstrap udp messages BSP.dgSocket = CreateObject("roDatagramSocket") if registrySettings.daUdpSocketPort$ = "" then registrySettings.daUdpSocketPort% = 8888 else registrySettings.daUdpSocketPort% = int(val(registrySettings.daUdpSocketPort$)) end if BSP.dgSocket.BindToLocalPort(registrySettings.daUdpSocketPort%) BSP.dgSocket.SetUserData("bootstrap") BSP.dgSocket.SetPort(msgPort) if registrySettings.wsUdpSocketPort$ = "" then registrySettings.wsUdpSocketPort% = 9999 else registrySettings.wsUdpSocketPort% = int(val(registrySettings.wsUdpSocketPort$)) end if endif ' create objects for lighting controllers BSP.blcs = CreateObject("roArray", 3, true) BSP.blcs[0] = CreateObject("roControlPort", "LightController-0-CONTROL") BSP.blcs[1] = CreateObject("roControlPort", "LightController-1-CONTROL") BSP.blcs[2] = CreateObject("roControlPort", "LightController-2-CONTROL") ' create objects for blc diagnostics BSP.blcDiagnostics = CreateObject("roArray", 3, true) BSP.blcDiagnostics[0] = CreateObject("roControlPort", "LightController-0-DIAGNOSTICS") if type(BSP.blcDiagnostics[0]) = "roControlPort" then BSP.blcDiagnostics[0].SetUserData("LightController-0-DIAGNOSTICS") BSP.blcDiagnostics[0].SetPort(msgPort) end if BSP.blcDiagnostics[1] = CreateObject("roControlPort", "LightController-1-DIAGNOSTICS") if type(BSP.blcDiagnostics[1]) = "roControlPort" then BSP.blcDiagnostics[1].SetUserData("LightController-1-DIAGNOSTICS") BSP.blcDiagnostics[1].SetPort(msgPort) end if BSP.blcDiagnostics[2] = CreateObject("roControlPort", "LightController-2-DIAGNOSTICS") if type(BSP.blcDiagnostics[2]) = "roControlPort" then BSP.blcDiagnostics[2].SetUserData("LightController-2-DIAGNOSTICS") BSP.blcDiagnostics[2].SetPort(msgPort) end if ' create objects for all attached button panels BSP.bpInputPorts = CreateObject("roArray", 4, true) BSP.bpInputPortIdentities = CreateObject("roArray", 4, true) BSP.bpInputPortHardware = CreateObject("roArray", 4, true) BSP.bpInputPortConfigurations = CreateObject("roArray", 4, true) BSP.bpInputPorts[0] = CreateObject("roControlPort", "TouchBoard-0-GPIO") if type(BSP.bpInputPorts[0]) = "roControlPort" then BSP.bpInputPorts[0].SetUserData("TouchBoard-0-GPIO") BSP.bpInputPortIdentities[0] = stri(BSP.bpInputPorts[0].GetIdentity()) BSP.bpInputPorts[0].SetPort(msgPort) properties = BSP.bpInputPorts[0].GetProperties() BSP.bpInputPortHardware[0] = properties.hardware BSP.bpInputPortConfigurations[0] = 0 end if BSP.bpInputPorts[1] = CreateObject("roControlPort", "TouchBoard-1-GPIO") if type(BSP.bpInputPorts[1]) = "roControlPort" then BSP.bpInputPorts[1].SetUserData("TouchBoard-1-GPIO") BSP.bpInputPortIdentities[1] = stri(BSP.bpInputPorts[1].GetIdentity()) BSP.bpInputPorts[1].SetPort(msgPort) properties = BSP.bpInputPorts[1].GetProperties() BSP.bpInputPortHardware[1] = properties.hardware BSP.bpInputPortConfigurations[1] = 0 end if BSP.bpInputPorts[2] = CreateObject("roControlPort", "TouchBoard-2-GPIO") if type(BSP.bpInputPorts[2]) = "roControlPort" then BSP.bpInputPorts[2].SetUserData("TouchBoard-2-GPIO") BSP.bpInputPortIdentities[2] = stri(BSP.bpInputPorts[2].GetIdentity()) BSP.bpInputPorts[2].SetPort(msgPort) properties = BSP.bpInputPorts[2].GetProperties() BSP.bpInputPortHardware[2] = properties.hardware BSP.bpInputPortConfigurations[2] = 0 end if BSP.bpInputPorts[3] = CreateObject("roControlPort", "TouchBoard-3-GPIO") if type(BSP.bpInputPorts[3]) = "roControlPort" then BSP.bpInputPorts[3].SetUserData("TouchBoard-3-GPIO") BSP.bpInputPortIdentities[3] = stri(BSP.bpInputPorts[3].GetIdentity()) BSP.bpInputPorts[3].SetPort(msgPort) properties = BSP.bpInputPorts[3].GetProperties() BSP.bpInputPortHardware[3] = properties.hardware BSP.bpInputPortConfigurations[3] = 0 end if BSP.sysInfo = sysInfo BSP.diagnosticCodes = diagnosticCodes BSP.diagnostics.SetSystemInfo(sysInfo, diagnosticCodes) BSP.logging.SetSystemInfo(sysInfo, diagnosticCodes) ' Create device specific User-Agent string BSP.userAgent$ = "BrightSign/" + sysInfo.deviceUniqueID$ + "/" + sysInfo.deviceFWVersion$ + " (" + sysInfo.deviceModel$ + ")" ' if the device is configured for local file networking with content transfers, require that the storage is writable settings = globalAA.settings if settings.lwsConfig = "c" and BSP.sysInfo.storageIsWriteProtected then DisplayStorageDeviceLockedMessage() lwsEnabled = false if settings.lwsConfig = "c" or settings.lwsConfig = "s" then lwsEnabled = true end if if lwsEnabled then BSP.localServer = CreateObject("roHttpServer", { port: 8080 }) BSP.localServer.SetPort(msgPort) if lwsEnabled then lwsUserName$ = settings.lwsUserName lwsPassword$ = settings.lwsPassword if (len(lwsUserName$) + len(lwsPassword$)) > 0 then lwsCredentials = { } lwsCredentials.AddReplace(lwsUserName$, lwsPassword$) else lwsCredentials = invalid end if end if BSP.GetDeviceConfigurationJsonAA = { HandleEvent: GetDeviceConfigurationJson, mVar: BSP } BSP.GetDeviceStatusJsonAA = { HandleEvent: GetDeviceStatusJson, mVar: BSP } BSP.GetDeviceSerialNumberJsonAA = { HandleEvent: GetDeviceSerialNumberJson, mVar: BSP } BSP.GetSnapshotConfigurationJsonAA = { HandleEvent: GetSnapshotConfigurationJson, mVar: BSP } BSP.GetSnapshotHistoryJsonAA = { HandleEvent: GetSnapshotHistoryJson, mVar: BSP } BSP.GetSnapshotJsonAA = { HandleEvent: GetSnapshotJson, mVar: BSP } BSP.PostFileJsonAA = { HandleEvent: PostFileJson, mVar: BSP } BSP.PostSyncSpecJsonAA = { HandleEvent: PostSyncSpecJson, mVar: BSP } BSP.PostPrepareForTransferJsonAA = { HandleEvent: PostPrepareForTransferJson, mVar: BSP } BSP.PostCardSizeLimitsJsonAA = { HandleEvent: PostCardSizeLimitsJson, mVar: BSP } BSP.GetCardSizeLimitsJsonAA = { HandleEvent: GetCardSizeLimitsJson, mVar: BSP } BSP.GetIDAA = { HandleEvent: GetID, mVar: BSP } BSP.GetUDPEventsAA = { HandleEvent: GetUDPEvents, mVar: BSP } BSP.GetRemoteDataAA = { HandleEvent: GetRemoteData, mVar: BSP } BSP.GetUserVarsAA = { HandleEvent: GetUserVars, mVar: BSP } BSP.GetIDInfoPageAA = { HandleEvent: GetIDInfoPage, mVar: BSP } BSP.SendUdpRestAA = { HandleEvent: SendUdpRest, mVar: BSP } BSP.GetUserVariableCategoriesAA = { HandleEvent: GetUserVariableCategories, mVar: BSP } BSP.GetUserVariablesByCategoryAA = { HandleEvent: GetUserVariablesByCategory, mVar: BSP } BSP.GetSnapshotConfigurationAA = { HandleEvent: GetSnapshotConfiguration, mVar: BSP } BSP.SetSnapshotConfigurationAA = { HandleEvent: SetSnapshotConfiguration, mVar: BSP } BSP.GetSnapshotAA = { HandleEvent: GetSnapshot, mVar: BSP } BSP.GetBSNStatusAA = { HandleEvent: GetBSNStatus, mVar: BSP } BSP.localServer.AddGetFromEvent({ url_path: "/v2/device/configuration", user_data: BSP.GetDeviceConfigurationJsonAA, passwords: lwsCredentials }) BSP.localServer.AddGetFromEvent({ url_path: "/v2/device/status", user_data: BSP.GetDeviceStatusJsonAA }) BSP.localServer.AddGetFromEvent({ url_path: "/v2/device/configuration/serialNumber", user_data: BSP.GetDeviceSerialNumberJsonAA}) BSP.localServer.AddGetFromEvent({ url_path: "/v2/snapshot/configuration", user_data: BSP.GetSnapshotConfigurationJsonAA, passwords: lwsCredentials }) BSP.localServer.AddGetFromEvent({ url_path: "/v2/snapshot/history", user_data: BSP.GetSnapshotHistoryJsonAA }) BSP.localServer.AddGetFromEvent({ url_path: "/v2/snapshot", user_data: BSP.GetSnapshotJsonAA }) BSP.localServer.AddPostToFormData({ url_path: "/v2/storage/configuration", user_data: BSP.PostCardSizeLimitsJsonAA, passwords: lwsCredentials }) BSP.localServer.AddGetFromEvent({ url_path: "/v2/storage/configuration", user_data: BSP.GetCardSizeLimitsJsonAA, passwords: lwsCredentials }) BSP.localServer.AddPostToFile({ url_path: "/v2/publish", destination_directory: GetDefaultDrive(), user_data: BSP.PostPrepareForTransferJsonAA, passwords: lwsCredentials }) BSP.localServer.AddPostToFile({ url_path: "/v2/publish/file", destination_directory: GetDefaultDrive(), user_data: BSP.PostFileJsonAA, passwords: lwsCredentials }) BSP.localServer.AddPostToFile({ url_path: "/v2/publish/sync", destination_directory: GetDefaultDrive(), user_data: BSP.PostSyncSpecJsonAA, passwords: lwsCredentials }) BSP.localServer.AddGetFromFile({ url_path: "/GetAutorun", content_type: "text/plain; charset=utf-8", filename: "autorun.brs" }) BSP.localServer.AddGetFromEvent({ url_path: "/GetID", user_data: BSP.GetIDAA }) BSP.localServer.AddGetFromEvent({ url_path: "/GetUDPEvents", user_data: BSP.GetUDPEventsAA }) BSP.localServer.AddGetFromEvent({ url_path: "/GetRemoteData", user_data: BSP.GetRemoteDataAA }) BSP.localServer.AddGetFromEvent({ url_path: "/GetUserVars", user_data: BSP.GetUserVarsAA }) BSP.localServer.AddGetFromEvent({ url_path: "/", user_data: BSP.GetIDInfoPageAA }) BSP.localServer.AddGetFromEvent({ url_path: "/GetUserVariableCategories", user_data: BSP.GetUserVariableCategoriesAA }) BSP.localServer.AddGetFromEvent({ url_path: "/GetUserVariablesByCategory", user_data: BSP.GetUserVariablesByCategoryAA }) BSP.localServer.AddGetFromEvent({ url_path: "/GetSnapshotConfiguration", user_data: BSP.GetSnapshotConfigurationAA }) BSP.localServer.AddPostToFormData({ url_path: "/SetSnapshotConfiguration", user_data: BSP.SetSnapshotConfigurationAA, passwords: lwsCredentials }) BSP.localServer.AddGetFromEvent({ url_path: "/GetSnapshot", user_data: BSP.GetSnapshotAA, passwords: lwsCredentials }) BSP.localServer.AddGetFromEvent({ url_path: "/bsn/status", user_data: BSP.GetBSNStatusAA, passwords: lwsCredentials }) BSP.localServer.AddPostToFormData({ url_path: "/SendUDP", user_data: BSP.SendUdpRestAA }) BSP.GetSynchronizerFilesToTransferAA = { HandleEvent: GetSynchronizerFilesToTransfer, mVar: BSP } BSP.localServer.AddPostToFile({ url_path: "/GetSynchronizerFilesToTransfer", destination_directory: GetDefaultDrive(), user_data: BSP.GetSynchronizerFilesToTransferAA }) BSP.SynchronizerFilePostedAA = { HandleEvent: SynchronizerFilePosted, mVar: BSP } BSP.localServer.AddPostToFile({ url_path: "/SynchronizerUploadFile", destination_directory: GetDefaultDrive(), user_data: BSP.SynchronizerFilePostedAA, passwords: lwsCredentials }) BSP.RestartScriptAA = { HandleEvent: RestartScript, mVar: BSP } BSP.localServer.AddGetFromEvent({ url_path: "/RestartScript", user_data: BSP.RestartScriptAA }) unitName$ = settings.unitName unitNamingMethod$ = settings.unitNamingMethod unitDescription$ = settings.unitDescription if settings.lwsConfig = "c" then BSP.lwsConfig$ = "content" else BSP.lwsConfig$ = "status" end if service = { } service.AddReplace("name", "BRIGHTSIGN-LWS-SERVICE") service.AddReplace("type", "_http._tcp") service.AddReplace("port", 8080) service.AddReplace("_functionality", BSP.lwsConfig$) service.AddReplace("_serialnumber", sysInfo.deviceUniqueID$) service.AddReplace("_unitname", unitName$) service.AddReplace("_unitnamingmethod", unitNamingMethod$) service.AddReplace("_unitdescription", unitDescription$) BSP.advert = CreateObject("roNetworkAdvertisement", service) if BSP.advert = invalid then BSP.diagnostics.PrintDebug("Unable to create roNetworkAdvertisement for BRIGHTSIGN-LWS-SERVICE on port 8080") end if else BSP.lwsConfig$ = "none" end if ' create will fail if storage device is formatted as NTFS ok = CreateDirectory("pool") ok = CreateDirectory("feedPool") ok = CreateDirectory("feed_cache") ok = CreateDirectory("htmlWidgets") ' ok = CreateDirectory("snapshots") BSP.assetPool = CreateObject("roAssetPool", "pool") BSP.feedPool = CreateObject("roAssetPool", "feedPool") activeSyncSpec = GetActiveSyncSpec() activeSyncSpecType = GetActiveSyncSpecType() activeSettings = GetActiveSettings() activeSyncSpecSettings = GetActiveSyncSpecSettings() BSP.contentEncrypted = false if activeSyncSpecType = "local" then BSP.assetCollection = activeSyncSpec.GetAssets("download") BSP.assetPoolFiles = CreateObject("roAssetPoolFiles", BSP.assetPool, BSP.assetCollection) ' update registry setting for USB content updates if necessary metadata = activeSyncSpec.GetMetadata("client") if metadata.DoesExist("usbUpdatePassword") then usbUpdatePassphrase$ = activeSyncSpec.LookupMetadata("client", "usbUpdatePassword") if usbUpdatePassphrase$ <> registrySettings.usbContentUpdatePassword$ then registrySettings.usbContentUpdatePassword$ = usbUpdatePassphrase$ WriteRegistrySetting("uup", usbUpdatePassphrase$) end if end if BSP.GetSupportedFeatures() obfuscatedPassphrase$ = activeSyncSpecSettings.obfuscatedPassphrase if obfuscatedPassphrase$ <> "" then deviceCustomization = CreateObject("roDeviceCustomization") deviceCustomization.StoreObfuscatedEncryptionKey("AesCtrHmac", obfuscatedPassphrase$) BSP.contentEncrypted = true end if BSP.networkingActive = false else if activeSyncSpecType = "network" or activeSyncSpecType = "localToBsn" then ' if the device is configured for networking, require that the storage is writable if BSP.sysInfo.storageIsWriteProtected then DisplayStorageDeviceLockedMessage() BSP.networkingHSM = newNetworkingStateMachine(BSP, BSP.msgPort) BSP.networkingHSM.SetSystemInfo(sysInfo, diagnosticCodes) BSP.logging.networking = BSP.networkingHSM BSP.assetCollection = activeSyncSpec.GetAssets("download") BSP.assetPoolFiles = CreateObject("roAssetPoolFiles", BSP.assetPool, BSP.assetCollection) BSP.downloadFiles = activeSyncSpec.GetFileList("download") BSP.networkingActive = true BSP.GetSupportedFeatures() deviceCustomization = CreateObject("roDeviceCustomization") if deviceCustomization.IsEncryptionKeyPresent("AesCtrHmac") then BSP.contentEncrypted = true end if BSP.SetPerFileEncryptionStatus(activeSyncSpec) BSP.networkingHSM.Initialize() end if ' determine and set file paths for global files globalAA.autoscheduleFilePath$ = GetAutoscheduleFilePath(BSP) if globalAA.autoscheduleFilePath$ = "" then stop globalAA.boseProductsFilePath$ = GetPoolFilePath(BSP.assetPoolFiles, "PartnerProducts.json") ' initialize logging parameters playbackLoggingEnabled = activeSettings.playbackLoggingEnabled eventLoggingEnabled = activeSettings.eventLoggingEnabled diagnosticLoggingEnabled = activeSettings.diagnosticLoggingEnabled stateLoggingEnabled = activeSettings.stateLoggingEnabled variableLoggingEnabled = activeSettings.variableLoggingEnabled uploadLogFilesAtBoot = activeSettings.uploadLogFilesAtBoot uploadLogFilesAtSpecificTime = activeSettings.uploadLogFilesAtSpecificTime if (activeSyncSpecType = "network" or activeSyncSpecType = "localToBsn") and IsInteger(activeSettings.uploadLogFilesTime) uploadLogFilesTime% = activeSettings.uploadLogFilesTime else uploadLogFilesTime% = 0 end if ' if the device is configured for logging, require that the storage is writable if (playbackLoggingEnabled or eventLoggingEnabled or stateLoggingEnabled or diagnosticLoggingEnabled or variableLoggingEnabled) and BSP.sysInfo.storageIsWriteProtected then DisplayStorageDeviceLockedMessage() BSP.variablesDBExists = false ' setup logging BSP.logging.InitializeLogging(playbackLoggingEnabled, eventLoggingEnabled, stateLoggingEnabled, diagnosticLoggingEnabled, variableLoggingEnabled, uploadLogFilesAtBoot, uploadLogFilesAtSpecificTime, uploadLogFilesTime%) BSP.logging.WriteDiagnosticLogEntry(diagnosticCodes.EVENT_STARTUP, BSP.sysInfo.deviceFWVersion$ + chr(9) + BSP.sysInfo.autorunVersion$ + chr(9) + BSP.sysInfo.customAutorunVersion$) BSP.InitializeNonPrintableKeyboardCodeList() ' ensure roAssetPool objects were created properly if type(BSP.assetPool) <> "roAssetPool" then BSP.diagnostics.PrintDebug("Unable to create roAssetPool for directory pool.") BSP.logging.WriteDiagnosticLogEntry(BSP.diagnosticCodes.EVENT_UNABLE_TO_CREATE_ASSET_POOL, "pool") BSP.logging.FlushLogFile() end if if type(BSP.feedPool) <> "roAssetPool" then BSP.diagnostics.PrintDebug("Unable to create roAssetPool for directory feedPool.") BSP.logging.WriteDiagnosticLogEntry(BSP.diagnosticCodes.EVENT_UNABLE_TO_CREATE_ASSET_POOL, "feedPool") BSP.logging.FlushLogFile() end if ' protect assets if not BSP.assetPool.ProtectAssets("current", BSP.assetCollection) then BSP.logging.WriteDiagnosticLogEntry(BSP.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, "AssetPool Protect Failure") BSP.logging.FlushLogFile() BSP.diagnostics.PrintDebug("### ProtectFiles failed: " + "AssetPool Protect Failure") end if ' limit pool sizes BSP.limitStorageSpace = false BSP.SetPoolSizes(activeSyncSpecSettings) ' Read and parse BoseProducts.json BSP.boseProductSpecs = ReadBoseProductsFile() ' Set up Bluetooth manager for players that support Bluetooth BSP.btManager = BSP.newBtManager() ' GPIO state machines and associated data structures dim gpioStateMachineRequired[8] dim gpioSM[8] BSP.gpioStateMachineRequired = gpioStateMachineRequired BSP.gpioSM = gpioSM ' BP state machines and associated data structures dim bpStateMachineRequired[4, 11] dim bpInputUsed[4, 11] dim bpOutputUsed[4, 11] dim bpSM[4, 11] BSP.bpStateMachineRequired = bpStateMachineRequired BSP.bpInputUsed = bpInputUsed BSP.bpOutputUsed = bpOutputUsed BSP.bpSM = bpSM ' Network priorities, etc for data feeds BSP.mrssDataFeedsBindingPriorityIndex = 0 BSP.mrssDataFeedsNumRetries% = 0 BSP.mrssMaxRetries% = 3 BSP.textDataFeedsBindingPriorityIndex = 0 BSP.textDataFeedsNumRetries% = 0 BSP.textMaxRetries% = 3 ' Create state machines ' Player state machine BSP.playerHSM = newPlayerStateMachine(BSP) BSP.playerHSM.SetSystemInfo(sysInfo, diagnosticCodes) ' Zone state machines are created by the Player state machine when it parses the schedule and autoplay files BSP.playerHSM.Initialize() BSP.CheckBLCsStatus() BSP.EventLoop() end sub Function IsControlPort(controlPort as object) as boolean return type(controlPort) = "roControlPort" end function Sub GetSupportedFeatures() ' NOTE - since minimum FW is required for bacon, all features that were supported in BA Classic are supported in this version of FW featureMinRevsFilePath$ = GetPoolFilePath(m.assetPoolFiles, "featureMinRevs.json") m.featureMinRevs = ParseFeatureMinRevs(featureMinRevsFilePath$) videoMode = CreateObject("roVideoMode") if type(videoMode) = "roVideoMode" then m.setSyncDomainSupported = true else m.setSyncDomainSupported = false endif m.forceResolutionSupported = true m.showHideVideoZoneSupported = true m.bypassProxySupported = true m.contentEncryptionSupported = true m.htmlSetTransformSupported = true m.httpWidgetGetUserAgentSupported = true m.mosaicModeSupported = true m.beaconsSupported = true m.btleSupported = true m.fullResolutionGraphicsSupported = true end sub Sub SetPerFileEncryptionStatus(syncSpec as object) ' get per file encryption status m.encryptionByFile = { } listOfDownloads = syncSpec.GetFileList("download") for each download in listOfDownloads if download.DoesExist("encryption") then m.encryptionByFile.AddReplace(download.name, download.encryption) end if next end sub Function ParseFeatureMinRevs(path$ as string) featureMinRevs = { } featureMinRevs$ = ReadAsciiFile(path$) if len(featureMinRevs$) > 0 then publishedFeatureMinRevs = ParseJson(featureMinRevs$) ' verify that this is a valid FeatureMinRevs Json file if type(publishedFeatureMinRevs.FeatureMinRevs) <> "roAssociativeArray" then print "Invalid FeatureMinRevs JSON file - name not FeatureMinRevs" : stop if not IsString(publishedFeatureMinRevs.FeatureMinRevs.version) then print "Invalid FeatureMinRevs JSON file - version not found" : stop for each featureAA in publishedFeatureMinRevs.FeatureMinRevs.features featureName$ = featureAA.name minRev$ = featureAA.minFWRev featureMinRevs.AddReplace(featureName$, minRev$) next end if return featureMinRevs end function Function GetRequestedBrowserStorageSpace(storageSpaceLimits as object) as integer browser_storage% = 0 if storageSpaceLimits.maximumHTMLLocalStoragePoolSizeMB% > 0 then if storageSpaceLimits.maximumHTMLLocalStoragePoolSizeMB% >= 2048 then storageSpaceLimits.maximumHTMLLocalStoragePoolSizeMB% = 2047 end if browser_storage% = browser_storage% + storageSpaceLimits.maximumHTMLLocalStoragePoolSizeMB% end if if storageSpaceLimits.maximumHTMLDataPoolSizeMB% > 0 then if storageSpaceLimits.maximumHTMLDataPoolSizeMB% >= 2048 then storageSpaceLimits.maximumHTMLDataPoolSizeMB% = 2047 end if browser_storage% = browser_storage% + storageSpaceLimits.maximumHTMLDataPoolSizeMB% end if if storageSpaceLimits.maximumHTMLIndexedDBPoolSizeMB% > 0 then if storageSpaceLimits.maximumHTMLIndexedDBPoolSizeMB% >= 2048 then storageSpaceLimits.maximumHTMLIndexedDBPoolSizeMB% = 2047 end if browser_storage% = browser_storage% + storageSpaceLimits.maximumHTMLIndexedDBPoolSizeMB% end if browser_storage% = browser_storage% * 1024 * 1024 return browser_storage% end function Function GetStorageSpaceLimits(syncSpecSettings as object) as object spaceLimitedByAbsoluteSize = syncSpecSettings.spaceLimitedByAbsoluteSize publishedDataSizeLimitMB = syncSpecSettings.publishedDataSizeLimitMB publishedDataSizeLimitPercentage = syncSpecSettings.publishedDataSizeLimitPercentage dynamicDataSizeLimitMB = syncSpecSettings.dynamicDataSizeLimitMB dynamicDataSizeLimitPercentage = syncSpecSettings.dynamicDataSizeLimitPercentage htmlDataSizeLimitPercentage = syncSpecSettings.htmlDataSizeLimitPercentage htmlDataSizeLimitMB = syncSpecSettings.htmlDataSizeLimitMB htmlLocalStorageSizeLimitPercentage = syncSpecSettings.htmlLocalStorageSizeLimitPercentage htmlLocalStorageSizeLimitMB = syncSpecSettings.htmlLocalStorageSizeLimitMB htmlIndexedDBSizeLimitPercentage = syncSpecSettings.htmlIndexedDBSizeLimitPercentage htmlIndexedDBSizeLimitMB = syncSpecSettings.htmlIndexedDBSizeLimitMB storageSpaceLimits = {} if not spaceLimitedByAbsoluteSize then ' convert from percentages to absolute values du = CreateObject("roStorageInfo", "./") totalCardSizeMB% = du.GetSizeInMegabytes() ' pool size for published data publishedDataSizeLimitPercentage% = int(val(publishedDataSizeLimitPercentage)) storageSpaceLimits.maximumPublishedDataPoolSizeMB% = publishedDataSizeLimitPercentage% * totalCardSizeMB% / 100 ' pool size for dynamic data dynamicDataSizeLimitPercentage% = int(val(dynamicDataSizeLimitPercentage)) storageSpaceLimits.maximumDynamicDataPoolSizeMB% = dynamicDataSizeLimitPercentage% * totalCardSizeMB% / 100 ' size for html data htmlDataSizeLimitPercentage% = int(val(htmlDataSizeLimitPercentage)) storageSpaceLimits.maximumHTMLDataPoolSizeMB% = htmlDataSizeLimitPercentage% * totalCardSizeMB% / 100 ' size for html local storage htmlLocalStorageSizeLimitPercentage% = 0 storageSpaceLimits.maximumHTMLLocalStoragePoolSizeMB% = 0 if htmlLocalStorageSizeLimitPercentage <> "" then htmlLocalStorageSizeLimitPercentage% = int(val(htmlLocalStorageSizeLimitPercentage)) storageSpaceLimits.maximumHTMLLocalStoragePoolSizeMB% = htmlLocalStorageSizeLimitPercentage% * totalCardSizeMB% / 100 end if ' size for html indexed db htmlIndexedDBSizeLimitPercentage% = 0 storageSpaceLimits.maximumHTMLIndexedDBPoolSizeMB% = 0 if htmlIndexedDBSizeLimitPercentage <> "" then htmlIndexedDBSizeLimitPercentage% = int(val(htmlIndexedDBSizeLimitPercentage)) storageSpaceLimits.maximumHTMLIndexedDBPoolSizeMB% = htmlIndexedDBSizeLimitPercentage% * totalCardSizeMB% / 100 end if else storageSpaceLimits.maximumPublishedDataPoolSizeMB% = int(val(publishedDataSizeLimitMB)) storageSpaceLimits.maximumDynamicDataPoolSizeMB% = int(val(dynamicDataSizeLimitMB)) storageSpaceLimits.maximumHTMLDataPoolSizeMB% = int(val(htmlDataSizeLimitMB)) storageSpaceLimits.maximumHTMLLocalStoragePoolSizeMB% = 0 if htmlLocalStorageSizeLimitMB <> "" then storageSpaceLimits.maximumHTMLLocalStoragePoolSizeMB% = int(val(htmlLocalStorageSizeLimitMB)) end if storageSpaceLimits.maximumHTMLIndexedDBPoolSizeMB% = 0 if htmlIndexedDBSizeLimitMB <> "" then storageSpaceLimits.maximumHTMLIndexedDBPoolSizeMB% = int(val(htmlIndexedDBSizeLimitMB)) end if end if return storageSpaceLimits end function Sub SetPoolSizes(syncSpecSettings as object) as object limitStorageSpace = syncSpecSettings.limitStorageSpace if limitStorageSpace then storageSpaceLimits = GetStorageSpaceLimits(syncSpecSettings) ok = m.assetPool.SetMaximumPoolSizeMegabytes(storageSpaceLimits.maximumPublishedDataPoolSizeMB%) ok = m.feedPool.SetMaximumPoolSizeMegabytes(storageSpaceLimits.maximumDynamicDataPoolSizeMB%) else ' clear prior settings ok = m.assetPool.SetMaximumPoolSizeMegabytes(-1) ok = m.feedPool.SetMaximumPoolSizeMegabytes(-1) end if end sub Sub CheckBLCsStatus() CheckBLCStatus(m.blcs[0], 0) CheckBLCStatus(m.blcs[1], 0) CheckBLCStatus(m.blcs[2], 0) end sub Sub CheckBLCStatus(controlPort as object, channel% as integer) if type(controlPort) <> "roControlPort" return control_cmd = CreateObject("roArray", 4, false) CHANNEL_CMD_STATUS% = &h1700 control_cmd[0] = CHANNEL_CMD_STATUS% control_cmd[1] = channel% ' Channel to check status for (note use 0 for main power) control_cmd[2] = 0 ' unused control_cmd[3] = 0 ' unused controlPort.SetOutputValues(control_cmd) end sub Function IsNetworkAvailable(network_interface as Dynamic) as boolean nc = CreateObject("roNetworkConfiguration", network_interface) if type(nc) <> "roNetworkConfiguration" then return false endif currentConfig = nc.GetCurrentConfig() if type(currentConfig) <> "roAssociativeArray" then return false endif return currentConfig.link end function Function GetBindingByPriority(dataType as string, priorityIndex as integer) as object aa = {} globalAA = GetGlobalAA() globalAA.bsp.diagnostics.PrintTimestamp() globalAA.bsp.diagnostics.PrintDebug("GetBindingByPriority entry:") globalAA.bsp.diagnostics.PrintDebug("dataType: " + dataType) globalAA.bsp.diagnostics.PrintDebug("priorityIndex: " + stri(priorityIndex)) if globalAA.networkInterfacePriorityLists.DoesExist(dataType) then priorityList = globalAA.networkInterfacePriorityLists.Lookup(dataType) while (priorityIndex < priorityList.count()) interface = priorityList[priorityIndex] ' check to see if interface has link if interface.networkInterface = "eth0" then network_interface = 0 else if interface.networkInterface = "wlan0" then network_interface = 1 else network_interface = interface.networkInterface endif bindingDiagnostic$ = GetBindingDiagnostic("Selected network_interface: ", network_interface) globalAA.bsp.diagnostics.PrintDebug(bindingDiagnostic$) nc = CreateObject("roNetworkConfiguration", network_interface) if type(nc) = "roNetworkConfiguration" then globalAA.bsp.diagnostics.PrintDebug("roNetworkConfiguration succeeded for selected network_interface") currentConfig = nc.GetCurrentConfig() if type(currentConfig) = "roAssociativeArray" then globalAA.bsp.diagnostics.PrintDebug("GetCurrentConfig succeeded for selected network_interface") if currentConfig.link then bindingDiagnostic$ = GetBindingDiagnostic("Return network_interface: ", network_interface) globalAA.bsp.diagnostics.PrintDebug(bindingDiagnostic$) aa = {} aa.network_interface = network_interface aa.priorityIndex = priorityIndex return aa else globalAA.bsp.diagnostics.PrintDebug("link false for selected network_interface") endif else globalAA.bsp.diagnostics.PrintDebug("GetCurrentConfig failed for selected network_interface") endif else globalAA.bsp.diagnostics.PrintDebug("roNetworkConfiguration failed for selected network_interface") endif priorityIndex = priorityIndex + 1 end while endif globalAA.bsp.diagnostics.PrintDebug("GetBindingByPriority returns -1") aa.network_interface = -1 aa.priorityIndex = 0 return aa end function Function GetBinding(dataType as string, priorityIndex as integer) as object globalAA = GetGlobalAA() if not globalAA.supervisorSupportsUsbNetworkInterfaces and globalAA.cellularModemActive then aa = {} aa.network_interface = 2 aa.priorityIndex = 0 return aa endif return GetBindingByPriority(dataType, priorityIndex) end function Function GetBindingDiagnostic(diagnostic$ as string, binding) as string if not IsString(binding) then binding = stri(binding) endif return diagnostic$ + binding end function Sub DiagnoseAndRecoverWifiNetwork(event as object) ' Check if event is url event if type(event) <> "roUrlEvent" then return ' Create wifi interface if not exist if m.wifiNetworkConfiguration = invalid then m.wifiNetworkConfiguration = CreateObject("roNetworkConfiguration", 1) end if ' Return if failed to create wifi interface if type(m.wifiNetworkConfiguration) <> "roNetworkConfiguration" then return ' Check if OS has function to safely reassociate wifi if not IsBoolean(m.osSupportsReassociateWifi) then m.osSupportsReassociateWifi = (findMemberFunction(m.wifiNetworkConfiguration, "ReassociateWiFi") <> invalid) end if ' Return if OS doesn't support reassociate wifi if m.osSupportsReassociateWifi <> true then return if not IsBoolean(m.disableWifiAutoRecovery) then ' GetGlobalAA().registrySection is the "networking" registry section ' NOTE: If this registry key is changed, it will not take effect until autorun is restarted. registryKey = GetGlobalAA().registrySection.Read("disable_wifi_auto_recovery") m.disableWifiAutoRecovery = GetBoolFromString(registryKey, false) end if ' Return if disabled by registry key if m.disableWifiAutoRecovery = true then return currentConfig = m.wifiNetworkConfiguration.GetCurrentConfig() ' Exit earlier if wifi not confiugred if currentConfig.wifi_bssid = "" then return responseCode = event.GetResponseCode() if responseCode >= 0 then ' Clear failure count and return if there is a successful connection m.minusSixFailureCount = 0 m.minusTwentyEightFailureCount = 0 m.minusFiftySixFailureCount = 0 return end if ' -6 is CURLE_COULDNT_RESOLVE_HOST if responseCode = -6 then m.minusSixFailureCount = m.minusSixFailureCount + 1 end if ' -28 is CURLE_OPERATION_TIMEDOUT if responseCode = -28 then m.minusTwentyEightFailureCount = m.minusTwentyEightFailureCount + 1 end if ' -56 is CURLE_RECV_ERROR if responseCode = -56 then m.minusFiftySixFailureCount = m.minusFiftySixFailureCount + 1 end if ' Make sure at least 5 mins(300 in seconds) has passed since last network reset ' to leave some time for the network re-connection RECONNECT_SECONDS = 300 RECONNECT_LIMIT = 5 kick = false if (m.lastWifiReconnectTimestampInSeconds = 0) or (m.systemTime.GetLocalDateTime().ToSecondsSinceEpoch() - m.lastWifiReconnectTimestampInSeconds > RECONNECT_SECONDS) then ' Check failure count against kick-wifi threshold if (m.minusSixFailureCount > RECONNECT_LIMIT) or (m.minusTwentyEightFailureCount > RECONNECT_LIMIT) or (m.minusFiftySixFailureCount > RECONNECT_LIMIT) then kick = true end if end if ip4_address = currentConfig.ip4_address ipInvalid = (currentConfig.link = true) and (ip4_address = Invalid or ip4_address = "" or instr(1, ip4_address, "169.254") = 1) ' Check and test for IP address if (ipInvalid or kick) then bad_bssid = currentConfig.wifi_bssid error_rates$ = "-6_count=" + stri(m.minusSixFailureCount) + ", -28_count=" + stri(m.minusTwentyEightFailureCount) + ", -56_count=" + stri(m.minusFiftySixFailureCount) m.PrintDebug("Recovering wifi: interface=" + bad_bssid + ", ip4_add=" + ip4_address) m.PrintDebug("Error counts: " + error_rates$) m.wifiNetworkConfiguration.ReassociateWiFi() m.lastWifiReconnectTimestampInSeconds = m.systemTime.GetLocalDateTime().ToSecondsSinceEpoch() ' We have reconnected, clear failure counts m.minusSixFailureCount = 0 m.minusTwentyEightFailureCount = 0 m.minusFiftySixFailureCount = 0 end if end sub Function newBSP(sysFlags as object, msgPort as object, systemTime as object) as object BSP = { } globalAA = GetGlobalAA() globalAA.bsp = BSP BSP.globalAA = globalAA BSP.msgPort = msgPort BSP.systemTime = systemTime BSP.diagnostics = newDiagnostics(sysFlags) BSP.Restart = Restart BSP.StartPlayback = StartPlayback BSP.DeallocateNodeComponents = DeallocateNodeComponents BSP.newLogging = newLogging BSP.logging = BSP.newLogging() BSP.LogActivePresentation = LogActivePresentation BSP.SetTouchRegions = SetTouchRegions BSP.InitializeTouchScreen = InitializeTouchScreen BSP.AddRectangularTouchRegion = AddRectangularTouchRegion BSP.ExecuteMediaStateCommands = ExecuteMediaStateCommands BSP.ExecuteTransitionCommands = ExecuteTransitionCommands BSP.ExecuteCmd = ExecuteCmd BSP.ExecuteSendWssCommand = ExecuteSendWssCommand BSP.ExecuteSendBMapCommand = ExecuteSendBMapCommand BSP.ExecuteSendBMapHexCommand = ExecuteSendBMapHexCommand BSP.ExecuteSwitchPresentationCommand = ExecuteSwitchPresentationCommand BSP.MatchWssEvent = MatchWssEvent BSP.ExecuteGpioOnCommand = ExecuteGpioOnCommand BSP.ExecuteGpioOffCommand = ExecuteGpioOffCommand BSP.ExecuteGpioSetStateCommand = ExecuteGpioSetStateCommand BSP.ExecuteSerialSendStringCommand = ExecuteSerialSendStringCommand BSP.ExecuteSendSerialBlockCommand = ExecuteSendSerialBlockCommand BSP.ExecuteSendSerialByteCommand = ExecuteSendSerialByteCommand BSP.ExecuteSendSerialBytesCommand = ExecuteSendSerialBytesCommand BSP.ExecuteSendUDPCommand = ExecuteSendUDPCommand BSP.ExecuteSendUDPBytesCommand = ExecuteSendUDPBytesCommand BSP.ExecuteSendProntoIRRemote = ExecuteSendProntoIRRemote BSP.ExecuteSendIRRemoteCommand = ExecuteSendIRRemoteCommand BSP.ExecuteSendBLC400OutputCommand = ExecuteSendBLC400OutputCommand BSP.ExecuteSendBPOutputCommand = ExecuteSendBPOutputCommand BSP.ExecuteSynchronizeCommand = ExecuteSynchronizeCommand BSP.ExecuteSendZoneMessageCommand = ExecuteSendZoneMessageCommand BSP.ExecuteSendPluginMessageCommand = ExecuteSendPluginMessageCommand BSP.ExecuteResizeZoneCommand = ExecuteResizeZoneCommand BSP.ExecuteHideZoneCommand = ExecuteHideZoneCommand BSP.ExecuteShowZoneCommand = ExecuteShowZoneCommand BSP.ExecutePauseZonePlaybackCommand = ExecutePauseZonePlaybackCommand BSP.ExecuteResumeZonePlaybackCommand = ExecuteResumeZonePlaybackCommand BSP.ExecuteInternalSynchronizeCommand = ExecuteInternalSynchronizeCommand BSP.ExecuteCecSendStringCommand = ExecuteCecSendStringCommand BSP.ExecuteCecPhilipsSetVolumeCommand = ExecuteCecPhilipsSetVolumeCommand BSP.ExecutePauseCommand = ExecutePauseCommand BSP.ExecuteSetVariableCommand = ExecuteSetVariableCommand BSP.ExecuteBeaconStartCommand = ExecuteBeaconStartCommand BSP.ExecuteBeaconStopCommand = ExecuteBeaconStopCommand BSP.ExecuteSetAllAudioOutputsCommand = ExecuteSetAllAudioOutputsCommand BSP.EventLoop = EventLoop BSP.SetAudioMode = SetAudioMode BSP.UnmuteAllAudio = UnmuteAllAudio BSP.UnmuteAudioConnector = UnmuteAudioConnector BSP.MuteAudioOutput = MuteAudioOutput BSP.MuteAudioOutputs = MuteAudioOutputs BSP.SetConnectorVolume = SetConnectorVolume BSP.ChangeConnectorVolume = ChangeConnectorVolume BSP.SetZoneVolume = SetZoneVolume BSP.ChangeZoneVolume = ChangeZoneVolume BSP.SetZoneChannelVolume = SetZoneChannelVolume BSP.ChangeZoneChannelVolume = ChangeZoneChannelVolume BSP.SetUSBAudioOutput = SetUSBAudioOutput BSP.SetAudioVolumeLimits = SetAudioVolumeLimits BSP.GetZone = GetZone BSP.GetVideoZone = GetVideoZone BSP.ChangeChannelVolumes = ChangeChannelVolumes BSP.SetChannelVolumes = SetChannelVolumes BSP.PauseVideo = PauseVideo BSP.ResumeVideo = ResumeVideo BSP.SetPowerSaveMode = SetPowerSaveMode BSP.CecDisplayOn = CecDisplayOn BSP.CecDisplayOff = CecDisplayOff BSP.CecSetSourceToBrightSign = CecSetSourceToBrightSign BSP.CecPhilipsSetVolume = CecPhilipsSetVolume BSP.SendCecCommand = SendCecCommand BSP.WaitForSyncResponse = WaitForSyncResponse BSP.GetAutoschedule = GetAutoschedule BSP.GetNonPrintableKeyboardCode = GetNonPrintableKeyboardCode BSP.InitializeNonPrintableKeyboardCodeList = InitializeNonPrintableKeyboardCodeList BSP.ConfigureIRRemote = ConfigureIRRemote BSP.ConfigureBPs = ConfigureBPs BSP.ConfigureBP = ConfigureBP BSP.ConfigureBPButton = ConfigureBPButton BSP.ConfigureBPInput = ConfigureBPInput BSP.ConfigureGPIOButton = ConfigureGPIOButton BSP.ConfigureGPIOInput = ConfigureGPIOInput BSP.GetID = GetID BSP.GetUDPEvents = GetUDPEvents BSP.GetRemoteData = GetRemoteData BSP.GetIDInfoPage = GetIDInfoPage BSP.FilePosted = FilePosted BSP.FreeSpaceOnDrive = FreeSpaceOnDriveJson BSP.GetBoseProductSpec = GetBoseProductSpec BSP.CreateSerial = CreateSerial BSP.ScheduleRetryCreateSerial = ScheduleRetryCreateSerial BSP.RetryCreateSerial = RetryCreateSerial BSP.AttemptOpenSerial = AttemptOpenSerial BSP.CreateBMapObjects = CreateBMapObjects BSP.CreateBMap = CreateBMap BSP.ScheduleRetryCreateBMap = ScheduleRetryCreateBMap BSP.RetryCreateBMap = RetryCreateBMap BSP.AttemptOpenBMap = AttemptOpenBMap BSP.CreateDatagramReceiver = CreateDatagramReceiver BSP.CreateUDPSender = CreateUDPSender BSP.SendUDPNotification = SendUDPNotification BSP.udpNotificationAddress$ = "224.0.200.200" BSP.udpNotificationPort% = 5000 BSP.rssFileIndex% = 0 BSP.GetRSSTempFilename = GetRSSTempFilename BSP.ReadVariablesDB = ReadVariablesDB BSP.DBIsValid = DBIsValid BSP.DBTablesExist = DBTablesExist BSP.ReadVariables = ReadVariables BSP.ReadSchema1Tables = ReadSchema1Tables BSP.CreateSchema2Tables = CreateSchema2Tables BSP.CreateDBTable = CreateDBTable BSP.DeleteDBTable = DeleteDBTable BSP.DropSchema1Tables = DropSchema1Tables BSP.AddDBSection = AddDBSection BSP.AddDBCategory = AddDBCategory BSP.AddDBVariable = AddDBVariable BSP.DeleteDBVariable = DeleteDBVariable BSP.UpdateDBVariable = UpdateDBVariable BSP.UpdateDBVariableDefaultValue = UpdateDBVariableDefaultValue BSP.UpdateDBVariableMediaUrl = UpdateDBVariableMediaUrl BSP.UpdateDBVariablePosition = UpdateDBVariablePosition BSP.GetDBVersion = GetDBVersion BSP.SetDBVersion = SetDBVersion BSP.UpdateDBVersion = UpdateDBVersion BSP.GetDBTableNames = GetDBTableNames BSP.GetDBSectionNames = GetDBSectionNames BSP.GetDBSectionId = GetDBSectionId BSP.GetDBCategoryId = GetDBCategoryId BSP.GetDBCategoryNames = GetDBCategoryNames BSP.DoGetCategories = DoGetCategories BSP.GetOrderedVariables = GetOrderedVariables BSP.GetUserVariableCategoryList = GetUserVariableCategoryList BSP.GetUserVariablesByCategoryList = GetUserVariablesByCategoryList BSP.GetUserVariablesGivenCategory = GetUserVariablesGivenCategory BSP.GetCategoryIdFromAccess = GetCategoryIdFromAccess BSP.GetCategoryFromSection = GetCategoryFromSection BSP.ExecuteDBInsert = ExecuteDBInsert BSP.ExecuteDBSelect = ExecuteDBSelect BSP.GetDBVersionCallback = GetDBVersionCallback BSP.GetDBCategoryIdCallback = GetDBCategoryIdCallback BSP.GetDBTableNamesCallback = GetDBTableNamesCallback BSP.GetDBSectionNamesCallback = GetDBSectionNamesCallback BSP.GetDBSectionIdCallback = GetDBSectionIdCallback BSP.ReadSchema1TablesCallback = ReadSchema1TablesCallback BSP.GetUserVariablesGivenCategoryCallback = GetUserVariablesGivenCategoryCallback BSP.ReadVariablesCallback = ReadVariablesCallback BSP.ExportVariablesDBToAsciiFile = ExportVariablesDBToAsciiFile BSP.GetUserVariable = GetUserVariable BSP.DeleteVariable = DeleteVariable BSP.ResetVariables = ResetVariables BSP.ResetVariable = ResetVariable BSP.ChangeUserVariableValue = ChangeUserVariableValue BSP.AssignSystemVariablesToUserVariables = AssignSystemVariablesToUserVariables BSP.AssignSystemVariableToUserVariables = AssignSystemVariableToUserVariables BSP.GenerateSessionGuid = GenerateSessionGuid BSP.ClearSessionGuid = ClearSessionGuid BSP.UpdateDataFeed = UpdateDataFeed BSP.CreateUserVariablesFromDataFeed = CreateUserVariablesFromDataFeed BSP.CheckBLCsStatus = CheckBLCsStatus BSP.CheckBLCStatus = CheckBLCStatus BSP.UpdateIPAddressUserVariables = UpdateIPAddressUserVariables BSP.UpdateEdidUserVariables = UpdateEdidUserVariables BSP.GetAttachedFiles = GetAttachedFiles BSP.PostponeRestart = PostponeRestart BSP.ProcessMediaEndEvent = ProcessMediaEndEvent BSP.SetPoolSizes = SetPoolSizes BSP.InitiateRemoteSnapshotTimer = InitiateRemoteSnapshotTimer BSP.RemoveRemoteSnapshotTimer = RemoveRemoteSnapshotTimer BSP.NetworkingIsActive = NetworkingIsActive BSP.GetSupportedFeatures = GetSupportedFeatures BSP.encryptionByFile = { } BSP.SetPerFileEncryptionStatus = SetPerFileEncryptionStatus BSP.SetEncryptionAttributes = SetEncryptionAttributes BSP.QueueRetrieveLiveDataFeed = QueueRetrieveLiveDataFeed BSP.RetrieveLiveDataFeed = RetrieveLiveDataFeed BSP.RetrievePendingLiveDataFeed = RetrievePendingLiveDataFeed BSP.AdvanceToNextLiveDataFeedInQueue = AdvanceToNextLiveDataFeedInQueue BSP.RemoveFailedFeedFromQueue = RemoveFailedFeedFromQueue BSP.newBtManager = newBtManager BSP.GetRuntimeUsbConnector = GetRuntimeUsbConnector BSP.GetSpecifiedConnector = GetSpecifiedConnector ' json functions BSP.jsonAutoschedule = jsonAutoschedule BSP.ResendAutorunCapabilitiesToSupervisor = ResendAutorunCapabilitiesToSupervisor BSP.SendAutorunCapabilitiesToSupervisorViaUdp = SendAutorunCapabilitiesToSupervisorViaUdp return BSP end function 'endregion 'region Local Web server Sub GetConfigurationPage(userData as object, e as object) mVar = userData.mVar e.AddResponseHeader("Content-type", "text/html; charset=utf-8") if type(mVar.sign) = "roAssociativeArray" and mVar.sign.deviceWebPageDisplay$ = "None" then e.SetResponseBodyString("") if not e.SendResponse(403) then stop end if else if mVar.deviceWebPageFilePath$ <> "" webPageContents$ = ReadAsciiFile(mVar.deviceWebPageFilePath$) e.SetResponseBodyString(webPageContents$) if not e.SendResponse(200) then stop end if else e.SetResponseBodyString("") if not e.SendResponse(404) then stop end if end if end sub Sub SendUdpRest(userData as object, e as object) mVar = userData.mVar args = e.GetFormData() CreateUDPSender(mVar) for each key in args value = args[key] mVar.udpSender.Send(value) next if not e.SendResponse(200) then stop end if end sub Function GetCategoryFromSection(sectionName$ as string, categoryName$ as string) as integer sectionId% = m.GetDBSectionId(sectionName$) if sectionId% < 0 then return -1 end if return m.GetDBCategoryId(sectionId%, categoryName$) end function Sub SetValuesByCategory(userData as object, e as object) mVar = userData.mVar args = e.GetFormData() if args.DoesExist("category") then categoryName$ = args.Lookup("category") categoryId% = mVar.GetCategoryFromSection(mVar.activePresentation$, categoryName$) if categoryId% < 0 then categoryId% = mVar.GetCategoryFromSection("Shared", categoryName$) end if if categoryId% > 0 then ' is there a way to check that userVariableName exists in the db? for each userVariableName in args mVar.UpdateDBVariable(categoryId%, userVariableName, args.Lookup(userVariableName)) next end if end if e.AddResponseHeader("Location", e.GetRequestHeader("Referer")) if not e.SendResponse(302) then stop end sub Sub SetValues(userData as object, e as object) mVar = userData.mVar args = e.GetFormData() userVariables = mVar.currentUserVariables userVariablesUpdated = false if type(userVariables) = "roAssociativeArray" then for each userVariableName in args if userVariables.DoesExist(userVariableName) then userVariable = userVariables.Lookup(userVariableName) userVariable.SetCurrentValue(args.Lookup(userVariableName), false) userVariablesUpdated = true end if next end if e.AddResponseHeader("Location", e.GetRequestHeader("Referer")) if not e.SendResponse(302) then stop if userVariablesUpdated then userVariablesChanged = { } userVariablesChanged["EventType"] = "USER_VARIABLES_UPDATED" mVar.msgPort.PostMessage(userVariablesChanged) ' Notify controlling devices to refresh mVar.SendUDPNotification("refresh") end if end sub Sub PopulateIDData(mVar as object, root as object) settings = GetGlobalAA().settings unitName$ = settings.unitName unitNamingMethod$ = settings.unitNamingMethod unitDescription$ = settings.unitDescription 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$) elem = root.AddElement("autorunVersion") elem.SetBody(mVar.sysInfo.autorunVersion$) elem = root.AddElement("firmwareVersion") elem.SetBody(mVar.sysInfo.deviceFWVersion$) elem = root.AddElement("bsnActive") if mVar.NetworkingIsActive() then elem.SetBody("yes") else elem.SetBody("no") end if elem = root.AddElement("snapshotsAvailable") globalAA = GetGlobalAA() if globalAA.listOfSnapshotFiles.Count() > 0 then elem.SetBody("yes") else elem.SetBody("no") end if end sub Function newUDPItem(udpLabel as object, udpEventName as object) as object udpItem = { } udpItem.action$ = udpEventName udpItem.label$ = udpLabel return udpItem end function Sub PopulateUDPData(mVar as object, root as object) sign = mVar.sign elem = root.AddElement("udpNotificationAddress") elem.SetBody(mVar.udpNotificationAddress$) elem = root.AddElement("notificationPort") elem.SetBody(StripLeadingSpaces(stri(mVar.udpNotificationPort%))) elem = root.AddElement("destinationPort") elem.SetBody(StripLeadingSpaces(stri(mVar.udpNotificationPort%))) if type(sign) = "roAssociativeArray" then elem = root.AddElement("receivePort") elem.SetBody(StripLeadingSpaces(stri(sign.udpReceivePort))) udpEvents = { } for each zoneHSM in sign.zonesHSM for each stateName in zoneHSM.stateTable state = zoneHSM.stateTable[stateName] udpEventsInState = state.udpEvents if type(udpEventsInState) = "roAssociativeArray" then for each udpEventName in udpEventsInState transition = udpEventsInState.Lookup(udpEventName) if type(transition.udpExport) = "roBoolean" and transition.udpExport then if type(transition.udpLabel$) = "roString" then udpLabel$ = transition.udpLabel$ else udpLabel$ = udpEventName end if if not udpEvents.DoesExist(udpLabel$) then udpEvents.AddReplace(udpLabel$, newUDPItem(udpLabel$, udpEventName)) end if end if if udpEventName = "" or udpEventName = "(.*)" then targetMediaState$ = udpEventsInState.Lookup(udpEventName).targetMediaState$ if targetMediaState$ <> "" then targetState = zoneHSM.stateTable[targetMediaState$] if targetState.type$ = "playFile" then filesTable = targetState.filesTable for each playFileKey in filesTable entry = filesTable[playFileKey] export = true if entry.DoesExist("export") and (not entry["export"]) then export = false end if if export then udpLabel$ = playFileKey if entry.DoesExist("label$") then udpLabel$ = entry["label$"] end if if not udpEvents.DoesExist(udpLabel$) then udpEvents.AddReplace(udpLabel$, newUDPItem(udpLabel$, playFileKey)) end if end if next end if end if end if next end if if state.type$ = "mediaList" then for each event in state.transitionToNextEventList if event.eventName = "udp" then if event.eventData.export then if IsString(event.eventData.label) then udpLabel$ = event.eventData.label else udpLabel$ = event.eventName end if if not udpEvents.DoesExist(udpLabel$) then udpEvents.AddReplace(udpLabel$, newUDPItem(udpLabel$, event.eventData.data)) end if endif endif next for each event in state.transitionToPreviousEventList if event.eventName = "udp" then if event.eventData.export then if IsString(event.eventData.label) then udpLabel$ = event.eventData.label else udpLabel$ = event.eventName end if if not udpEvents.DoesExist(udpLabel$) then udpEvents.AddReplace(udpLabel$, newUDPItem(udpLabel$, event.eventData.data)) end if endif endif next end if next next udpEventsElem = root.AddElement("udpEvents") udpEventsElem.AddAttribute("useLabel", "true") for each udpEvent in udpEvents udpItem = udpEvents.Lookup(udpEvent) udpEventElem = udpEventsElem.AddElement("udpEvent") udpEventLabel = udpEventElem.AddElement("label") udpEventLabel.SetBody(udpItem.label$) udpEventAction = udpEventElem.AddElement("action") udpEventAction.SetBody(udpItem.action$) next end if end sub Sub GetID(userData as object, e as object) mVar = userData.mVar root = CreateObject("roXMLElement") root.SetName("BrightSignID") PopulateIDData(mVar, root) xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) e.AddResponseHeader("Content-type", "text/xml") e.SetResponseBodyString(xml) e.SendResponse(200) end sub Sub GetUDPEvents(userData as object, e as object) mVar = userData.mVar root = CreateObject("roXMLElement") root.SetName("BrightSignUDPEvents") PopulateUDPData(mVar, root) xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) e.AddResponseHeader("Content-type", "text/xml") e.SetResponseBodyString(xml) e.SendResponse(200) end sub Sub GetRemoteData(userData as object, e as object) mVar = userData.mVar root = CreateObject("roXMLElement") root.SetName("BrightSignRemoteData") PopulateIDData(mVar, root) PopulateUDPData(mVar, root) elem = root.AddElement("contentPort") elem.SetBody("8008") elem = root.AddElement("activePresentation") if mVar.activePresentation$ <> invalid then elem.SetBody(mVar.activePresentation$) end if xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) e.AddResponseHeader("Content-type", "text/xml") e.SetResponseBodyString(xml) e.SendResponse(200) end sub Function GetUserVariables(mVar as object) as object userVariablesList = mVar.GetOrderedVariables(mVar.activePresentation$) ' restrict returned user variables to those in the presentation filteredUserVariables = [] for each userVariable in userVariablesList if mVar.currentUserVariables.DoesExist(userVariable.name$) then uv = mVar.currentUserVariables.Lookup(userVariable.name$) if uv.position% <> -1 then userVariable.position% = uv.position% filteredUserVariables.push(userVariable) end if end if next if mVar.sign.alphabetizeVariableNames then BubbleSortUserVariables(filteredUserVariables, "name$") else BubbleSortUserVariables(filteredUserVariables, "position%") end if return filteredUserVariables end function Sub BubbleSortUserVariables(userVariables as object, sortKey$ as string) if type(userVariables) = "roArray" then n = userVariables.Count() while n <> 0 newn = 0 for i = 1 to (n - 1) if userVariables[i - 1].Lookup(sortKey$) > userVariables[i].Lookup(sortKey$) then k = userVariables[i] userVariables[i] = userVariables[i - 1] userVariables[i - 1] = k newn = i end if next n = newn end while end if end sub Sub PopulateUserVarData(mVar as object, root as object) userVariables = GetUserVariables(mVar) for each userVariable in userVariables variableName = userVariable.name$ elem = root.AddElement("BrightSignVar") elem.AddAttribute("name", variableName) elem.SetBody(userVariable.GetCurrentValue()) next end sub Sub GetUserVars(userData as object, e as object) mVar = userData.mVar root = CreateObject("roXMLElement") root.SetName("BrightSignUserVariables") PopulateUserVarData(mVar, root) xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) e.AddResponseHeader("Content-type", "text/xml; charset=utf-8") e.SetResponseBodyString(xml) e.SendResponse(200) end sub Sub GetUserVariableCategories(userData as object, e as object) mVar = userData.mVar root = CreateObject("roXMLElement") root.SetName("BrightSignUserVariableCategories") userVariableCategoryList = mVar.GetUserVariableCategoryList(mVar.activePresentation$) for each userVariableCategory in userVariableCategoryList elem = root.AddElement("BrightSignUserVariableCategory") elem.SetBody(userVariableCategory) next xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) e.AddResponseHeader("Content-type", "text/xml; charset=utf-8") e.SetResponseBodyString(xml) e.SendResponse(200) end sub Sub UpdateFeedByCategory(userData as object, e as object) mVar = userData.mVar categoryName$ = e.GetRequestParam("CategoryName") updateDataFeedByCategoryMsg = { } updateDataFeedByCategoryMsg["EventType"] = "UPDATE_DATA_FEED_BY_CATEGORY" updateDataFeedByCategoryMsg["Name"] = categoryName$ mVar.msgPort.PostMessage(updateDataFeedByCategoryMsg) e.SendResponse(200) end sub Sub UpdateAllFeeds(userData as object, e as object) mVar = userData.mVar updateDataFeedByCategoryMsg = { } updateDataFeedByCategoryMsg["EventType"] = "UPDATE_ALL_DATA_FEEDS" mVar.msgPort.PostMessage(updateDataFeedByCategoryMsg) e.SendResponse(200) end sub Sub GetUserVariablesByCategory(userData as object, e as object) mVar = userData.mVar root = CreateObject("roXMLElement") root.SetName("BrightSignUserVariablesByCategory") categoryName$ = e.GetRequestParam("CategoryName") if categoryName$ <> "" then userVariablesByCategoryList = mVar.GetUserVariablesByCategoryList(categoryName$) for each userVariable in userVariablesByCategoryList variableName = userVariable.name$ elem = root.AddElement("BrightSignVar") elem.AddAttribute("name", variableName) elem.AddAttribute("mediaUrl", userVariable.mediaUrl$) elem.SetBody(userVariable.GetCurrentValue()) next end if xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) e.AddResponseHeader("Content-type", "text/xml; charset=utf-8") e.SetResponseBodyString(xml) e.SendResponse(200) end sub Sub GetIDInfoPage(userData as object, e as object) mVar = userData.mVar e.AddResponseHeader("Content-type", "text/html; charset=utf-8") deviceIdWebPageFilePath$ = "sys:/web-client/Default_DeviceWebPage/_deviceIdWebPage.html" ret = e.SetResponseBodyFile(deviceIdWebPageFilePath$) if not ret then print e.GetFailureReason() stop end if ret2 = e.SendResponse(200) if not ret2 then print e.GetFailureReason() stop end if end sub Sub FilePosted(userData as object, e as object) destinationFilename = e.GetRequestHeader("Destination-Filename") currentDir$ = "pool/" poolDepth% = 2 while poolDepth% > 0 newDir$ = Left(Right(destinationFilename, poolDepth%), 1) currentDir$ = currentDir$ + newDir$ + "/" CreateDirectory(currentDir$) poolDepth% = poolDepth% - 1 end while regex = CreateObject("roRegEx", "/", "i") fileParts = regex.Split(destinationFilename) fullFilePath$ = currentDir$ + fileParts[1] MoveFile(e.GetRequestBodyFile(), fullFilePath$) e.SetResponseBodyString("RECEIVED") e.SendResponse(200) end sub Function GetContentFiles(topDir$ as string) as object allFiles = { } firstLevelDirs = MatchFiles(topDir$, "*") for each firstLevelDir in firstLevelDirs firstLevelDirSpec$ = topDir$ + firstLevelDir + "/" secondLevelDirs = MatchFiles(firstLevelDirSpec$, "*") for each secondLevelDir in secondLevelDirs secondLevelDirSpec$ = firstLevelDirSpec$ + secondLevelDir + "/" files = MatchFiles(secondLevelDirSpec$, "*") for each file in files allFiles.AddReplace(file, secondLevelDirSpec$) next next next return allFiles end function Sub PopulateSnapshotData(mVar as object, root as object, startTimeSpecified as boolean, startTime as object) globalAA = GetGlobalAA() for each snapshotFile in globalAA.listOfSnapshotFiles ' get timestamp, id from file name index% = instr(1, snapshotFile, ".jpg") if index% > 0 then time$ = mid(snapshotFile, 1, index% - 1) snapshotTime = CreateObject("roDateTime") snapshotTime.FromIsoString(time$) if not startTimeSpecified or snapshotTime.GetString() >= startTime.GetString() then itemElem = root.AddElement("Item") timeElem = itemElem.AddElement("Time") timeElem.SetBody(time$) idElem = itemElem.AddElement("ID") idElem.SetBody(time$) ' imagePathElem = itemElem.AddElement("ImagePath") ' imagePathElem.SetBody("/snapshots/" + snapshotFile) end if end if next end sub ' Called by BrightSignApp, not LFN Function GetSnapshotConfiguration(userData as object, e as object) as object mVar = userData.mVar globalAA = GetGlobalAA() root = CreateObject("roXMLElement") root.SetName("BrightSignSnapshots") root.AddAttribute("Count", StripLeadingSpaces(stri(globalAA.listOfSnapshotFiles.Count()))) settings = GetActiveSettings() if settings.deviceScreenShotsEnabled then strVal = "true" else strVal = "false" end if root.AddAttribute("Enabled", strVal) root.AddAttribute("Interval", StripLeadingSpaces(stri(settings.deviceScreenShotsInterval))) root.AddAttribute("MaxImages", StripLeadingSpaces(stri(settings.deviceScreenShotsCountLimit))) root.AddAttribute("Quality", StripLeadingSpaces(stri(settings.deviceScreenShotsQuality))) if lcase(settings.deviceScreenShotsOrientation) = "landscape" then remoteSnapshotDisplayPortrait = "false" else remoteSnapshotDisplayPortrait = "true" endif root.AddAttribute("DisplayPortraitMode", remoteSnapshotDisplayPortrait) root.AddAttribute("Orientation", settings.deviceScreenShotsOrientation) root.AddAttribute("ResX", StripLeadingSpaces(stri(mVar.videoMode.GetOutputResX()))) root.AddAttribute("ResY", StripLeadingSpaces(stri(mVar.videoMode.GetOutputResY()))) ' CanConfigure attribute notifies clients if snapshot configuration can be managed by this unit root.AddAttribute("CanConfigure", "true") startTimeSpecified = false startDT = CreateObject("roDateTime") startTime$ = e.GetRequestParam("starttime") if startTime$ <> "" then startTimeSpecified = startDT.FromIsoString(startTime$ + ".000") end if PopulateSnapshotData(mVar, root, startTimeSpecified, startDT) xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) e.AddResponseHeader("Content-type", "text/xml") e.SetResponseBodyString(xml) e.SendResponse(200) end function Function GetSnapshot(userData as object, e as object) as object mVar = userData.mVar globalAA = GetGlobalAA() listOfSnapshotFiles = globalAA.listOfSnapshotFiles snapshotID$ = e.GetRequestParam("ID") if snapshotID$ <> "" then ' perform linear search to find image for each snapshotFile in listOfSnapshotFiles if snapshotFile = snapshotID$ + ".jpg" then e.AddResponseHeader("Content-type", "image/jpeg") e.SetResponseBodyFile("snapshots/" + snapshotID$ + ".jpg") e.SendResponse(200) return 0 end if next end if e.AddResponseHeader("Content-type", "text/plain; charset=utf-8") if snapshotID$ <> "" then e.SetResponseBodyString("Snapshot file corresponding to ID " + snapshotID$ + " not found") else e.SetResponseBodyString("Snapshot ID not specified.") end if e.SendResponse(404) end function Function GetBSNStatus(userData as object, e as object) as object mVar = userData.mVar root = CreateObject("roXMLElement") root.SetName("BrightSignID") elem = root.AddElement("bsnActive") if mVar.NetworkingIsActive() then elem.SetBody("yes") else elem.SetBody("no") end if xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) e.AddResponseHeader("Content-type", "text/xml") e.SetResponseBodyString(xml) e.SendResponse(200) end function 'endregion 'region Sync Function FreeSpaceOnDriveJson() as object proposedPublishFiles$ = ReadAsciiFile("filesToPublish.json") ' files that need to be copied by BrightAuthor actualPublishFiles = { } ' files that can be deleted to make room for more content deletionCandidates = { } if len(proposedPublishFiles$) > 0 then currentPoolFiles = GetContentFiles("/pool/") for each file in currentPoolFiles deletionCandidates.AddReplace(file, currentPoolFiles.Lookup(file)) next proposedPublishFiles = ParseJson(proposedPublishFiles$) ' determine total space required totalSpaceRequired! = 0 for each file in proposedPublishFiles.file poolFileName$ = file.poolFileName o = deletionCandidates.Lookup(poolFileName$) if not IsString(o) then ' file is not already on the card fileItem = { } fileItem.AddReplace("poolFileName", file.poolFileName) fileItem.AddReplace("fileName", file.fileName) fileItem.AddReplace("filePath", file.filePath) fileItem.AddReplace("hash", file.hash) fileItem.AddReplace("size", file.size) actualPublishFiles.AddReplace(poolFileName$, fileItem) ' files that need to be copied to the card fileSize% = fileItem.size totalSpaceRequired! = totalSpaceRequired! + fileSize% end if next proposedPublishFiles = invalid ' determine if additional space is required du = CreateObject("roStorageInfo", "./") freeInMegabytes! = du.GetFreeInMegabytes() totalFreeSpace! = freeInMegabytes! * 1048576 ' print "totalFreeSpace = "; totalFreeSpace!;", totalSpaceRequired = ";totalSpaceRequired! if m.limitStorageSpace then budgetedMaximumPoolSize = 0 if m.spaceLimitedByAbsoluteSize = "true" then budgetedMaximumPoolSize = m.publishedDataSizeLimitMB * (1024.0 * 1024.0) else totalCardSize = du.GetSizeInMegabytes() * (1024.0 * 1024.0) publishedDataSizeLimitPercentage% = m.publishedDataSizeLimitPercentage budgetedMaximumPoolSize = (publishedDataSizeLimitPercentage% / 100.0) * totalCardSize end if ' calculate the space that will be used after the files are copied over (size of existing pool + size of files that are getting copied over) ' totalSpaceRequired! = size of files that are getting copied over totalSizeOfPoolAfterCopy! = totalSpaceRequired! ' units are bytes for each file in currentPoolFiles relativePath$ = currentPoolFiles.Lookup(file) fullPath$ = relativePath$ + file ' get size of file size% = GetFileSize(fullPath$) totalSizeOfPoolAfterCopy! = totalSizeOfPoolAfterCopy! + size% next end if deleteUnneededFiles = false if totalFreeSpace! < totalSpaceRequired! then deleteUnneededFiles = true end if if m.limitStorageSpace then if totalSizeOfPoolAfterCopy! > budgetedMaximumPoolSize then deleteUnneededFiles = true end if end if if deleteUnneededFiles then ' parse local-sync.json - remove its files from deletionCandidates localSync$ = ReadAsciiFile("local-sync.json") if len(localSync$) > 0 then localSync = ParseJson(localSync$) for each file in localSync.files.download hashValue$ = file.hash.hex hashMethod$ = file.hash.method fileName$ = hashMethod$ + "-" + hashValue$ fileExisted = deletionCandidates.Delete(fileName$) next end if ' remove 'new' files from deletionCandidates proposedPublishFiles = ParseJson(proposedPublishFiles$) for each file in proposedPublishFiles.file poolFileName$ = file.poolFileName fileExisted = deletionCandidates.Delete(poolFileName$) next ' delete files from deletionCandidates until totalFreeSpace! > totalSpaceRequired! ' if the user has limited storage space for pool files, delete content until that limitation is reached for each fileToDelete in deletionCandidates path$ = deletionCandidates.Lookup(fileToDelete) pathOnCard$ = path$ + fileToDelete if m.limitStorageSpace then size% = GetFileSize(pathOnCard$) totalSizeOfPoolAfterCopy! = totalSizeOfPoolAfterCopy! - size% end if deletionCandidates.Delete(fileToDelete) DeleteFile(pathOnCard$) continueDeleting = false if m.limitStorageSpace then if totalSizeOfPoolAfterCopy! > budgetedMaximumPoolSize then continueDeleting = true end if else du = invalid du = CreateObject("roStorageInfo", "./") freeInMegabytes! = du.GetFreeInMegabytes() totalFreeSpace! = freeInMegabytes! * 1048576 ' print "Delete file ";pathOnCard$ ' print "totalFreeSpace = "; totalFreeSpace!;", totalSpaceRequired = ";totalSpaceRequired! if totalFreeSpace! <= totalSpaceRequired! then continueDeleting = true end if end if if not continueDeleting then return actualPublishFiles end if next ' the way this code is currently written, this method will return 'fail' if we can't delete enough files to get to the budgeted amount return -1 end if end if return actualPublishFiles end function Function GetFileSize(filePath$ as string) as integer size = 0 checkFile = CreateObject("roReadFile", filePath$) if (checkFile <> invalid) then checkFile.SeekToEnd() size = checkFile.CurrentPosition() checkFile = invalid end if return size end function Function ConvertToInt(str as string) as integer if str = "" then return 0 end if return int(val(str)) end function 'endregion 'region Presentation Sub ConfigureGPIOInput(buttonNumber$ as string) buttonNumber% = int(val(buttonNumber$)) m.gpioStateMachineRequired[buttonNumber%] = true end sub Sub ConfigureGPIOButton(buttonNumber$ as string, gpioSpec as object) buttonNumber% = int(val(buttonNumber$)) if type(m.gpioSM[buttonNumber%]) = "roAssociativeArray" then m.gpioSM[buttonNumber%].ConfigureButton(gpioSpec) end if end sub Sub ConfigureBPInput(buttonPanelIndex% as integer, buttonNumber$ as string) if buttonNumber$ = "-1" then for i% = 0 to 10 m.bpStateMachineRequired[buttonPanelIndex%, i%] = true next else buttonNumber% = int(val(buttonNumber$)) m.bpStateMachineRequired[buttonPanelIndex%, buttonNumber%] = true m.bpInputUsed[buttonPanelIndex%, buttonNumber%] = true end if end sub Sub ConfigureBPButton(buttonPanelIndex% as integer, buttonNumber$ as string, bpSpec as object) if buttonNumber$ = "-1" then for i% = 0 to 10 if type(m.bpSM[buttonPanelIndex%, i%]) = "roAssociativeArray" then m.bpSM[buttonPanelIndex%, i%].ConfigureButton(bpSpec) end if next else buttonNumber% = int(val(buttonNumber$)) if type(m.bpSM[buttonPanelIndex%, buttonNumber%]) = "roAssociativeArray" then m.bpSM[buttonPanelIndex%, buttonNumber%].ConfigureButton(bpSpec) end if end if end sub Function NewGlobalVariables() as object globalVariables = { } globalVariables.language$ = "eng" return globalVariables end function Function jsonParseScriptPlugin(scriptPluginSpec as object) as object scriptPlugin = { } scriptPlugin.name$ = scriptPluginSpec.name scriptPlugin.plugin = invalid return scriptPlugin end function Function jsonParseVideoModePlugin(videoModePluginSpec As Object) As Object videoModePlugin = {} videoModePlugin.name$ = videoModePluginSpec.name videoModePlugin.functionName$ = videoModePluginSpec.functionName return videoModePlugin End Function Function jsonParseParserPlugin(parserPluginSpec as object) as object parserPlugin = { } parserPlugin.name$ = parserPluginSpec.name parserPlugin.parseFeedFunction$ = parserPluginSpec.feedParserFunctionName parserPlugin.parseUVFunction$ = parserPluginSpec.userVariableParserFunctionName parserPlugin.userAgentFunction$ = parserPluginSpec.userAgentParserFunctionName return parserPlugin end function Function getParserPlugin(bsp as object, parserPluginName as object) as object if Len(parserPluginName) > 0 then for each plugin in bsp.parserPlugins if plugin.name$ = parserPluginName then return plugin end if next end if return invalid end function Function newNodeApp(bsp as object, nodeAppDescription as object) as object nodeApp = { } nodeApp.name$ = nodeAppDescription.name$ nodeApp.prefix$ = nodeAppDescription.prefix$ nodeApp.filePath$ = nodeAppDescription.filePath$ return nodeApp End Function Function newHTMLSite(bsp as object, htmlSiteDescription as object) as object htmlSite = { } htmlSite.name$ = htmlSiteDescription.name$ htmlSite.isNodeServer = htmlSiteDescription.enableNode htmlSite.queryString = newParameterValue(bsp, htmlSiteDescription.queryString) if htmlSiteDescription.contentIsLocal then htmlSite.prefix$ = htmlSiteDescription.prefix$ htmlSite.filePath$ = htmlSiteDescription.filePath$ else htmlSite.url = newParameterValue(bsp, htmlSiteDescription.url) end if htmlSite.contentIsLocal = htmlSiteDescription.contentIsLocal return htmlSite end function Function newLiveDataFeed(bsp as object, liveDataFeedDescription as object) as object liveDataFeed = InitializeLiveDataFeedParameters(bsp) liveDataFeed.id$ = liveDataFeedDescription.id$ liveDataFeed.isLiveBSNDataFeed = liveDataFeedDescription.isLiveBSNDataFeed liveDataFeed.isDynamicPlaylist = liveDataFeedDescription.isDynamicPlaylist liveDataFeed.isLiveMediaFeed = liveDataFeedDescription.isLiveMediaFeed liveDataFeed.url = newParameterValue(bsp, liveDataFeedDescription.urlPV) parserPluginName = liveDataFeedDescription.parserPluginName uvParserPluginName = liveDataFeedDescription.uvParserPluginName ' look at main parser for feed first - all functions "should" be in that one ' older versions of BA were able to specify a second parser for User Vars, however, so we check for that if necessary parser = getParserPlugin(bsp, parserPluginName) if parser <> invalid then liveDataFeed.parser$ = parser.parseFeedFunction$ liveDataFeed.uvParser$ = parser.parseUVFunction$ liveDataFeed.customUserAgent$ = parser.userAgentFunction$ if liveDataFeed.uvParser$ = "" then ' Backward compatibility check - see if UV parser plugin separately defined parser = getParserPlugin(bsp, uvParserPluginName) if parser <> invalid then liveDataFeed.uvParser$ = parser.parseUVFunction$ end if end if end if liveDataFeed.updateInterval% = liveDataFeedDescription.updateInterval% liveDataFeed.useHeadRequest = liveDataFeedDescription.useHeadRequest liveDataFeed.usage$ = lcase(liveDataFeedDescription.usage$) liveDataFeed.autoGenerateUserVariables = liveDataFeedDescription.autoGenerateUserVariables liveDataFeed.userVariableAccess$ = liveDataFeedDescription.userVariableAccess$ SetLiveDataFeedHandlers(liveDataFeed) return liveDataFeed end function Function newLiveDataFeedWithAuthData(bsp as object, url as object, authData as object, updateInterval% as integer) as object liveDataFeed = InitializeLiveDataFeedParameters(bsp) liveDataFeed.id$ = CleanName(url.GetCurrentParameterValue()) liveDataFeed.url = url liveDataFeed.authenticationData = authData liveDataFeed.updateInterval% = updateInterval% liveDataFeed.useHeadRequest = false SetLiveDataFeedHandlers(liveDataFeed) return liveDataFeed end function Function InitializeLiveDataFeedParameters(bsp as object) liveDataFeed = { } liveDataFeed.bsp = bsp liveDataFeed.id$ = "" liveDataFeed.title$ = "" liveDataFeed.url = "" liveDataFeed.parser$ = "" liveDataFeed.uvParser$ = "" liveDataFeed.customUserAgent$ = "" liveDataFeed.updateInterval% = 0 liveDataFeed.usage$ = "text" liveDataFeed.isMRSSFeed = false liveDataFeed.isDynamicPlaylist = false liveDataFeed.isLiveMediaFeed = false liveDataFeed.headRequest = false liveDataFeed.useHeadRequest = false liveDataFeed.autoGenerateUserVariables = false liveDataFeed.forceUpdate = false liveDataFeed.restrictNumberOfItems = false liveDataFeed.numberOfItemsToDisplay = -1 liveDataFeed.feedContentFilesToDownload = { } return liveDataFeed end function Sub SetLiveDataFeedHandlers(liveDataFeed as object) liveDataFeed.ReadFeedContent = ReadFeedContent liveDataFeed.ReadLiveFeedContent = ReadLiveFeedContent liveDataFeed.ParseSimpleRSSFeed = ParseSimpleRSSFeed liveDataFeed.ParseJSONRSS = ParseJSONRSS liveDataFeed.ParseRSSWithParserPlugin = ParseRSSWithParserPlugin liveDataFeed.ReadMRSSContent = ReadMRSSContent liveDataFeed.DownloadLiveFeedContent = DownloadLiveFeedContent liveDataFeed.DownloadMRSSContent = DownloadMRSSContent liveDataFeed.ParseMRSSFeed = ParseMRSSFeed liveDataFeed.FeedIsMRSS = FeedIsMRSS liveDataFeed.RestartLiveDataFeedDownloadTimer = RestartLiveDataFeedDownloadTimer liveDataFeed.HandleLiveDataFeedContentDownloadAssetFetcherEvent = HandleLiveDataFeedContentDownloadAssetFetcherEvent liveDataFeed.HandleLiveDataFeedContentDownloadAssetFetcherProgressEvent = HandleLiveDataFeedContentDownloadAssetFetcherProgressEvent liveDataFeed.ConvertMRSSFormatToContent = ConvertMRSSFormatToContent liveDataFeed.ParseCustomContentFormat = ParseCustomContentFormat end sub Function CleanName(input as string) as string charsToReplace = ["/", ":", ",", ".", "&", "=", "?"] output = input for each charToReplace in charsToReplace index = 1 while index <> 0 index = instr(1, output, charToReplace) if index <> 0 then part1 = "" if (index - 1) > 0 then part1 = mid(output, 0, index - 1) end if part2 = "" if (len(output) - index) > 0 then part2 = mid(output, index + 1, len(output) - index) end if output = part1 + "-" + part2 end if end while next return output end function Sub Restart(presentationName$ as string) globalAA = GetGlobalAA() currentSyncSpec = GetActiveSyncSpec() if not type(currentSyncSpec) = "roSyncSpec" then stop end if m.restartPendingMediaEnd = false m.dontChangePresentationUntilMediaEndEventReceived = false m.currentUserVariables = { } m.liveDataFeeds = { } m.presentations = { } m.nodeApps = { } m.htmlSites = { } m.beacons = { } m.scriptPlugins = [] m.videoModePlugins = [] m.parserPlugins = [] m.additionalPublishedFiles = [] m.bypassProxyHosts = [] m.IsSyncMaster = false m.IsSyncSlave = false for n% = 0 to 3 for i% = 0 to 10 m.bpStateMachineRequired[n%, i%] = false m.bpInputUsed[n%, i%] = false m.bpOutputUsed[n%, i%] = false next next for i% = 0 to 7 m.gpioStateMachineRequired[i%] = false next if presentationName$ = "" then autoscheduleFileContents$ = ReadAsciiFile(globalAA.autoscheduleFilePath$) if autoscheduleFileContents$ = "" then stop end if schedule = m.GetAutoschedule(globalAA.autoscheduleFilePath$) if type(schedule.activeScheduledEvent) = "roAssociativeArray" then autoplayPath$ = schedule.autoplayPoolFile$ else autoplayPath$ = "" end if m.schedule = schedule if (autoplayPath$ <> "") then presentationName$ = schedule.activeScheduledEvent.presentationName$ end if else autoplayFileName$ = GetAutoplayFileName(presentationName$) autoplayPath$ = m.assetPoolFiles.GetPoolFilePath(autoplayFileName$) m.activePresentation$ = presentationName$ end if ' Clean up Node.js Enabled roHtmlWidget and Node.js Apps and their 'view' references m.DeallocateNodeComponents() if (autoplayPath$ <> "") then brightAuthor = GetAutoplay(autoplayPath$) m.diagnostics.PrintTimestamp() m.diagnostics.PrintDebug("### create sign object from autoplay") version% = GetAutoplayVersion(brightAuthor) m.sysInfo.baconVersion$ = GetBaconVersion(brightAuthor) sign = newSign(brightAuthor, m.globalVariables, m, m.msgPort, m.controlPort, version%) m.LogActivePresentation() else sign = invalid if globalAA.registrySettings.setupSplashScreenEnabled = "True" then videoMode = CreateObject("roVideoMode") if type(videoMode) = "roVideoMode" then m.deviceSetupSplashScreen = SetDeviceSetupSplashScreen("lfn", m.msgPort) end if else ' set idle screen color b = CreateObject("roByteArray") b.FromHexString(globalAA.registrySettings.idleScreenColor$) color_spec% = (255 * 256 * 256 * 256) + (b[1] * 256 * 256) + (b[2] * 256) + b[3] m.diagnostics.PrintDebug("Set idle screen color: red = " + stri(b[1]) + ", green = " + stri(b[2]) + ", blue = " + stri(b[3])) videoMode = GetVideoMode() if type(videoMode) = "roVideoMode" then videoMode.SetBackgroundColor(color_spec%) videoMode = invalid endif end if end if if type(m.sign) = "roAssociativeArray" and type(m.sign.zonesHSM) = "roArray" then for each zoneHSM in m.sign.zonesHSM if IsAudioPlayer(zoneHSM.audioPlayer) then zoneHSM.audioPlayer.Stop() zoneHSM.audioPlayer = invalid end if if type(zoneHSM.videoPlayer) = "roVideoPlayer" then zoneHSM.videoPlayer.Stop() zoneHSM.videoPlayer = invalid end if next end if zoneHSM = invalid m.dispatchingZone = invalid m.sign = invalid RunGarbageCollector() m.sign = sign if type(m.sign) = "roAssociativeArray" then if m.sign.enableSettingsHandler <> globalAA.supervisorEnableSettingsHandler then if m.sign.enableSettingsHandler then enableSettingsHandler = GetEnableSettingsHandler() else enableSettingsHandler = false endif globalAA.supervisorEnableSettingsHandler = enableSettingsHandler SendAutorunCapabilitiesToSupervisorViaPost([], "") endif ' initialize audio configuration audioConfiguration = CreateObject("roAudioConfiguration") if type(audioConfiguration) = "roAudioConfiguration" then audioConfiguration$ = lcase(m.sign.audioConfiguration$) audioAutoLevel = m.sign.audioAutoLevel if audioAutoLevel = true then m.diagnostics.PrintDebug("Debug: audioAutoLevel is set to true") else if audioAutoLevel = false then m.diagnostics.PrintDebug("Debug: audioAutoLevel is set to false") end if if audioConfiguration$ = "mixedaudiopcmonly" then if audioAutoLevel = true then audioRouting = { mode: "prerouted", autolevel: "on", pcmonly: "true", srcrate: 48000 } else audioRouting = { mode: "prerouted", autolevel: "off", pcmonly: "true", srcrate: 48000 } end if else if audioConfiguration$ = "mixedaudiopcmcompressed" then if audioAutoLevel = true then audioRouting = { mode: "prerouted", autolevel: "on", pcmonly: "false", srcrate: 48000 } else audioRouting = { mode: "prerouted", autolevel: "off", pcmonly: "false", srcrate: 48000 } end if else audioRouting = { mode : "dynamic" } end if ok = audioConfiguration.ConfigureAudio(audioRouting) if not ok then m.diagnostics.PrintDebug("Configure audio failure: " + audioConfiguration$) end if end if ' initialize remote m.ConfigureIRRemote() else globalAA.supervisorEnableSettingsHandler = GetEnableSettingsHandler() SendAutorunCapabilitiesToSupervisorViaPost([], "") end if ' create required GPIO state machines for i% = 0 to 7 if m.gpioStateMachineRequired[i%] then m.gpioSM[i%] = newGPIOStateMachine(m, m.controlPort, m.controlPortIdentity, i%) else m.gpioSM[i%] = invalid end if next ' create, initialize, and configure required BP state machines and BP's for buttonPanelIndex% = 0 to 3 if type(m.bpInputPorts[buttonPanelIndex%]) = "roControlPort" then configuration% = m.bpInputPortConfigurations[buttonPanelIndex%] else configuration% = 0 end if for i% = 0 to 10 if (configuration% and (2 ^ i%)) <> 0 then forceUsed = true else forceUsed = false end if ' if m.bpStateMachineRequired[buttonPanelIndex%, i%] then if (m.bpInputUsed[buttonPanelIndex%, i%] or forceUsed) and IsString(m.bpInputPortIdentities[buttonPanelIndex%]) and type(m.bpInputPorts[buttonPanelIndex%]) = "roControlPort" then m.bpSM[buttonPanelIndex%, i%] = newBPStateMachine(m, m.bpInputPortIdentities[buttonPanelIndex%], buttonPanelIndex%, i%) else m.bpSM[buttonPanelIndex%, i%] = invalid end if next next m.ConfigureBPs() for buttonPanelIndex% = 0 to 3 if type(m.bpOutputSetup[buttonPanelIndex%]) = "roControlPort" then configuration% = m.bpInputPortConfigurations[buttonPanelIndex%] ' set bits in our mask for buttons we want to disable (not enable!) loopFlag% = 1 buttonFlag% = 0 for i% = 0 to 10 if (configuration% and (2 ^ i%)) <> 0 then forceUsed = true else forceUsed = false end if ' if not (m.bpInputUsed[buttonPanelIndex%, i%] or m.bpOutputUsed[buttonPanelIndex%, i%]) then ' if not m.bpStateMachineRequired[buttonPanelIndex%, i%] then if not (m.bpInputUsed[buttonPanelIndex%, i%] or forceUsed) then buttonFlag% = buttonFlag% + loopFlag% end if loopFlag% = loopFlag% * 2 next ' the 1 here is the position of the mask for disabling buttons m.bpOutputSetup[buttonPanelIndex%].SetOutputValue(1, buttonFlag%) loopFlag% = 1 ledFlag% = 0 for i% = 0 to 10 if (configuration% and (2 ^ i%)) <> 0 then forceUsed = true else forceUsed = false end if if not (m.bpInputUsed[buttonPanelIndex%, i%] or m.bpOutputUsed[buttonPanelIndex%, i%] or forceUsed) then ledFlag% = ledFlag% + loopFlag% end if loopFlag% = loopFlag% * 2 next ' the 2 here signifies the mask position for LED disabling m.bpOutputSetup[buttonPanelIndex%].SetOutputValue(2, ledFlag%) end if next ' reset connector volumes m.analogVolume% = 100 m.hdmiVolume% = 100 m.spdifVolume% = 100 ' UsbCleanup m.usbVolumeA% = 100 m.usbVolumeB% = 100 m.usbVolumeTypeA% = 100 m.usbVolumeTypeC% = 100 m.usbVolumeA1% = 100 m.usbVolumeA2% = 100 m.usbVolumeA3% = 100 m.usbVolumeA4% = 100 m.usbVolumeA5% = 100 m.usbVolumeA6% = 100 m.usbVolumeA7% = 100 ' reclaim memory RunGarbageCollector() ' user variable web server settings = globalAA.settings if type(m.sign) = "roAssociativeArray" and (settings.lwsConfig = "c" or settings.lwsConfig = "s") then lwsUserName$ = settings.lwsUserName lwsPassword$ = settings.lwsPassword if (len(lwsUserName$) + len(lwsPassword$)) > 0 then credentials = { } credentials.AddReplace(lwsUserName$, lwsPassword$) else credentials = invalid end if ' support HTTPS configuration if certificate files exists deviceWebPageKeyFileName$ = "dwp.key" deviceWebPageCertFileName$ = "dwp.pem" serverOptions = { port: 8008 } if FileExists(deviceWebPageCertFileName$) then httpsOptions = { certificate_file: deviceWebPageCertFileName$ } if FileExists(deviceWebPageKeyFileName$) then httpsOptions.AddReplace("key_file", deviceWebPageKeyFileName$) end if serverOptions.AddReplace("https", httpsOptions) end if m.sign.localServer = CreateObject("roHttpServer", serverOptions) if (m.sign.localServer = invalid or type(m.sign.localServer) <> "roHttpServer") then ' print the error and continue the process m.diagnostics.PrintDebug("### failed to create user variable web server") else m.sign.localServer.SetPort(m.msgPort) m.sign.GetUserVarsAA = { HandleEvent: GetUserVars, mVar: m } m.sign.GetConfigurationPageAA = { HandleEvent: GetConfigurationPage, mVar: m } m.sign.GetUDPEventsAA = { HandleEvent: GetUDPEvents, mVar: m } m.sign.SendUdpRestAA = { HandleEvent: SendUdpRest, mVar: m } m.sign.SetValuesAA = { HandleEvent: SetValues, mVar: m } m.sign.SetValuesByCategoryAA = { HandleEvent: SetValuesByCategory, mVar: m } m.sign.GetUserVariableCategoriesAA = { HandleEvent: GetUserVariableCategories, mVar: m } m.sign.GetUserVariablesByCategoryAA = { HandleEvent: GetUserVariablesByCategory, mVar: m } m.sign.UpdateFeedByCategoryAA = { HandleEvent: UpdateFeedByCategory, mVar: m } m.sign.UpdateAllFeedsAA = { HandleEvent: UpdateAllFeeds, mVar: m } m.sign.localServer.AddGetFromFile({ url_path: "/GetAutorun", content_type: "text/plain; charset=utf-8", filename: "autorun.brs" }) m.sign.localServer.AddGetFromEvent({ url_path: "/GetUserVars", user_data: m.sign.GetUserVarsAA }) m.sign.localServer.AddGetFromEvent({ url_path: "/", user_data: m.sign.GetConfigurationPageAA, passwords: credentials }) m.sign.localServer.AddGetFromEvent({ url_path: "/GetUDPEvents", user_data: m.sign.GetUDPEventsAA }) m.sign.localServer.AddGetFromEvent({ url_path: "/GetUserVariableCategories", user_data: m.sign.GetUserVariableCategoriesAA }) m.sign.localServer.AddGetFromEvent({ url_path: "/GetUserVariablesByCategory", user_data: m.sign.GetUserVariablesByCategoryAA }) m.sign.localServer.AddGetFromEvent({ url_path: "/UpdateFeedByCategory", user_data: m.sign.UpdateFeedByCategoryAA }) m.sign.localServer.AddGetFromEvent({ url_path: "/UpdateAllFeeds", user_data: m.sign.UpdateAllFeedsAA }) m.sign.localServer.AddPostToFormData({ url_path: "/SetValues", user_data: m.sign.SetValuesAA, passwords: credentials }) m.sign.localServer.AddPostToFormData({ url_path: "/SetValuesByCategory", user_data: m.sign.SetValuesByCategoryAA, passwords: credentials }) m.sign.localServer.AddPostToFormData({ url_path: "/SendUDP", user_data: m.sign.SendUdpRestAA }) end if end if userVariables = m.currentUserVariables ' by default, process roStreamEnd events - initialize value here and give plugin a chance to override this behaviour. m.pluginProcessesStreamEndEvent = false ' if there are script plugins associated with this sign, initialize them here ERR_NORMAL_END = &hFC for each scriptPlugin in m.scriptPlugins initializeFunction$ = "result = " + scriptPlugin.name$ + "_Initialize(m.msgPort, userVariables, m)" retVal = eval(initializeFunction$) if type(retVal) = "roList" then ' log the failure m.diagnostics.PrintDebug("Failure executing Eval to initialize script plugin file: error string was " + retVal[0].ERRSTR + ", line number was " + stri(retVal[0].LINENO) + ", call was " + initializeFunction$) m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SCRIPT_PLUGIN_FAILURE, retVal[0].ERRSTR + chr(9) + stri(retVal[0].LINENO) + chr(9) + scriptPlugin.name$) else if retVal <> ERR_NORMAL_END then ' log the failure m.diagnostics.PrintDebug("Failure executing Eval to initialize script plugin file: return value = " + stri(retVal) + ", call was " + initializeFunction$) m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SCRIPT_PLUGIN_FAILURE, stri(retVal) + chr(9) + scriptPlugin.name$) else scriptPlugin.plugin = result end if next ' device web page m.deviceWebPageFilePath$ = "" if type(m.sign) = "roAssociativeArray" and m.sign.deviceWebPageDisplay$ <> "None" and type(m.sign.localServer) = "roHttpServer" then ' get all the files in the device web page if m.sign.deviceWebPageDisplay$ = "Custom" and m.sign.customDeviceWebPage <> invalid then ' files in the custom device web page start with either ' -customDeviceWebPage- ' or ' -customDeviceWebPage- downloadFiles = currentSyncSpec.GetFileList("download") for each downloadFile in downloadFiles fileName$ = downloadFile.name ' the main web page is ' / name$ = m.sign.customDeviceWebPage.name indexFileName$ = m.sign.customDeviceWebPage.indexFileName indexPoolPath$ = name$ + "/" + indexFileName$ if fileName$ = indexPoolPath$ then m.deviceWebPageFilePath$ = GetPoolFilePath(m.assetPoolFiles, fileName$) else indexOfMarker% = instr(0, fileName$, name$) if indexOfMarker% > 0 then strippedFileName$ = mid(fileName$, indexOfMarker% + len(name$)) ' other asset ext = GetFileExtension(strippedFileName$) if ext <> invalid then contentType$ = GetMimeTypeByExtension(ext) if contentType$ <> invalid then url$ = strippedFileName$ filePath$ = GetPoolFilePath(m.assetPoolFiles, fileName$) m.sign.localServer.AddGetFromFile({ url_path: url$, filename: filePath$, content_type: contentType$ }) end if end if end if end if next else m.deviceWebPageFilePath$ = GetPoolFilePath(m.assetPoolFiles, "Default_PresentationWebPage/_deviceWebPage.html") end if end if plugins = CreateObject("roArray", 1, true) ' plugun info should only get from current running presentation, not all presentations ' TODO: construct plugin payload once scripts are redesigned to load for each presentation if type(m.sign) = "roAssociativeArray" and type(currentSyncSpec) = "roSyncSpec" then ' Construct status payload to send to bootstrap scriptFiles = currentSyncSpec.FilterFiles("download", { group: "script" }) scriptDownloadSection = scriptFiles.getFileList("download") for each scriptFile in scriptDownloadSection fileName = scriptFile.name if fileName <> "" and fileName <> "autoplugins.brs" and fileName <> "autorun.brs" then pluginFile = CreateObject("roAssociativeArray") pluginFile.AddReplace("fileName",fileName) pluginFile.AddReplace("fileSize",scriptFile.size) pluginFile.AddReplace("fileHash",scriptFile.hash) plugins.push(pluginFile) end if next end if m.ResendAutorunCapabilitiesToSupervisor(plugins, presentationName$) assetCollection = currentSyncSpec.GetAssets("download") m.view = CreateObject("roAssetCollectionView", m.assetPool, assetCollection) m.nodeJsObjects = [] ' Launch node apps for each nodeAppName in m.nodeApps nodeApp = m.nodeApps.Lookup(nodeAppName) syncSpecFileName$ = nodeApp.prefix$ + nodeApp.filePath$ m.nodeJsObjects.push(CreateObject("roNodeJs", m.view.getpath() + nodeApp.prefix$ + nodeApp.filePath$, {})) next ' Notify controlling devices we have started playback m.SendUDPNotification("startPlayback") end sub ' This function is responsible for deallocating Node.js Enabled roHtmlWidgets, Node.js Apps, and their roAssetCollectionView. ' The roHtmlWidgets and Node.js Apps are deallocated prior to deallocating their reference to roAssetCollectionView. ' (hence the usage of 'components' in the function name referring to both "Node.js Enabled roHtmlWidgets" and Node.js Apps, ' and their references to view) Sub DeallocateNodeComponents() temporaryHtmlWidgetViews = [] ' Deallocate each zone's and zone's state table's Node.js enabled roHtmlWidget references from prior presentation's scope if type(m.sign) = "roAssociativeArray" and type(m.sign.zonesHSM) = "roArray" then for each zoneHSM in m.sign.zonesHSM for each key in zoneHSM.stateTable if zoneHSM.stateTable[key].type$ = "html5" and zoneHSM.stateTable[key].isNodeServer then ' Save temporary view reference to invalidate roHtmlWidgets prior to invalidating their 'view' if zoneHSM.stateTable[key].view <> invalid then temporaryHtmlWidgetViews.push(zoneHSM.stateTable[key].view) end if end if next ' Assume all loadingHtmlWidget and displayedhtmlwidget are Node.js enabled. ' There is no current method as of 11/4/22 indicating they are a Node.js enabled object. ' Deallocate loadingHtmlWidget which may be used for preparing next html widget to display zoneHSM.loadingHtmlWidget = InvalidateNodeHtmlWidget(zoneHSM.loadingHtmlWidget) zoneHSM.loadinghtmlwidgetparams = invalid ' Deallocate displayingHtmlWidget which may be currently displaying an html widget zoneHSM.displayedhtmlwidget = InvalidateNodeHtmlWidget(zoneHSM.displayedhtmlwidget) ' Deallocate each roHtmlWidget's view reference for i% = 0 to temporaryHtmlWidgetViews.Count() - 1 temporaryHtmlWidgetViews[i%] = invalid next ' Empty out array for next zone temporaryHtmlWidgetViews = [] next end if ' Deallocate nodeJsObject references and their 'view' from prior presentation running Node Apps if type(m.nodeJsObjects) = "roArray" then for i% = 0 to m.nodeJsObjects.Count() - 1 m.nodeJsObjects[i%] = invalid next ' Each Node.js App references the base directory path of 'view', invalidating the global view ' invalidates all Node.js App view references GetGlobalAA().bsp.view = invalid end if end sub Function InvalidateNodeHtmlWidget(htmlWidget as object) as object if type(htmlWidget) = "roHtmlWidget" then htmlWidget.Hide() ' Not necessary, call to avoid possibly visual artifacts htmlWidget = invalid end if return htmlWidget end function Function GetFileExtension(file as string) as object s = file.tokenize(".") if s.Count() > 1 ext = s.pop() return ext end if return invalid end function Function getMediumFromMimeType(mimeType as string) as string if mimeType = "audio/mpeg" or mimeType = "audio/ogg" or mimeType = "audio/flac" or mimeType = "audio/aac" or mimeType = "audio/mp4" or mimeType = "audio/ac3" or mimeType = "audio/eac3" then return "audio" endif if mimeType = "video/mpeg" or mimeType = "video/mp4" or mimeType = "video/mpeg" or mimeType = "video/quicktime" or mimeType = "video/x-matroska" then return "video" endif return "image" end function Function GetMimeTypeByExtension(ext as string) as string ' start with audio types ' if ext = "mp3" return "audio/mpeg" else if ext = "ogg" return "audio/ogg" else if ext = "flac" return "audio/flac" else if ext = "aac" return "audio/aac" else if ext = "m4a" return "audio/mp4" else if ext = "ac3" return "audio/ac3" else if ext = "eac3" return "audio/eac3" ' now image types ' else if ext = "gif" return "image/gif" else if ext = "jpeg" return "image/jpeg" else if ext = "jpg" return "image/jpeg" else if ext = "png" return "image/png" else if ext = "svg" return "image/svg+xml" ' now text types' else if ext = "css" return "text/css" else if ext = "js" return "application/JavaScript" else if ext = "csv" return "text/csv" else if ext = "html" return "text/html" else if ext = "htm" return "text/html" else if ext = "txt" return "text/plain" else if ext = "xml" return "text/xml" ' now some video types' else if ext = "mpeg" return "video/mpeg" else if ext = "mp4" return "video/mp4" else if ext = "ts" return "video/mpeg" else if ext = "mov" return "video/quicktime" else if ext = "mkv" return "video/x-matroska" end if return "" end function Sub LogActivePresentation() if type(m.activePresentation$) = "roString" then activePresentation$ = m.activePresentation$ else activePresentation$ = "" end if m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_START_PRESENTATION, activePresentation$) end sub Sub UpdateEdidUserVariables(postMsg as boolean, videoConnector$ as string) userVariables = m.currentUserVariables suffix$ = "$" if videoConnector$ <> "" then suffix$ = "_" + videoConnector$ + "$" for each userVariableKey in userVariables userVariable = userVariables.Lookup(userVariableKey) if userVariable.systemVariable$ = "EdidMonitorSerialNumber" then userVariable.SetCurrentValue(m.sysInfo["edidMonitorSerialNumber"+suffix$], postMsg) else if userVariable.systemVariable$ = "EdidYearOfManufacture" then userVariable.SetCurrentValue(m.sysInfo["edidYearOfManufacture"+suffix$], postMsg) else if userVariable.systemVariable$ = "EdidMonitorName" then userVariable.SetCurrentValue(m.sysInfo["edidMonitorName"+suffix$], postMsg) else if userVariable.systemVariable$ = "EdidManufacturer" then userVariable.SetCurrentValue(m.sysInfo["edidManufacturer"+suffix$], postMsg) else if userVariable.systemVariable$ = "EdidUnspecifiedText" then userVariable.SetCurrentValue(m.sysInfo["edidUnspecifiedText"+suffix$], postMsg) else if userVariable.systemVariable$ = "EdidSerialNumber" then userVariable.SetCurrentValue(m.sysInfo["edidSerialNumber"+suffix$], postMsg) else if userVariable.systemVariable$ = "EdidManufacturerProductCode" then userVariable.SetCurrentValue(m.sysInfo["edidManufacturerProductCode"+suffix$], postMsg) else if userVariable.systemVariable$ = "EdidWeekOfManufacture" then userVariable.SetCurrentValue(m.sysInfo["edidWeekOfManufacture"+suffix$], postMsg) end if next end sub Sub UpdateIPAddressUserVariables(postMsg as boolean) userVariables = m.currentUserVariables for each userVariableKey in userVariables userVariable = userVariables.Lookup(userVariableKey) if userVariable.systemVariable$ = "IpAddressWired" then userVariable.SetCurrentValue(m.sysInfo.ipAddressWired$, postMsg) else if userVariable.systemVariable$ = "IpAddressWireless" then userVariable.SetCurrentValue(m.sysInfo.ipAddressWireless$, postMsg) end if next end sub 'endregion 'region Bose Products Function ReadBoseProductsFile() as object boseProducts = { } globalAA = GetGlobalAA() boseProductsFileContents$ = ReadAsciiFile(globalAA.boseProductsFilePath$) if len(boseProductsFileContents$) = 0 then return boseProducts boseProductsAA = ParseJson(boseProductsFileContents$) ' verify that this is a valid BoseProducts json file if not IsString(boseProductsAA.lookup("version")) then print "Invalid BoseProducts Json file - version not found" : stop if type(boseProductsAA.product) <> "roArray" then print "Invalid BoseProducts Json file - no product array" : stop boseProductsList = boseProductsAA.product numBoseProducts% = boseProductsList.count() rebootRequired = false for each boseProductJson in boseProductsList if boseProductJson.usbInternalHub = invalid then boseProductJson.usbInternalHub = "" endif registryKeyWritten = AddBoseProduct(boseProducts, boseProductJson) rebootRequired = rebootRequired or registryKeyWritten next if rebootRequired then usbRegistrySection = CreateObject("roRegistrySection", "usb") if type(usbRegistrySection) <> "roRegistrySection" then print "Error: Unable to create roRegistrySection": stop usbRegistrySection.Flush() RebootSystem() end if return boseProducts end function Function UpdateUsbRegistryKeys(usbRegistrySection as object, usbToSerialEntry as object) as boolean registryKey$ = usbToSerialEntry.registryKey registryValue$ = usbToSerialEntry.registryValue existingRegistryValue$ = usbRegistrySection.Read(registryKey$) if registryValue$ <> existingRegistryValue$ then usbRegistrySection.Write(registryKey$, registryValue$) return true end if return false end function Function AddBoseProduct(boseProducts as object, boseProductJson as object) rebootRequired = false boseProduct = { } productName$ = boseProductJson.productName boseProduct.tapProtocol = boseProductJson.tapProtocol boseProduct.bmapProtocol = boseProductJson.bmapProtocol ' revisit when bmap is added boseProduct.usbHIDCommunication = true ' if IsString(boseProduct.tapProtocol) and lcase(boseProduct.tapProtocol) = "hid" then ' boseProduct.usbHIDCommunication = true ' else ' boseProduct.usbHIDCommunication = false ' end if if boseProductJson.isAudioDevice = invalid then boseProduct.isAudioDevice = false else boseProduct.isAudioDevice = boseProductJson.isAudioDevice end if if boseProductJson.usbNetInterfaceIndex = invalid then boseProduct.usbNetInterfaceIndex$ = "" else boseProduct.usbNetInterfaceIndex$ = boseProductJson.usbNetInterfaceIndex end if boseProduct.usbAudioInterfaceIndex$ = boseProductJson.usbAudioInterfaceIndex if IsString(boseProduct.usbAudioInterfaceIndex$) then if boseProduct.usbAudioInterfaceIndex$ <> "" then boseProduct.usbAudio = true end if else boseProduct.usbAudio = false boseProduct.usbAudioInterfaceIndex$ = "" end if boseProduct.usbInternalHub$ = boseProductJson.usbInternalHub boseProduct.usbTapInterfaceIndex$ = boseProductJson.usbTapInterfaceIndex boseProduct.tapProtocol = boseProductJson.tapProtocol if boseProductJson.usbAsyncAudio = invalid then boseProduct.usbAsyncAudio = false else boseProduct.usbAsyncAudio = boseProductJson.usbAsyncAudio end if if type(boseProductJson.usbToSerial) = "roAssociativeArray" or (type(boseProductJson.usbToSerial) = "roArray" and boseProductJson.usbToSerial.Count() > 0) then usbRegistrySection = CreateObject("roRegistrySection", "usb") if type(usbRegistrySection) <> "roRegistrySection" then print "Error: Unable to create roRegistrySection": stop if type(boseProductJson.usbToSerial) = "roAssociativeArray" then if UpdateUsbRegistryKeys(usbRegistrySection, boseProductJson.usbToSerial) then rebootRequired = true end if else for each usbToSerialEntry in boseProductJson.usbToSerial if UpdateUsbRegistryKeys(usbRegistrySection, usbToSerialEntry) then rebootRequired = true end if next end if end if volumeTable = { } if type(boseProductJson.volumeTable) = "roAssociativeArray" then volumeTable.xval1% = boseProductJson.volumeTable.xval1 volumeTable.yval1% = boseProductJson.volumeTable.yval1 volumeTable.xval2% = boseProductJson.volumeTable.xval2 volumeTable.yval2% = boseProductJson.volumeTable.yval2 volumeTable.xval3% = boseProductJson.volumeTable.xval3 volumeTable.yval3% = boseProductJson.volumeTable.yval3 end if boseProduct.volumeTable = volumeTable if type(boseProductJson.transport) = "roAssociativeArray" then boseProduct.baudRate% = boseProductJson.transport.baudRate boseProduct.dataBits$ = boseProductJson.transport.dataBits boseProduct.parity$ = boseProductJson.transport.parity boseProduct.stopBits$ = boseProductJson.transport.stopBits boseProduct.flowControl$ = boseProductJson.transport.flowControl boseProduct.invertSignals = boseProductJson.transport.invertSignals boseProduct.sendEOL$ = boseProductJson.transport.sendEOL boseProduct.receiveEOL$ = boseProductJson.transport.receiveEOL else ' BACONTODO - not sure whether or not this is required. test at some point to see if it can be removed. boseProduct.baudRate% = 115200 boseProduct.dataBits$ = "8" boseProduct.parity$ = "N" boseProduct.stopBits$ = "1" boseProduct.flowControl$ = "none" boseProduct.invertSignals = true boseProduct.sendEOL$ = "CR" boseProduct.receiveEOL$ = "CR+LF" end if boseProduct.protocol$ = "ASCII" boseProducts.AddReplace(productName$, boseProduct) return rebootRequired end function Function GetBoseProductSpec(productName$) as object if type(m.boseProductSpecs) = "roAssociativeArray" then return m.boseProductSpecs.Lookup(productName$) else return 0 end if end function Function MatchToUniqueParameterName(properties as object, uniqueName$ as string, propertyNameBreadcrumbs as object) as boolean for each propertyName in properties if lcase(propertyName) = "uniquename" then if properties[propertyName] = uniqueName$ then return true else return false end if end if propertyNameBreadcrumbs.push(propertyName) if properties.DoesExist(propertyName) then property = properties.Lookup(propertyName) if type(property) = "roAssociativeArray" then matchFound = MatchToUniqueParameterName(property, uniqueName$, propertyNameBreadcrumbs) if matchFound then return true end if end if end if removedPropertyName = propertyNameBreadcrumbs.pop() next return false end function Function GetWssCommunicationSpecEventFromWebSocketEvent(wssCommunicationSpec as object, webSocketEvent as object) as object eventHeader = webSocketEvent.header eventResource = eventHeader.resource for i% = 0 to wssCommunicationSpec.events.Count() - 1 wssCommunicationSpecEvent = wssCommunicationSpec.events[i%] resource = wssCommunicationSpecEvent.header.resource if resource = eventResource then matchedWssCommunicationSpecEvent = MatchFixedWssHeaders(wssCommunicationSpecEvent, webSocketEvent) if matchedWssCommunicationSpecEvent <> invalid then webSocketEventProperties = [] for each eventBodyPropertyName in webSocketEvent.body webSocketEventProperty = { } webSocketEventProperty.name = eventBodyPropertyName webSocketEventProperty.value = webSocketEvent.body[eventBodyPropertyName] webSocketEventProperties.push(webSocketEventProperty) next matchedByBody = MatchFixedWssBodyProperties(wssCommunicationSpecEvent.body, webSocketEventProperties) if matchedByBody then return matchedWssCommunicationSpecEvent end if end if end if next return invalid end function Function MatchFixedWssBodyProperties(wssCommunicationSpecEventBodyProperties as object, webSocketEventBodyProperties as object) as boolean for i% = 0 to webSocketEventBodyProperties.Count() - 1 webSocketEventPropertyAA = webSocketEventBodyProperties[i%] eventBodyPropertyName = webSocketEventPropertyAa.name eventBodyPropertyNameValue = webSocketEventPropertyAa.value ' if associativeArray, drill down further into hierarchy if type(eventBodyPropertyNameValue) = "roAssociativeArray" then ' call recursively, searching for eventBodyPropertyNameValue if wssCommunicationSpecEventBodyProperties.DoesExist(eventBodyPropertyName) then wssCommunicationSpecEventBodyProperties = wssCommunicationSpecEventBodyProperties.Lookup(eventBodyPropertyName) if wssCommunicationSpecEventBodyProperties <> invalid then ' create webSocketEventBodyProperties to use for recursive call childEventProperties = [] for each childEventPropertyName in eventBodyPropertyNameValue childEventProperty = { } childEventProperty.name = childEventPropertyName childEventProperty.value = eventBodyPropertyNameValue[childEventPropertyName] childEventProperties.push(childEventProperty) next return MatchFixedWssBodyProperties(wssCommunicationSpecEventBodyProperties, childEventProperties) end if end if else if type(eventBodyPropertyNameValue) = "roString" then ' get match in wssCommunicationSpecEventBodyProperties if wssCommunicationSpecEventBodyProperties.DoesExist(eventBodyPropertyName) then wssCommunicationSpecPropertyValue = wssCommunicationSpecEventBodyProperties.Lookup(eventBodyPropertyName) if type(wssCommunicationSpecPropertyValue) <> "roAssociativeArray" then if type(wssCommunicationSpecPropertyValue) = "roInt" then propertyValue$ = StripLeadingSpaces(stri(wssCommunicationSpecPropertyValue)) if propertyValue$ <> StripLeadingSpaces(eventBodyPropertyNameValue) then return false end if ' string else if wssCommunicationSpecPropertyValue <> eventBodyPropertyNameValue then return false end if end if end if end if next return true end function Function MatchFixedWssHeaders(wssCommunicationSpecEvent as object, webSocketEvent as object) as object ' check webSocketEvent header property against fixed header property values from wssCommunicationSpec ' any header property in wssCommunicationSpec that is not an associative array is considered 'fixed' for each eventHeaderPropertyName in webSocketEvent.header ' don't need to check resource - it's already been matched if eventHeaderPropertyName <> "resource" then ' skip header properties from the webSocketEvent that does not have a corresponding entry in the wssCommunicationSpec ' includes: device if webSocketEvent.header.DoesExist(eventHeaderPropertyName) then eventHeaderPropertyValue = webSocketEvent.header.Lookup(eventHeaderPropertyName) if type(eventHeaderPropertyValue) = "roString" then ' get match in wssCommunicationSpecEvent if wssCommunicationSpecEvent.header.DoesExist(eventHeaderPropertyName) then wssCommunicationSpecPropertyValue = wssCommunicationSpecEvent.header.Lookup(eventHeaderPropertyName) if type(wssCommunicationSpecPropertyValue) <> "roAssociativeArray" then if wssCommunicationSpecPropertyValue <> eventHeaderPropertyValue then return invalid end if end if end if end if end if end if next return wssCommunicationSpecEvent end function Function GetMatchedParameter(webSocketEvent as object, wssCommunicationSpecEvent as object, wssEventParameter as object, wssEventTransitionEventSpec as object, specifiedEventPropertyName as string) as object paramAttrs = { } paramAttrs.propertyNameBreadcrumbs = [] ' if there is no parameter and everything to this point matched, then this is a match if type(wssEventParameter) <> "roAssociativeArray" then paramAttrs.matchFound = true return paramAttrs end if matchedPropertyName$ = "" propertyName = wssEventParameter.parameterName if propertyName = specifiedEventPropertyName then matchedPropertyName$ = propertyName else paramAttrs.matchFound = false return paramAttrs endif 'properties = wssEventProperties.Lookup(matchedPropertyName$) 'propertyName$ = properties.PropertyName 'uniqueName$ = properties.UniqueName paramAttrs.matchFound = MatchToUniqueParameterName(wssCommunicationSpecEvent, wssEventParameter.uniqueName, paramAttrs.propertyNameBreadcrumbs) if paramAttrs.matchFound then propertyValue = webSocketEvent for i% = 0 to paramAttrs.propertyNameBreadcrumbs.Count() - 1 if not propertyValue.DoesExist(paramAttrs.propertyNameBreadcrumbs[i%]) then paramAttrs.matchFound = false return paramAttrs end if propertyValue = propertyValue.Lookup(paramAttrs.propertyNameBreadcrumbs[i%]) next ' at this point, see if any of the keys in wssEventTransitionEventSpec match propertyValue. if they do, use that value for everything ' if they don't, see if properties.ParameterValue is a regular expression ' if it is, assign it to parameterValue$ and continue as before' for each specifiedPropertyValue in wssEventTransitionEventSpec if specifiedPropertyValue = propertyValue then paramAttrs.matchFound = true paramAttrs.parameterValue$ = propertyValue paramAttrs.propertyValue = propertyValue paramAttrs.transition = wssEventTransitionEventSpec.Lookup(propertyValue) return paramAttrs end if next paramAttrs.parameterValue$ = wssEventParameter.parameterValue ' this is the specified value. (but isn't currently correct for keyPresses) paramAttrs.propertyValue = propertyValue ' this is the value from the actual webSocket event paramAttrs.transition = wssEventTransitionEventSpec.Lookup(wssEventParameter.parameterValue) end if return paramAttrs end function Function MatchWssEvent(wssCommunicationSpecEvent as object, webSocketEvent as object, wssEventParameter as object, wssEventTransitionEventSpec as object, specifiedEventPropertyName as string) as object autoplayEvent = { } ' endpoint for this event resource = webSocketEvent.header.resource paramAttrs = GetMatchedParameter(webSocketEvent, wssCommunicationSpecEvent, wssEventParameter, wssEventTransitionEventSpec, specifiedEventPropertyName) if paramAttrs.matchFound = false then return paramAttrs else if paramAttrs.propertyNameBreadcrumbs.Count() = 0 then return paramAttrs end if parameterValue$ = paramAttrs.parameterValue$ propertyValue = paramAttrs.propertyValue transition = paramAttrs.transition ' look for regular expression match if spec includes wildcard if instr(1, parameterValue$, "(.*)") > 0 then r = CreateObject("roRegEx", parameterValue$, "i") if type(r) = "roRegex" then matches = r.match(propertyValue) if matches.Count() > 0 then '' m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "wssEvent", propertyValue, "1") if transition.assignInputToUserVariable then transition.AssignEventInputToUserVariable(m, propertyValue) end if if matches.Count() > 1 and transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m, matches[1]) end if return paramAttrs end if end if end if if propertyValue = parameterValue$ then return paramAttrs end if paramAttrs.matchFound = false return paramAttrs end function ' wssItem = wssEvent or wssCommand ' section = 'header' or 'body' Function ParseWss(wssItem as object, mainSection as string, section as string) as object properties = { } if wssItem.DoesExist(mainSection) then wssItemResponse = wssItem.Lookup(mainSection) if wssItemResponse.DoesExist(section) then wssItemResponseSection = wssItemResponse.Lookup(section) if type(wssItemResponseSection) = "roAssociativeArray" then for each propertyName in wssItemResponseSection if wssItemResponseSection.DoesExist(propertyName) then property = wssItemResponseSection.Lookup(propertyName) properties[propertyName] = property end if next end if end if end if return properties end function Sub ParseWssCommands(wssCommands as object) as object commands = { } for each wssCommand in wssCommands command = { } command.header = ParseWss(wssCommand, "request", "header") command.body = ParseWss(wssCommand, "request", "body") commands[wssCommand.friendlyId] = command next return commands end sub Sub ParseWssEvents(wssEvents as object) as object events = [] for each wssEvent in wssEvents event = { } event.header = ParseWss(wssEvent, "response", "header") event.body = ParseWss(wssEvent, "response", "body") if wssEvent.supportsMultipleTransitions = invalid then event.supportsMultipleTransitions = false else event.supportsMultipleTransitions = wssEvent.supportsMultipleTransitions end if events.push(event) next return events end sub Sub ParseBoseBmapCommunicationSpec(path$ as string) as object json$ = ReadAsciiFile(path$) bmap = ParseJson(json$) return bmap end sub Sub ParseBoseWssCommunicationSpec(wss as object) as object ' TODO - should this log file always be enabled? getGlobalAA().eddieDumpFile = CreateObject("roAppendFile", "eddie.json") wssSpec = { } wssSpec.name = wss.Name wssSpec.commands = ParseWssCommands(wss.Commands) wssSpec.events = ParseWssEvents(wss.Events) return wssSpec end sub 'endregion 'region Schedule ' required format looks like ' 2012-10-03T15:49:00 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 validDateTime$ = helper_ValidateInvalidPrint(dateTime$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_INVALID_DATE_TIME_SPEC, validDateTime$) m.bsp.diagnostics.PrintDebug("### Invalid roDateTime transformation : " + validDateTime$) return invalid end if return dateTime end function Function GetActiveScheduledEvent(scheduledEvents as object) as object ' determine if there is a scheduled event that should be active at this time activeScheduledEvent = invalid for each scheduledEvent in scheduledEvents ' is there a playlist that should be active now based on the scheduledEvent? if scheduledEvent.allDayEveryDay then activeScheduledEvent = scheduledEvent exit for end if ' 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 end if end if 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 end if end if ' 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 end if else ' EveryWeekend if currentDateTime.GetDayOfWeek() = 0 or currentDateTime.GetDayOfWeek() = 6 then scheduledEventActiveToday = true end if end if else ' Weekly bitwiseDaysOfWeek% = scheduledEvent.recurrencePatternDaysOfWeek% currentDayOfWeek = currentDateTime.GetDayOfWeek() bitDayOfWeek% = 2 ^ currentDayOfWeek if (bitwiseDaysOfWeek% and bitDayOfWeek%) <> 0 then scheduledEventActiveToday = true end if end if end if end if ' 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 exit for end if end if next return activeScheduledEvent end function Function GetNextScheduledEventTime(scheduledEvents) 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 scheduledEvents if scheduledEvent.allDayEveryDay then ' an allDayEveryDay event is always active, so by definition, it is not a future event. goto endLoop end if 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 end if end if ' 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 end if 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 end if 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 end if 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 end if 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 end if end if 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 end if end if ' 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 end if end while end if end if end if end if 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] end if 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% end if next next return end sub 'endregion 'region StartPlayback and CreateObjects Sub StartPlayback() sign = m.sign ' set a default udp receive port m.udpReceivePort = sign.udpReceivePort m.udpSendPort = sign.udpSendPort m.udpAddress$ = sign.udpAddress$ m.udpAddressType$ = sign.udpAddressType$ ' kick off playback ' set background screen color if type(sign.backgroundScreenColor%) = "roInt" then videoMode = GetVideoMode() if type(videoMode) = "roVideoMode" then m.diagnostics.PrintTimestamp() m.diagnostics.PrintDebug("### set background screen color") videoMode.SetBackgroundColor(sign.backgroundScreenColor%) videoMode = invalid endif end if ' unmute all audio explicitly for Cheetah / Panther / Puma m.UnmuteAllAudio() numZones% = sign.zonesHSM.Count() if numZones% > 0 then ' construct zones for i% = 0 to numZones% - 1 zoneHSM = sign.zonesHSM[i%] if type(zoneHSM.playlist) = "roAssociativeArray" then m.diagnostics.PrintTimestamp() m.diagnostics.PrintDebug("### Constructor zone") zoneHSM.Constructor() end if next ' launch the zones for i% = 0 to numZones% - 1 zoneHSM = sign.zonesHSM[i%] if type(zoneHSM.playlist) = "roAssociativeArray" then m.diagnostics.PrintTimestamp() m.diagnostics.PrintDebug("### Launch playback") zoneHSM.Initialize() end if next end if ' Notify controlling devices we have started playback m.SendUDPNotification("startPlayback") end sub ' m is the zone Sub CreateObjects() zoneHSM = m ' is there any harm in creating a keyboard object even if it is not used? if type(m.bsp.keyboard) <> "roKeyboard" then m.bsp.keyboard = CreateObject("roKeyboard") m.bsp.keyboard.SetPort(m.bsp.msgPort) end if for each key in zoneHSM.stateTable state = zoneHSM.stateTable[key] if state.type$ = "mediaList" then for each cmd in state.transitionNextItemCmds m.CreateObjectForTransitionCommand(cmd) next for each cmd in state.transitionPreviousItemCmds m.CreateObjectForTransitionCommand(cmd) next if type(state.mediaListEndEvent) = "roAssociativeArray" then m.CreateObjectsNeededForTransitionCommands(state.mediaListEndEvent) endif end if gpioEvents = state.gpioEvents for each gpioEventNumber in gpioEvents if type(gpioEvents[gpioEventNumber]) = "roAssociativeArray" then m.CreateObjectsNeededForTransitionCommands(gpioEvents[gpioEventNumber]) end if next gpioUpEvents = state.gpioUpEvents for each gpioEventNumber in gpioUpEvents if type(gpioUpEvents[gpioEventNumber]) = "roAssociativeArray" then m.CreateObjectsNeededForTransitionCommands(gpioUpEvents[gpioEventNumber]) end if next for buttonPanelIndex% = 0 to 3 bpEvents = state.bpEvents[buttonPanelIndex%] for each bpEventNumber in bpEvents if type(bpEvents[bpEventNumber]) = "roAssociativeArray" then m.CreateObjectsNeededForTransitionCommands(bpEvents[bpEventNumber]) end if next next if type(state.mstimeoutEvent) = "roAssociativeArray" m.CreateObjectsNeededForTransitionCommands(state.mstimeoutEvent) end if if type(state.timeClockEvents) = "roArray" then for each timeClockEvent in state.timeClockEvents m.CreateObjectsNeededForTransitionCommands(timeClockEvent.transition) next end if if type(state.videoEndEvent) = "roAssociativeArray" m.CreateObjectsNeededForTransitionCommands(state.videoEndEvent) end if if type(state.wssEvents) = "roAssociativeArray" m.CreateObjectsNeededForTransitionCommands(state.wssEvents) end if if type(state.signChannelEndEvent) = "roAssociativeArray" m.CreateObjectsNeededForTransitionCommands(state.signChannelEndEvent) end if if type(state.audioEndEvent) = "roAssociativeArray" m.CreateObjectsNeededForTransitionCommands(state.audioEndEvent) end if if type(state.keyboardEvents) = "roAssociativeArray" or type(state.usbStringEvents) = "roAssociativeArray" then 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]) end if next end if if type(state.usbStringEvents) = "roAssociativeArray" usbEvents = state.usbStringEvents for each usbEvent in state.usbEvents if type(usbEvents[usbEvent]) = "roAssociativeArray" then m.CreateObjectsNeededForTransitionCommands(usbEvents[usbEvent]) end if next end if end if if type(state.remoteEvents) = "roAssociativeArray" then remoteEvents = state.remoteEvents for each remoteEvent in state.remoteEvents m.CreateObjectsNeededForTransitionCommands(remoteEvents[remoteEvent]) next end if if (type(state.gpsEnterRegionEvents) = "roArray" and state.gpsEnterRegionEvents.Count() > 0) or (type(state.gpsExitRegionEvents) = "roArray" and state.gpsExitRegionEvents.Count() > 0) then m.CreateSerial(m.bsp, m.bsp.gpsPort$, false) end if serialEvents = state.serialEvents for each serialPort in serialEvents m.CreateSerial(m.bsp, serialPort, false) protocol$ = "" if IsUsbPort(serialPort) then usbSpec = GetGlobalAA().usbConnectorNameToUsbSpec.Lookup(serialPort) if GetGlobalAA().usbHIDPortConfigurations.DoesExist(serialPort) then usbHIDPortConfiguration = GetGlobalAA().usbHIDPortConfigurations[serialPort] protocol$ = usbHIDPortConfiguration.protocol$ end if else port% = int(val(serialPort)) serialPortConfiguration = m.bsp.sign.serialPortConfigurations[port%] protocol$ = serialPortConfiguration.protocol$ end if if protocol$ = "Binary" then if type(serialEvents[serialPort]) = "roAssociativeArray" then if type(serialEvents[serialPort].streamInputTransitionSpecs) = "roArray" then for each streamInputTransitionSpec in serialEvents[serialPort].streamInputTransitionSpecs m.CreateObjectsNeededForTransitionCommands(streamInputTransitionSpec.transition) next end if end if else if protocol$ <> "" then for each serialEvent in serialEvents[serialPort] m.CreateObjectsNeededForTransitionCommands(serialEvents[serialPort][serialEvent]) next end if next if type(state.zoneMessageEvents) = "roAssociativeArray" then for each zoneMessageEvent in state.zoneMessageEvents m.CreateObjectsNeededForTransitionCommands(state.zoneMessageEvents[zoneMessageEvent]) next end if if type(state.pluginMessageEvents) = "roAssociativeArray" then for each pluginMessageEvent in state.pluginMessageEvents m.CreateObjectsNeededForTransitionCommands(state.pluginMessageEvents[pluginMessageEvent]) next end if if type(state.internalSynchronizeEvents) = "roAssociativeArray" then for each internalSynchronizeEvent in state.internalSynchronizeEvents m.CreateObjectsNeededForTransitionCommands(state.internalSynchronizeEvents[internalSynchronizeEvent]) next end if createDatagramReceiverObj = false if state.type$ = "mediaList" then createDatagramReceiverObj = m.CheckForSyncEventInEventList(state.transitionToNextEventList, createDatagramReceiverObj) createDatagramReceiverObj = m.CheckForSyncEventInEventList(state.transitionToPreviousEventList, createDatagramReceiverObj) endif if type(state.udpEvents) = "roAssociativeArray" or type(state.synchronizeEvents) = "roAssociativeArray" or createDatagramReceiverObj then m.bsp.CreateDatagramReceiver(m.bsp.udpReceivePort) if type(state.udpEvents) = "roAssociativeArray" then udpEvents = state.udpEvents for each udpEvent in state.udpEvents m.CreateObjectsNeededForTransitionCommands(udpEvents[udpEvent]) next end if if type(state.synchronizeEvents) = "roAssociativeArray" then synchronizeEvents = state.synchronizeEvents for each synchronizeEvent in state.synchronizeEvents m.CreateObjectsNeededForTransitionCommands(synchronizeEvents[synchronizeEvent]) next end if end if if state.type$ = "html5" and state.enableMouseEvents then m.bsp.InitializeTouchScreen(zoneHSM) end if if type(state.touchEvents) = "roAssociativeArray" then m.bsp.InitializeTouchScreen(zoneHSM) for each eventNum in state.touchEvents m.bsp.AddRectangularTouchRegion(m, state.touchEvents[eventNum], val(eventNum)) m.CreateObjectsNeededForTransitionCommands(state.touchEvents[eventNum]) next end if if type(state.audioTimeCodeEvents) = "roAssociativeArray" then for each eventNum in state.audioTimeCodeEvents m.CreateObjectsNeededForTransitionCommands(state.audioTimeCodeEvents[eventNum]) next end if if type(state.videoTimeCodeEvents) = "roAssociativeArray" then for each eventNum in state.videoTimeCodeEvents m.CreateObjectsNeededForTransitionCommands(state.videoTimeCodeEvents[eventNum]) next end if if type(state.cmds) = "roArray" then for each cmd in state.cmds m.CreateCommunicationObjects(cmd) next end if if type(state.exitCmds) = "roArray" then for each cmd in state.exitCmds m.CreateCommunicationObjects(cmd) next end if next mode = invalid if m.bsp.sign.enableEnhancedSynchronization and (m.bsp.IsSyncMaster or m.bsp.IsSyncSlave) then syncDomain = m.bsp.sign.ptpDomain$ mode = "multi-player" else if not m.bsp.sign.enableEnhancedSynchronization and (m.bsp.IsSyncMaster) then ' Use the default sync manager domain syncDomain = "BS1" mode = "single-player" end if if ShouldResetSyncManager(m.bsp.syncManagerMode, mode) then aa = { } aa.Domain = syncDomain m.bsp.SyncManager = invalid m.bsp.SyncManager = CreateObject("roSyncManager", aa) m.bsp.diagnostics.PrintDebug("@@@ roSyncManager created. Value of sync domain:" + aa.Domain) m.bsp.SyncManager.SetMasterMode(m.bsp.IsSyncMaster) m.bsp.syncManagerMode = mode if m.bsp.IsSyncSlave then m.bsp.SyncManager.SetPort(m.bsp.msgPort) m.bsp.diagnostics.PrintDebug("@@@ Node is a slave") end if if m.bsp.setSyncDomainSupported then if m.bsp.videomode.SetSyncDomain(syncDomain) then m.bsp.diagnostics.PrintDebug("@@@ VSYNC Enabled on Domain @@@ : " + syncDomain) else m.bsp.diagnostics.PrintDebug("@@@ VSYNC failed to enable on Domain @@@ : " + syncDomain + " : " + m.bsp.videomode.GetFailureReason()) end if end if end if end sub Function ShouldResetSyncManager(currentMode as Object, newMode as Object) as boolean ' Check the current set sync manager. ' If it's lower priority than what we want, then set the new sync manager. ' Priority: multi-player > single-player > none. ' No need to update if new mode is invalid if newMode = invalid then return false ' Do not overwrite a multi-player sync manager with lower priority if currentMode = "multi-player" and newMode = "single-player" then return false return true end function Sub CreateCommunicationObjects(cmd as object) commandName$ = cmd.name$ if commandName$ = "sendUDPCommand" or commandName$ = "sendUDPBytesCommand" or commandName$ = "synchronize" then m.CreateUDPSender(m.bsp) else if commandName$ = "serialSendString" or commandName$ = "sendSerialStringCommand" or commandName$ = "sendSerialBlockCommand" or commandName$ = "sendSerialByteCommand" or commandName$ = "sendSerialBytesCommand" then port$ = cmd.parameters["port"].GetCurrentParameterValue() m.CreateSerial(m.bsp, port$, true) end if end sub Function CheckForSyncEventInEventList(eventList as object, createDatagramReceiverObj as boolean) as boolean for each event in eventList if event.eventName = "synchronize" then m.bsp.IsSyncSlave = true createDatagramReceiverObj = true endif next return createDatagramReceiverObj end function Sub CreateObjectsNeededForTransitionCommands(transition as object) if type(transition.transitionCmds) = "roArray" then for each cmd in transition.transitionCmds m.CreateObjectForTransitionCommand(cmd) next end if if type(transition.conditionalTransitions) = "roArray" then for each conditionalTransition in transition.conditionalTransitions if type(conditionalTransition.transitionCmds) = "roArray" then for each cmd in conditionalTransition.transitionCmds m.CreateObjectForTransitionCommand(cmd) next end if next end if end sub Sub CreateObjectForTransitionCommand(cmd as object) commandName$ = cmd.name$ if commandName$ = "sendUDPCommand" or commandName$ = "sendUDPBytesCommand" or commandName$ = "synchronize" then m.CreateUDPSender(m.bsp) else if commandName$ = "serialSendString" or commandName$ = "sendSerialStringCommand" or commandName$ = "sendSerialBlockCommand" or commandName$ = "sendSerialByteCommand" or commandName$ = "sendSerialBytesCommand" then port$ = cmd.parameters["port"].GetCurrentParameterValue() m.CreateSerial(m.bsp, port$, true) end if if commandName$ = "synchronize" then m.bsp.IsSyncMaster = true end if end sub Sub ConfigureIRRemote() irInConfiguration = m.sign.irInConfiguration irOutConfiguration = m.sign.irOutConfiguration irRemoteConfiguration = m.sign.irRemoteControl irConfig = {} irConfig.source = irInConfiguration.source irConfig.encodings = [] irConfig.encodings[0] = irRemoteConfiguration.encoding m.irReceiver = CreateObject("roIRReceiver", irConfig) if type(m.irReceiver) = "roIRReceiver" then m.irReceiver.SetPort(m.msgPort) endif End Sub Sub ConfigureBPs() m.bpOutput = CreateObject("roArray", 4, true) m.bpOutputSetup = CreateObject("roArray", 4, true) m.ConfigureBP(0, "TouchBoard-0-LED", "TouchBoard-0-LED-SETUP") m.ConfigureBP(1, "TouchBoard-1-LED", "TouchBoard-1-LED-SETUP") m.ConfigureBP(2, "TouchBoard-2-LED", "TouchBoard-2-LED-SETUP") m.ConfigureBP(3, "TouchBoard-3-LED", "TouchBoard-3-LED-SETUP") end sub Sub ConfigureBP(buttonPanelIndex% as integer, touchBoardLED$ as string, touchBoardLEDSetup$ as string) if type(m.bpInputPorts[buttonPanelIndex%]) = "roControlPort" then if type(m.bpOutput[buttonPanelIndex%]) <> "roControlPort" then m.diagnostics.PrintDebug("Creating bpOutput") m.bpOutput[buttonPanelIndex%] = CreateObject("roControlPort", touchBoardLED$) if type(m.bpOutput[buttonPanelIndex%]) = "roControlPort" then m.bpOutput[buttonPanelIndex%].SetUserData(touchBoardLED$) m.bpOutputSetup[buttonPanelIndex%] = CreateObject("roControlPort", touchBoardLEDSetup$) if type(m.bpOutputSetup[buttonPanelIndex%]) = "roControlPort" then m.bpOutputSetup[buttonPanelIndex%].SetUserData(touchBoardLEDSetup$) m.bpOutputSetup[buttonPanelIndex%].SetOutputValue(0, 22) end if end if end if end if end sub Sub CreateUDPSender(bsp as object) createDatagramSender = false if type(bsp.udpSender) <> "roDatagramSender" then createDatagramSender = true else if (type(bsp.existingUdpAddressType$) = "roString" and bsp.existingUdpAddressType$ <> bsp.udpAddressType$) or (type(bsp.existingUdpAddress$) = "roString" and bsp.existingUdpAddress$ <> bsp.udpAddress$) or (type(bsp.existingUdpSendPort) = "roInt" and bsp.existingUdpSendPort <> bsp.udpSendPort) then createDatagramSender = true end if end if if createDatagramSender then bsp.diagnostics.PrintDebug("Creating roDatagramSender") bsp.udpSender = CreateObject("roDatagramSender") if bsp.udpAddressType$ = "LocalSubnet" then bsp.udpSender.SetDestination("BCAST-LOCAL-SUBNETS", bsp.udpSendPort) else if bsp.udpAddressType$ = "Ethernet" then bsp.udpSender.SetDestination("BCAST-SUBNET-0", bsp.udpSendPort) else if bsp.udpAddressType$ = "Wireless" then bsp.udpSender.SetDestination("BCAST-SUBNET-1", bsp.udpSendPort) else bsp.udpSender.SetDestination(bsp.udpAddress$, bsp.udpSendPort) end if bsp.existingUdpAddressType$ = bsp.udpAddressType$ bsp.existingUdpAddress$ = bsp.udpAddress$ bsp.existingUdpSendPort = bsp.udpSendPort end if end sub Sub SendUDPNotification(msg as string) if not getGlobalAA().settings.lwsEnableUpdateNotifications then return end if if type(m.udpNotifier) <> "roDatagramSender" then m.diagnostics.PrintDebug("Creating roDatagramSender for notifications") m.udpNotifier = CreateObject("roDatagramSender") m.udpNotifier.SetDestination(m.udpNotificationAddress$, m.udpNotificationPort%) end if m.udpNotifier.Send(msg) m.diagnostics.PrintDebug("UDP notification sent: " + msg) end sub Sub ScheduleRetryCreateSerial(port$ as string, outputOnly as boolean) if type(m.serialPortsToRetry) <> "roAssociativeArray" then m.serialPortsToRetry = { } end if if not m.serialPortsToRetry.DoesExist(port$) then aa = { } aa.port$ = port$ aa.outputOnly = outputOnly timer = CreateObject("roTimer") timer.SetPort(m.msgPort) timer.SetElapsed(15, 0) timer.Start() aa.timer = timer m.serialPortsToRetry[port$] = aa m.diagnostics.PrintDebug("ScheduleRetryCreateSerial on port " + port$) end if end sub Function RetryCreateSerial(port$ as string, outputOnly as boolean) as boolean ok = m.AttemptOpenSerial(port$, outputOnly) m.diagnostics.PrintDebug("RetryCreateSerial on port " + port$) if ok and m.serialPortsToRetry.DoesExist(port$) then m.serialPortsToRetry.Delete(port$) end if return ok end function Sub CreateDatagramReceiver(udpReceivePort) createDatagramReceiverObj = false if type(m.udpReceiver) <> "roDatagramReceiver" then createDatagramReceiverObj = true else if type(m.existingUdpReceivePort) = "roInt" and m.existingUdpReceivePort <> m.udpReceivePort then createDatagramReceiverObj = true end if end if if createDatagramReceiverObj then m.udpReceiver = CreateObject("roDatagramReceiver", udpReceivePort) ' Set user data to distinguish between presentation udp messages and bootstrap udp messages m.udpReceiver.SetUserData("receiver") m.udpReceiver.SetPort(m.msgPort) m.existingUdpReceivePort = m.udpReceivePort end if end sub Sub CreateSerial(bsp as object, port$ as string, outputOnly as boolean) ok = bsp.AttemptOpenSerial(port$, outputOnly) if not ok then bsp.ScheduleRetryCreateSerial(port$, outputOnly) end if end sub Function IsUsbPort(connectorName$) as boolean return GetGlobalAA().usbConnectorNameToUsbSpec.DoesExist(connectorName$) end function Function GetRuntimeUsbConnector(connector$ as string) as string if m.replaceUSB700_1_with_USB_C and lcase(connector$) = "usb700_1" then connector$ = "usbTypeC" else if m.replaceUSB_C_with_USB700_1 and lcase(connector$) = "usbtypec" then connector$ = "usb700_1" endif return connector$ end function Function GetSpecifiedConnector(connector$ as string) as string if m.replaceUSB700_1_with_USB_C and lcase(connector$) = "usbtypec" then connector$ = "usb700_1" else if m.replaceUSB_C_with_USB700_1 and lcase(connector$) = "usb700_1" then connector$ = "usbTypeC" endif return connector$ end function Function AttemptOpenSerial(port$ as string, outputOnly as boolean) as boolean port$ = m.GetRuntimeUsbConnector(port$) if type(m.serial) <> "roAssociativeArray" then m.serial = { } end if if type(m.serialOutputOnlySpec) <> "roAssociativeArray" then m.serialOutputOnlySpec = { } end if gaa = GetGlobalAA() usbConnectorNameToUsbSpec = gaa.usbConnectorNameToUsbSpec ' BCN-11195 ' If Bose serial device is defined in presentation but not connected when creating serial object ' bypass this serial deivce port creation by returning true ' This check is agnostic to the successor conditions if not IsSerialPortNumberOfTypeString(port$) and m.sign.boseProductsByConnector <> invalid and m.sign.boseProductsByConnector[port$] <> invalid and not usbConnectorNameToUsbSpec.DoesExist(port$) then di = CreateObject("roDeviceInfo") connectedUSBDevices = di.GetUSBTopology({ array : true }) boseProduct = m.sign.boseProductsByConnector[port$] GetConnectedUSBDeviceName(m.bsp, di.GetModel(), connectedUSBDevices, port$, boseProduct.usbInternalHub$) if not usbConnectorNameToUsbSpec.DoesExist(port$) then ' Force the retry operation to not occur since the serial device is not connected m.diagnostics.PrintDebug("Attempt to open serial port " + port$ + " failed because device is not connected") return true end if end if if IsUsbPort(port$) then usbSpec = usbConnectorNameToUsbSpec.Lookup(port$) hidOutputSpec = usbSpec.hidOutputSpec usbHIDPortConfiguration = gaa.usbHIDPortConfigurations[port$] sendEol$ = usbHIDPortConfiguration.sendEol$ receiveEol$ = usbHIDPortConfiguration.receiveEol$ protocol$ = usbHIDPortConfiguration.protocol$ if type(m.serial[port$]) = "roUsbTap" then serial = m.serial[port$] else serial = CreateObject("roUsbTap", hidOutputSpec) if type(serial) <> "roUsbTap" then m.diagnostics.PrintDebug("Error creating roUsbTap " + hidOutputSpec) return false end if m.serialOutputOnlySpec[port$] = true ' post Usb connect event connectData = {} connectData.port = port$ connectData.hidOutputSpec = hidOutputSpec usbConnectEvent = { } usbConnectEvent["EventType"] = "USB_CONNECT_EVENT" usbConnectEvent["EventData"] = connectData m.msgPort.PostMessage(usbConnectEvent) end if else if IsSerialPortNumberOfTypeString(port$) then ' validation for port integers represented as strings, e.g., "0", "1", etc port% = int(val(port$)) serialPortConfiguration = m.serialPortConfigurations[port%] serialPortSpeed% = serialPortConfiguration.serialPortSpeed% serialPortMode$ = serialPortConfiguration.serialPortMode$ if type(m.serial[port$]) = "roSerialPort" then serial = m.serial[port$] else serial = CreateObject("roSerialPort", port%, serialPortSpeed%) if type(serial) <> "roSerialPort" then m.diagnostics.PrintDebug("Error creating roSerialPort " + port$) return false end if m.serialOutputOnlySpec[port$] = true end if ok = serial.SetMode(serialPortMode$) if not ok then m.diagnostics.PrintDebug("Error setting serial mode") : return false protocol$ = serialPortConfiguration.protocol$ sendEol$ = serialPortConfiguration.sendEol$ receiveEol$ = serialPortConfiguration.receiveEol$ if serialPortConfiguration.invertSignals then serial.SetInverted(1) else serial.SetInverted(0) end if else m.diagnostics.PrintDebug("Error with serial port value validation: " + port$) return false endif serial.SetSendEol(sendEol$) serial.SetReceiveEol(receiveEol$) serial.SetUserData(port$) m.serial[port$] = serial if not outputOnly then m.serialOutputOnlySpec[port$] = false if protocol$ = "Binary" then serial.SetByteEventPort(m.msgPort) else serial.SetLineEventPort(m.msgPort) end if end if return true end function ' Check whether input value is a integer represented as a string type Function IsSerialPortNumberOfTypeString(inputVariable as string) as boolean if not IsString(inputVariable) then return false inputVariable = StripLeadingSpaces(inputVariable) if (inputVariable = "0" or inputVariable = "1" or inputVariable = "2" or inputVariable = "3" or inputVariable = "4" or inputVariable = "5" or inputVariable = "6" or inputVariable = "7") then return true end if return false end function Function IsNonEmptyString(inputVariable as object) as boolean if not IsString(inputVariable) then return false if len(inputVariable) = 0 then return false return true end function Function IsBoolean(inputVariable as object) as boolean if type(inputVariable) = "roBoolean" or type(inputVariable) = "boolean" then return true return false end function Function IsString(inputVariable as object) as boolean if type(inputVariable) = "roString" or type(inputVariable) = "String" then return true return false end function Function IsInteger(inputVariable as object) as boolean if type(inputVariable) = "roInt" or type(inputVariable) = "Integer" then return true return false end function Function GetEolFromSpec(eolSpec$ as string) as string eol$ = chr(13) if eolSpec$ = "LF" then eol$ = chr(10) else if eolSpec$ = "CRLF" then eol$ = chr(13) + chr(10) else if eolSpec$ = "CR+LF" then eol$ = chr(13) + chr(10) end if return eol$ end function ' This function is similar to lodash get, given an object and the path, trying to get ' the field with type check. If type check failed or cannot find the field, the default ' will be returned. ' @params ' startObj: the object to start the search ' path$: the path to look down in startObj ' targetType$: the type that returned result from object should match ' theDefault: the default value returns if nothing is found Function getVarFromObj(startObj as object, path$ as string, targetType$ as string, theDefault) ' split path$ by dot to prepare for looking in nested object regex = CreateObject("roRegEx", "\.", "i") pathArray = regex.Split(path$) ' if cannot get the correct path, return the original object, or the default depends on the type check if type(pathArray) <> "roList" or pathArray.Count() = 0 then if type(startObj) = targetType$ then return startObj else return theDefault end if end if targetObj = startObj ' looping down the object to find the field for i% = 0 to pathArray.Count() - 1 targetObj = targetObj[pathArray[i%]] if i% = pathArray.Count() - 1 then ' if it's the last field, check type of the result and return depend on the type check if type(targetObj) = targetType$ then return targetObj else return theDefault end if else ' if has not reached the last field, the type has to be object to continue the search if type(targetObj) <> "roAssociativeArray" then return theDefault end if next end function ' Helper function to get the value of the text parameter from a parameters object. ' Should only be used with object that has GetCurrentParameterValue() function. Function getTextParameterFallbackToEmpty(parameters as object, key as string) as string parameter = parameters[key] returnVal$ = "" if type(parameter) = "roAssociativeArray" and IsString(parameter.GetCurrentParameterValue()) then returnVal$ = parameter.GetCurrentParameterValue() end if return returnVal$ end function 'endregion 'region newSign Function newSign(BrightAuthor as object, globalVariables as object, bsp as object, msgPort as object, controlPort as object, version% as integer) as object autoplay = ParseAutoplay(BrightAuthor, bsp) meta = autoplay.meta ' FUTURE - check for obsolete models here Sign = { } Sign.BuildBoseProductsByConnector = BuildBoseProductsByConnector Sign.numTouchEvents% = 0 Sign.numAudioTimeCodeEvents% = 0 Sign.numVideoTimeCodeEvents% = 0 Sign.name$ = meta.name if not IsString(Sign.name$) then print "Invalid autoplay file - meta name not found" : stop ' videoMode plugins meta.videoModePlugins = [] for each videoModePluginSpec in BrightAuthor.meta.videoModePlugins videoModePlugin = jsonParseVideoModePlugin(videoModePluginSpec) meta.videoModePlugins.push(videoModePlugin) bsp.videoModePlugins.push(videoModePlugin) next ' parse script plugins meta.scriptPlugins = [] for each scriptPluginSpec in BrightAuthor.meta.scriptPlugins scriptPlugin = jsonParseScriptPlugin(scriptPluginSpec) ' BACON - is it necessary to store the script plugins both in meta and in bsp? meta.scriptPlugins.push(scriptPlugin) bsp.scriptPlugins.push(scriptPlugin) next Sign.screens = BrightAuthor.screens videoMode = GetVideoMode() if type(videoMode) = "roVideoMode" then ' check if player can be rotated by screen if CanRotateByScreen(Sign, videoMode) then screenModes = videoMode.GetScreenModes() screensInUse = {} hasScreenEnabled = false ' loop through the 2D array to configure video mode of each screen for each screenRow in Sign.screens if type(screenRow) = "roArray" then for each screen in screenRow ' screen is a valid object if type(screen) = "roAssociativeArray" and type(screen.dimensions) = "roAssociativeArray" then name$ = screen.videoConnectorName ' Update the corresponding settings to the OS video output array videoOutputOSIndex% = GetVideoOutputIndexFromOS(name$, screenModes) ' non-negative index means the connector name can be found in OS array if videoOutputOSIndex% >= 0 then videoMode$ = screen.videoMode forceResolution = screen.forceResolution if forceResolution = invalid or forceResolution = false then videoMode$ = videoMode$ + ":preferred" end if dolbyVisionEnabled = screen.dolbyVisionEnabled if dolbyVisionEnabled = true then videoMode$ = videoMode$ + ":dbv" end if fullResGraphicsEnabled = screen.fullResGraphicsEnabled if fullResGraphicsEnabled = true then videoMode$ = videoMode$ + ":fullres" end if tenBitColorEnabled = screen.tenBitColorEnabled if tenBitColorEnabled = true then videoMode$ = videoMode$ + ":10bit" end if screenModes[videoOutputOSIndex%].video_mode = videoMode$ ' transform has options normal, 90, 180 or 270 orientation$ = lcase(screen.monitorOrientation) if orientation$ = "portraitbottomleft" then screenModes[videoOutputOSIndex%].transform = "90" else if orientation$ = "portraitbottomright" then screenModes[videoOutputOSIndex%].transform = "270" else screenModes[videoOutputOSIndex%].transform = "normal" end if displayLeftX = screen.dimensions.x displayTopY = screen.dimensions.y ' negative display_x and display_y is okay for OS to handle screenModes[videoOutputOSIndex%].display_x = displayLeftX screenModes[videoOutputOSIndex%].display_y = displayTopY screenModes[videoOutputOSIndex%].enabled = true screensInUse.AddReplace(stri(videoOutputOSIndex%), screen.name) hasScreenEnabled = true end if end if next end if next ' only make updates when at least one screen is enabled if hasScreenEnabled = true then ' disable any HDMI ports that wasn't mentioned in the array for i% = 0 to screenModes.Count() - 1 if (not screensInUse.DoesExist(stri(i%))) then screenModes[i%].enabled = false next end if ' allow videoModePlugin to set multi screens videoMode videoModeInputs = {} videoModeInputs.screenModes = screenModes screenModes = parseVideoModePlugin(videoModeInputs, bsp, "", screenModes) ok = videoMode.SetScreenModes(screenModes) ' error handling in case it fails if ok = 0 then errMsg$ = "Error: Can't set VIDEOMODE screen array. Resetting to 1920x1080x60i" bsp.diagnostics.PrintDebug(errMsg$) bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_SET_VIDEO_MODE, errMsg$) videoMode.SetMode("1920x1080x60i") end if ' we do not support video wall and multiple screens at the same time Sign.isVideoWall = false else ' players do not support multi screens Sign.videoMode$ = meta.videoMode if Sign.videoMode$ <> "not applicable" then if not IsString(Sign.videoMode$) then print "Invalid autoplay file - meta videoMode not found" : stop ' print "Video mode is ";Sign.videoMode$ videoMode$ = Sign.videoMode$ forceResolution = meta.forceResolution setPreferredResolution = false if not forceResolution then setPreferredResolution = true videoMode$ = videoMode$ + ":preferred" end if dolbyVisionEnabled = meta.dolbyVisionEnabled if dolbyVisionEnabled then videoMode$ = videoMode$ + ":dbv" end if fullResGraphicsEnabled = meta.fullResGraphicsEnabled if fullResGraphicsEnabled then videoMode$ = videoMode$ + ":fullres" end if tenBitColorEnabled = meta.tenBitColorEnabled if tenBitColorEnabled then videoMode$ = videoMode$ + ":10bit" end if ' allow videoModePlugin to set videoMode videoModeInputs = { } videoModeInputs.signVideoMode$ = Sign.videoMode$ videoModeInputs.setPreferredResolution = setPreferredResolution videoModeInputs.fullResGraphicsEnabled = fullResGraphicsEnabled videoModeInputs.tenBitColorEnabled = tenBitColorEnabled videoModeInputs.dolbyVisionEnabled = dolbyVisionEnabled videoModeInputs.videoMode$ = videoMode$ ' allow videoModePlugin to set single screen videoMode singleVideoMode = parseVideoModePlugin(videoModeInputs, bsp, videoMode$, {}) ok = videoMode.SetMode(singleVideoMode) if ok = 0 then print "Error: Can't set VIDEOMODE to ::"; singleVideoMode; " resetting to 1920x1080x60i" ok = videoMode.SetMode("1920x1080x60i") endif if bsp.forceResolutionSupported then aa = videoMode.GetConfiguredMode() if aa <> invalid then bsp.configuredResX = aa.graphicsPlaneWidth bsp.configuredResY = aa.graphicsPlaneHeight else bsp.configuredResX = videoMode.GetResX() bsp.configuredResY = videoMode.GetResY() end if bsp.diagnostics.PrintDebug("Specified videoMode: " + singleVideoMode + ", actual videoMode: " + videoMode.GetMode()) bsp.logging.WriteDiagnosticLogEntry(bsp.diagnosticCodes.EVENT_SET_VIDEO_MODE, "Specified videoMode: " + singleVideoMode + ", actual videoMode: " + videoMode.GetMode()) else bsp.configuredResX = videoMode.GetResX() bsp.configuredResY = videoMode.GetResY() end if ' if the user specified a video mode other than what the system chose, scale screen items as needed bsp.actualResX = videoMode.GetResX() bsp.actualResY = videoMode.GetResY() Sign.videoConnector$ = meta.videoConnector if not IsString(Sign.videoConnector$) then print "Invalid XML file - meta videoConnector not found" : stop endif Sign.isVideoWall = false if meta.stretchedVideoWall.Count() = 1 then Sign.isVideoWall = true Sign.videoWallType$ = "stretched" Sign.videoWallNumRows% = int(val(BrightAuthor.meta.stretchedVideoWall.videoWallNumRows.GetText())) Sign.videoWallNumColumns% = int(val(BrightAuthor.meta.stretchedVideoWall.videoWallNumColumns.GetText())) Sign.videoWallRowPosition% = int(val(BrightAuthor.meta.stretchedVideoWall.videoWallRowPosition.GetText())) Sign.videoWallColumnPosition% = int(val(BrightAuthor.meta.stretchedVideoWall.videoWallColumnPosition.GetText())) videoMode.SetMultiscreenBezel(int(val(BrightAuthor.meta.stretchedVideoWall.bezelWidthPercentage.GetText())), int(val(BrightAuthor.meta.stretchedVideoWall.bezelHeightPercentage.GetText()))) else videoMode.SetMultiscreenBezel(0, 0) end if videoMode = invalid end if endif Sign.monitorOrientation = meta.monitorOrientation Sign.deviceWebPageDisplay$ = meta.deviceWebPageDisplay Sign.customDeviceWebPage = meta.customDeviceWebPage Sign.enableSettingsHandler = meta.enableSettingsHandler Sign.alphabetizeVariableNames = meta.alphabetizeVariableNames Sign.htmlEnableJavascriptConsole = meta.htmlEnableJavascriptConsole Sign.backgroundScreenColor% = meta.backgroundScreenColor bsp.dontChangePresentationUntilMediaEndEventReceived = meta.delayScheduleChangeUntilMediaEndEvent Sign.languageKey$ = meta.languageKey globalVariables.language$ = Sign.languageKey$ Sign.irRemoteControl = meta.irRemoteControl Sign.irInConfiguration = meta.irInConfiguration Sign.irOutConfiguration = meta.irOutConfiguration Sign.serialPortConfigurations = meta.serialPortConfigurations GetGlobalAA().usbHIDPortConfigurations = { } GetGlobalAA().usbAudioPortConfigurations = { } GetGlobalAA().usbBMAPHIDPortConfigurations = { } if type(bsp.bpInputPorts[0]) = "roControlPort" then bsp.bpInputPortConfigurations[0] = GetBPConfiguration(bsp.bpInputPortHardware[0], meta.bp900AConfigureAutomatically, meta.bp900AConfiguration%, meta.bp200AConfigureAutomatically, meta.bp200AConfiguration%) end if if type(bsp.bpInputPorts[1]) = "roControlPort" then bsp.bpInputPortConfigurations[1] = GetBPConfiguration(bsp.bpInputPortHardware[1], meta.bp900BConfigureAutomatically, meta.bp900BConfiguration%, meta.bp200BConfigureAutomatically, meta.bp200BConfiguration%) end if if type(bsp.bpInputPorts[2]) = "roControlPort" then bsp.bpInputPortConfigurations[2] = GetBPConfiguration(bsp.bpInputPortHardware[2], meta.bp900CConfigureAutomatically, meta.bp900CConfiguration%, meta.bp200CConfigureAutomatically, meta.bp200CConfiguration%) end if if type(bsp.bpInputPorts[3]) = "roControlPort" then bsp.bpInputPortConfigurations[3] = GetBPConfiguration(bsp.bpInputPortHardware[3], meta.bp900DConfigureAutomatically, meta.bp900DConfiguration%, meta.bp200DConfigureAutomatically, meta.bp200DConfiguration%) end if ' serial ports' bsp.gpsConfigured = false bsp.gpsLocation = { latitude: invalid, longitude: invalid } bsp.gpsPort$ = "" for each serialPortConfigurationSpec in meta.serialPortConfigurations serialPortConfiguration = { } serialPortConfiguration.serialPortSpeed% = serialPortConfigurationSpec.serialPortSpeed% serialPortConfiguration.protocol$ = serialPortConfigurationSpec.protocol$ serialPortConfiguration.sendEol$ = serialPortConfigurationSpec.sendEol$ serialPortConfiguration.receiveEol$ = serialPortConfigurationSpec.receiveEol$ serialPortConfiguration.invertSignals = serialPortConfigurationSpec.invertSignals serialPortConfiguration.serialPortMode$ = serialPortConfigurationSpec.serialPortMode port% = serialPortConfigurationSpec.port serialPortConfiguration.gps = serialPortConfigurationSpec.gps if serialPortConfigurationSpec.gps then bsp.gpsConfigured = true bsp.gpsPort$ = stri(port%) else serialPortConfiguration.gps = false end if Sign.serialPortConfigurations[port%] = serialPortConfiguration next ' parse parser plugins parserPluginsContainer = meta.parserPlugins for each parserPlugin in parserPluginsContainer bsp.parserPlugins.push(parserPlugin) next ' first pass parse of user variables bsp.variablesDBExists = false bsp.ReadVariablesDB(bsp.activePresentation$) ' BACONTODO - parseAutoplay may need this set earlier' bsp.privateDBSectionId% = bsp.GetDBSectionId(bsp.activePresentation$) if bsp.privateDBSectionId% < 0 then bsp.AddDBSection(bsp.activePresentation$) bsp.privateDBSectionId% = bsp.GetDBSectionId(bsp.activePresentation$) end if bsp.privateBrightAuthorCategoryId% = bsp.GetDBCategoryId(bsp.privateDBSectionId%, "BrightAuthor") if bsp.privateBrightAuthorCategoryId% < 0 then bsp.AddDBCategory(bsp.privateDBSectionId%, "BrightAuthor") bsp.privateBrightAuthorCategoryId% = bsp.GetDBCategoryId(bsp.privateDBSectionId%, "BrightAuthor") end if bsp.sharedDBSectionId% = bsp.GetDBSectionId("Shared") if bsp.sharedDBSectionId% < 0 then bsp.AddDBSection("Shared") bsp.sharedDBSectionId% = bsp.GetDBSectionId("Shared") end if bsp.sharedBrightAuthorCategoryId% = bsp.GetDBCategoryId(bsp.sharedDBSectionId%, "BrightAuthor") if bsp.sharedBrightAuthorCategoryId% < 0 then bsp.AddDBCategory(bsp.sharedDBSectionId%, "BrightAuthor") bsp.sharedBrightAuthorCategoryId% = bsp.GetDBCategoryId(bsp.sharedDBSectionId%, "BrightAuthor") end if variablePosition% = 0 userVariables = bsp.currentUserVariables for each userVariableSpec in meta.userVariableSpecs name$ = userVariableSpec.name defaultValue$ = userVariableSpec.defaultValue access$ = userVariableSpec.access if access$ = "Shared" then userVariableSpec.categoryId% = bsp.sharedBrightAuthorCategoryId% else userVariableSpec.categoryId% = bsp.privateBrightAuthorCategoryId% end if categoryId% = userVariableSpec.categoryId% systemVariable$ = userVariableSpec.systemVariable$ url$ = userVariableSpec.url liveDataFeedId$ = userVariableSpec.liveDataFeedId if not userVariables.DoesExist(name$) then bsp.AddDBVariable(categoryId%, name$, defaultValue$, "", 0) userVariable = newUserVariable(bsp, name$, defaultValue$, defaultValue$, "", access$, systemVariable$) userVariables.AddReplace(name$, userVariable) else userVariable = userVariables.Lookup(name$) if userVariable.defaultValue$ <> defaultValue$ then userVariable.defaultValue$ = defaultValue$ bsp.UpdateDBVariableDefaultValue(categoryId%, name$, defaultValue$) end if end if userVariable.position% = variablePosition% variablePosition% = variablePosition% + 1 userVariable.systemVariable$ = systemVariable$ if url$ <> "" then userVariable.url$ = url$ else if liveDataFeedId$ <> "" then userVariable.liveDataFeedId$ = CleanName(liveDataFeedId$) end if videoConnector$ = getVarFromObj(userVariableSpec, "videoConnector$", "roString", "") if videoConnector$ <> "" then userVariable.videoConnector$ = videoConnector$ next ' parse presentations for each presentationIdentifier in meta.presentationIdentifiers bsp.presentations.AddReplace(presentationIdentifier.presentationId, presentationIdentifier) next ' BACON - retrieve html sites here and de-dupe them? ' BACON - parse presentations? ' BACON - parse beacons ' get list of additional files to publish for each additionalPublishedFileName in meta.additionalPublishedFiles additionalPublishedFile = { } additionalPublishedFile.fileName$ = additionalPublishedFileName additionalPublishedFile.filePath$ = GetPoolFilePath(bsp.assetPoolFiles, additionalPublishedFileName) bsp.additionalPublishedFiles.push(additionalPublishedFile) next gaa = GetGlobalAA() ' parse boseProduct section - information about Bose products in use in this presentation Sign.boseProducts = meta.boseProducts Sign.boseProductsByConnector = Sign.BuildBoseProductsByConnector(bsp, meta) ' Get the USB topology of the device and create a mapping of Bose port names in the presentation to USB device names bsp.replaceUSB700_1_with_USB_C = false bsp.replaceUSB_C_with_USB700_1 = false BuildUSBDevicesByConnector(bsp, sign) if bsp.replaceUSB700_1_with_USB_C then if gaa.usbHIDPortConfigurations.DoesExist("usb700_1") then tmp = gaa.usbHIDPortConfigurations["usb700_1"] gaa.usbHIDPortConfigurations["usbTypeC"] = tmp gaa.usbHIDPortConfigurations.Delete("usb700_1") end if if gaa.usbBMAPHIDPortConfigurations.DoesExist("usb700_1") then tmp = gaa.usbBMAPHIDPortConfigurations["usb700_1"] gaa.usbBMAPHIDPortConfigurations["usbTypeC"] = tmp gaa.usbBMAPHIDPortConfigurations.Delete("usb700_1") end if if gaa.usbAudioPortConfigurations.DoesExist("usb700_1") then tmp = gaa.usbAudioPortConfigurations["usb700_1"] gaa.usbAudioPortConfigurations["usbTypeC"] = tmp gaa.usbAudioPortConfigurations.Delete("usb700_1") end if else if bsp.replaceUSB_C_with_USB700_1 then if gaa.usbHIDPortConfigurations.DoesExist("usbTypeC") then tmp = gaa.usbHIDPortConfigurations["usbTypeC"] gaa.usbHIDPortConfigurations["usb700_1"] = tmp gaa.usbHIDPortConfigurations.Delete("usbTypeC") end if if gaa.usbBMAPHIDPortConfigurations.DoesExist("usbTypeC") then tmp = gaa.usbBMAPHIDPortConfigurations["usbTypeC"] gaa.usbBMAPHIDPortConfigurations["usb700_1"] = tmp gaa.usbBMAPHIDPortConfigurations.Delete("usbTypeC") end if if gaa.usbAudioPortConfigurations.DoesExist("usbTypeC") then tmp = gaa.usbAudioPortConfigurations["usbTypeC"] gaa.usbAudioPortConfigurations["usb700_1"] = tmp gaa.usbAudioPortConfigurations.Delete("usbTypeC") end if end if usbHIDPortConfigurations = { } usbAudioPortConfigurations = { } usbBMAPHIDPortConfigurations = { } for each connector in Sign.boseProductsByConnector boseProduct = Sign.boseProductsByConnector[connector] if bsp.boseProductSpecs[boseproduct.productname$].usbAsyncAudio then audioConfiguration = CreateObject("roAudioConfiguration") audioConfiguration.ConfigureAudio({ usbasync: 1 }) end if connector = bsp.GetRuntimeUsbConnector(connector) if gaa.usbConnectorNameToUsbSpec.DoesExist(connector) then usbSpec = gaa.usbConnectorNameToUsbSpec[connector] ' TEDTODO - is this used anywhere? boseProduct.usbSpec = usbSpec if gaa.usbHIDPortConfigurations.DoesExist(connector) then usbHIDPortConfiguration = gaa.usbHIDPortConfigurations.Lookup(connector) usbHIDPortConfigurations.AddReplace(connector, usbHIDPortConfiguration) end if if gaa.usbBMAPHIDPortConfigurations.DoesExist(connector) then usbBMAPHIDPortConfiguration = gaa.usbBMAPHIDPortConfigurations.Lookup(connector) usbBMAPHIDPortConfigurations.AddReplace(connector, usbBMAPHIDPortConfiguration) end if if gaa.usbAudioPortConfigurations.DoesExist(connector) then usbAudioPortConfiguration = gaa.usbAudioPortConfigurations.Lookup(connector) usbAudioPortConfigurations.AddReplace(connector, usbAudioPortConfiguration) end if end if next gaa.usbHIDPortConfigurations = usbHIDPortConfigurations gaa.usbAudioPortConfigurations = usbAudioPortConfigurations gaa.usbBMAPHIDPortConfigurations = usbBMAPHIDPortConfigurations if type(bsp.bmapByPort) <> "roAssociativeArray" then bsp.bmapByPort = {} endif ' Create bmap objects for the sign bsp.CreateBMapObjects(Sign) ' set default serial port speed, mode bsp.serialPortConfigurations = CreateObject("roArray", 8, true) for i% = 0 to 7 bsp.serialPortConfigurations[i%] = meta.serialPortConfigurations[i%] next Sign.udpReceivePort = meta.udpReceivePort Sign.udpSendPort = meta.udpSendPort Sign.udpAddressType$ = meta.udpAddressType if Sign.udpAddressType$ = "" then Sign.udpAddressType$ = "IPAddress" Sign.udpAddress$ = meta.udpAddress ' synchronization section Sign.enableEnhancedSynchronization = meta.enableEnhancedSynchronization Sign.deviceIsSyncMaster = meta.deviceIsSyncMaster Sign.ptpDomain$ = meta.ptpDomain$ if (Sign.enableEnhancedSynchronization) then if Sign.deviceIsSyncMaster then targetSyncMasterInRegistry = "1" else targetSyncMasterInRegistry = "0" end if rebootRequired = false ' check the sync master value in the registry. if it does not exist or is different, set it and reboot. ' check the domain value in the registry. if it does not exist or is different, set it and reboot. ptpDomainInRegistry$ = gaa.registrySection.Read("ptp_domain") if ptpDomainInRegistry$ <> Sign.ptpDomain$ then gaa.registrySection.Write("ptp_domain", Sign.ptpDomain$) bsp.diagnostics.PrintDebug("@@@ PTP domain value written to registry:" + Sign.ptpDomain$) rebootRequired = true end if ' check the syncMaster value in the registry. if it does not exist or is different, set it and reboot. syncMasterInRegistry$ = gaa.registrySection.Read("sync_master") if syncMasterInRegistry$ <> targetSyncMasterInRegistry then gaa.registrySection.Write("sync_master", targetSyncMasterInRegistry) rebootRequired = true end if if rebootRequired then gaa.registrySection.Flush() RebootSystem() end if end if Sign.flipCoordinates = meta.flipCoordinates Sign.touchCursorDisplayMode$ = meta.touchCursorDisplayMode Sign.gpio0Config = meta.gpio[0] Sign.gpio1Config = meta.gpio[1] Sign.gpio2Config = meta.gpio[2] Sign.gpio3Config = meta.gpio[3] Sign.gpio4Config = meta.gpio[4] Sign.gpio5Config = meta.gpio[5] Sign.gpio6Config = meta.gpio[6] Sign.gpio7Config = meta.gpio[7] if IsControlPort(controlPort) then for i% = 0 to 7 if meta.gpio[i%] = "input" then controlPort.EnableInput(i%) else controlPort.EnableOutput(i%) end if next end if Sign.audioConfiguration$ = meta.audioConfiguration Sign.audioAutoLevel = meta.audioAutoLevel Sign.audio1MinVolume% = meta.audio1MinVolume Sign.audio1MaxVolume% = meta.audio1MaxVolume Sign.usbTypeAMinVolume% = meta.usbTypeAMinVolume Sign.usbTypeAMaxVolume% = meta.usbTypeAMaxVolume Sign.usbTypeCMinVolume% = meta.usbTypeCMinVolume Sign.usbTypeCMaxVolume% = meta.usbTypeCMaxVolume Sign.usb700_1MinVolume% = meta.usb700_1MinVolume Sign.usb700_1MaxVolume% = meta.usb700_1MaxVolume Sign.usb700_2MinVolume% = meta.usb700_2MinVolume Sign.usb700_2MaxVolume% = meta.usb700_2MaxVolume Sign.usb700_3MinVolume% = meta.usb700_3MinVolume Sign.usb700_3MaxVolume% = meta.usb700_3MaxVolume Sign.usb700_4MinVolume% = meta.usb700_4MinVolume Sign.usb700_4MaxVolume% = meta.usb700_4MaxVolume Sign.usb700_5MinVolume% = meta.usb700_5MinVolume Sign.usb700_5MaxVolume% = meta.usb700_5MaxVolume Sign.usb700_6MinVolume% = meta.usb700_6MinVolume Sign.usb700_6MaxVolume% = meta.usb700_6MaxVolume Sign.usb700_7MinVolume% = meta.usb700_7MinVolume Sign.usb700_7MaxVolume% = meta.usb700_7MaxVolume Sign.usb_1MinVolume% = meta.usb_1MinVolume Sign.usb_1MaxVolume% = meta.usb_1MaxVolume Sign.usb_2MinVolume% = meta.usb_2MinVolume Sign.usb_2MaxVolume% = meta.usb_2MaxVolume Sign.usb_3MinVolume% = meta.usb_3MinVolume Sign.usb_3MaxVolume% = meta.usb_3MaxVolume Sign.usb_4MinVolume% = meta.usb_4MinVolume Sign.usb_4MaxVolume% = meta.usb_4MaxVolume Sign.usb_5MinVolume% = meta.usb_5MinVolume Sign.usb_5MaxVolume% = meta.usb_5MaxVolume Sign.usb_6MinVolume% = meta.usb_6MinVolume Sign.usb_6MaxVolume% = meta.usb_6MaxVolume Sign.hdmiMinVolume% = meta.hdmiMinVolume Sign.hdmiMaxVolume% = meta.hdmiMaxVolume Sign.hdmi1MinVolume% = meta.hdmi1MinVolume Sign.hdmi1MaxVolume% = meta.hdmi1MaxVolume Sign.hdmi2MinVolume% = meta.hdmi2MinVolume Sign.hdmi2MaxVolume% = meta.hdmi2MaxVolume Sign.hdmi3MinVolume% = meta.hdmi3MinVolume Sign.hdmi3MaxVolume% = meta.hdmi3MaxVolume Sign.hdmi4MinVolume% = meta.hdmi4MinVolume Sign.hdmi4MaxVolume% = meta.hdmi4MaxVolume Sign.spdifMinVolume% = meta.spdifMinVolume Sign.spdifMaxVolume% = meta.spdifMaxVolume ' enable zone support here to ensure that SetGraphicsZOrder works EnableZoneSupport(true) videoMode = GetVideoMode() if type(videoMode) = "roVideoMode" then graphicsZOrder = meta.graphicsZOrder videoMode.SetGraphicsZOrder(lcase(graphicsZOrder)) ' mosaic mode / decoders Sign.isMosaic = meta.isMosaic if Sign.isMosaic then for each mosaicDecoderSpec in meta.mosaicDecoders decoderName = mosaicDecoderSpec.decoderName timeSliceMode = mosaicDecoderSpec.timeSliceMode zOrder = mosaicDecoderSpec.zOrder friendlyName = mosaicDecoderSpec.friendlyName enableMosaicDeinterlacer = mosaicDecoderSpec.enableMosaicDeinterlacer ok = videoMode.SetDecoderMode(decoderName, timeSliceMode, int(val(zOrder)), friendlyName, enableMosaicDeinterlacer) next else decoders = videoMode.GetDecoderModes() for each decoder in decoders videoMode.SetDecoderMode(decoders[0].decoder_name, decoders[0].max_decode_size, 0, decoders[0].decoder_name, false) next end if videoMode = invalid endif ' create sign wide objects from parsed info for each liveDataFeedDescription in meta.liveDataFeedDescriptions liveDataFeed = newLiveDataFeed(bsp, liveDataFeedDescription) bsp.liveDataFeeds.AddReplace(liveDataFeed.id$, liveDataFeed) next if bsp.liveDataFeeds.IsEmpty() then if type(bsp.networkingHSM) = "roAssociativeArray" then bsp.networkingHSM.UploadDeviceDownloadProgressFileList() bsp.networkingHSM.FileListPendingUpload = false end if end if if type(userVariables) = "roAssociativeArray" then for each userVariableKey in userVariables userVariable = userVariables.Lookup(userVariableKey) if type(userVariable.liveDataFeedId$) <> "Invalid" and userVariable.liveDataFeedId$ <> "" then userVariable.liveDataFeed = bsp.liveDataFeeds.Lookup(userVariable.liveDataFeedId$) end if next end if ' reset variables if indicated in sign if meta.resetVariablesOnPresentationStart then bsp.ResetVariables() end if ' assign system variables to user variables bsp.AssignSystemVariablesToUserVariables() for each nodeAppDescription in meta.nodeAppDescriptions nodeApp = newNodeApp(bsp, nodeAppDescription) bsp.nodeApps.AddReplace(nodeApp.name$, nodeApp) next for each htmlSiteDescription in meta.htmlSiteDescriptions htmlSite = newHTMLSite(bsp, htmlSiteDescription) bsp.htmlSites.AddReplace(htmlSite.name$, htmlSite) next ' get zone descriptions, then create zone state machines zoneDescriptions = ParseZones(bsp, BrightAuthor, Sign) numZones% = zoneDescriptions.Count() Sign.zonesHSM = CreateObject("roArray", numZones%, true) for each zoneDescription in zoneDescriptions bsZoneHSM = newZoneHSM(bsp, msgPort, Sign, zoneDescription, globalVariables) if (bsZoneHSM.type$ = "VideoOrImages" or bsZoneHSM.type$ = "VideoOnly") or Sign.videoZoneHSM = invalid then Sign.videoZoneHSM = bsZoneHSM end if Sign.zonesHSM.push(bsZoneHSM) next ' BCN-14814: Update registry entry and reboot if needed chromiumPlaybackValue = meta.htmlEnableChromiumVideoPlayback if chromiumPlaybackValue <> invalid then rebootRequiredForChromium = false htmlRegistrySection = CreateObject("roRegistrySection", "html") if type(htmlRegistrySection) <> "roRegistrySection" then print "Error: Unable to create htmlRegistrySection roRegistrySection": stop end if useBsMediaPlayer = htmlRegistrySection.Read("use-brightsign-media-player") print " == useBsMediaPlayer from registry: ";useBsMediaPlayer if chromiumPlaybackValue = true and useBsMediaPlayer <> "0" then htmlRegistrySection.Write("use-brightsign-media-player", "0") rebootRequiredForChromium = true else if chromiumPlaybackValue = false and useBsMediaPlayer <> "1" then htmlRegistrySection.Write("use-brightsign-media-player", "1") rebootRequiredForChromium = true end if if rebootRequiredForChromium then htmlRegistrySection.Flush() RebootSystem() end if end if return Sign end function Sub CreateBMapObjects(sign) for each connector in sign.boseProductsByConnector m.CreateBMap(connector, sign) next end sub Function CreateBMap(connector, sign) as Object gaa = GetGlobalAA() boseProductInSign = sign.boseProductsByConnector[connector] if type(boseProductInSign.bmapCommunicationSpec) = "roAssociativeArray" then port$ = boseProductInSign.port$ port$ = m.GetRuntimeUsbConnector(port$) m.diagnostics.PrintDebug("CreateBMap on port " + port$ + " invoked") ok = m.AttemptOpenBMap(port$) if not ok then m.ScheduleRetryCreateBMap(port$) end if endif end function Sub ScheduleRetryCreateBMap(port$ as string) m.diagnostics.PrintDebug("ScheduleRetryCreateBMap on port " + port$ + " invoked") if type(m.bmapPortsToRetry) <> "roAssociativeArray" then m.bmapPortsToRetry = { } end if if not m.bmapPortsToRetry.DoesExist(port$) then aa = { } aa.port$ = port$ timer = CreateObject("roTimer") timer.SetPort(m.msgPort) timer.SetElapsed(15, 0) timer.Start() aa.timer = timer m.bmapPortsToRetry[port$] = aa m.diagnostics.PrintDebug("ScheduleRetryCreateBMap on port " + port$ + " timer set") end if end sub Function RetryCreateBMap(port$ as string) as boolean m.diagnostics.PrintDebug("RetryCreateBMap on port " + port$ + " invoked") ok = m.AttemptOpenBMap(port$) if ok and m.bmapPortsToRetry.DoesExist(port$) then m.diagnostics.PrintDebug("RetryCreateBMap on port " + port$ + " succeeded, remove from bmapPortsToRetry") m.bmapPortsToRetry.Delete(port$) else m.diagnostics.PrintDebug("RetryCreateBMap on port " + port$ + " failed") end if return ok end function Function AttemptOpenBMap(port$ as string) as boolean gaa = GetGlobalAA() m.diagnostics.PrintDebug("AttemptOpenBMap invoked on port " + port$ + " invoked") if gaa.usbBMAPHIDPortConfigurations.DoesExist(port$) then m.diagnostics.PrintDebug("AttemptOpenBMap invoked on port " + port$ + ". Exists in usbBMAPHIDPortConfigurations") bmapHIDPortConfiguration = gaa.usbBMAPHIDPortConfigurations[port$] if bmapHIDPortConfiguration.bmapProtocol = "HID" then m.diagnostics.PrintDebug("AttemptOpenBMap invoked on port " + port$ + ". Protocol is HID") usbSpec = gaa.usbConnectorNameToUsbSpec[port$] boseUSBAudioDevice = usbSpec.audiooutputspec usbPort$ = usbSpec.hidoutputspec m.diagnostics.PrintDebug("Create roBMap on port " + port$ + " (usbPort " + usbPort$ + ")") bmap = CreateObject("roBmap", usbPort$) if type(bmap) = "roBmap" then m.diagnostics.PrintDebug("AttemptOpenBMap on port " + port$ + " succeeded") bmap.SetPort(m.msgPort) bmap.SetUserData(port$) m.bmapByPort.AddReplace(port$, bmap) ' post BMap connect event connectData = {} connectData.port = port$ connectData.hidOutputSpec = usbPort$ bmapConnectEvent = { } bmapConnectEvent["EventType"] = "BMAP_CONNECT_EVENT" bmapConnectEvent["EventData"] = connectData m.msgPort.PostMessage(bmapConnectEvent) return true else m.diagnostics.PrintDebug("AttemptOpenBMap on port " + port$ + "failed") endif end if else m.diagnostics.PrintDebug("AttemptOpenBMap invoked on port " + port$ + ". Does not exist in usbBMAPHIDPortConfigurations") end if return false end function ' Support Video Mode Plugin to set videoMode for single and multi screens Function parseVideoModePlugin(videoModeInputs As Object, bsp As Object, singleScreenMode As String, multiScreenModes As Object) As Object ERR_NORMAL_END = &hFC ERR_NO_VALUE_RETURN = 224 ' Value returned when plugin does not contain specified function for each videoModePlugin in bsp.videoModePlugins setVideoModeFunction$ = "result = " + videoModePlugin.functionName$ + "(videoModeInputs, bsp)" retVal = Eval(setVideoModeFunction$) if type(retVal) = "roList" then ' compilation error bsp.diagnostics.PrintDebug("Compilation error invoking Eval to parse VideoMode script plugin: return value = " + stri(retVal)) bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_SCRIPT_PLUGIN_FAILURE, stri(retVal)) else if retVal <> ERR_NORMAL_END then ' runtime error (function may not exist) ' log the failure bsp.diagnostics.PrintDebug("Failure executing Eval to execute videoMode plugin: return value = " + stri(retVal) + ", call was " + setVideoModeFunction$) bsp.logging.WriteDiagnosticLogEntry(bsp.diagnosticCodes.EVENT_SCRIPT_PLUGIN_FAILURE, stri(retVal) + chr(9) + videoModePlugin.name$) else videoModeFromPlugin = result ' if videoMode is a string and is not empty, overwrite previously calculated screen mode if isString(videoModeFromPlugin) then if len(videoModeFromPlugin) > 0 then singleScreenMode = videoModeFromPlugin endif ' else if videoMode is a array and is not empty, overwrite previously calculated multi screen modes else if type(videoModeFromPlugin) = "roArray" then if videoModeFromPlugin.count() > 0 then multiScreenModes = videoModeFromPlugin endif endif endif next if singleScreenMode <> "" then return singleScreenMode endif return multiScreenModes End Function Function CanUseScreenModes(sign as object, videoMode as object) as boolean if type(videoMode) <> "roVideoMode" then videoMode = GetVideoMode() ' GetScreenModes is a function introduced in Series 5 players. ' Therefore we need to check if it's supported before use to avoid errors in lower series. if type(videoMode) = "roVideoMode" and findMemberFunction(videoMode, "GetScreenModes") <> invalid then screenModes = videoMode.GetScreenModes() if type(screenModes) = "roArray" and screenModes.Count() > 0 and type(sign.screens) = "roArray" and sign.screens.Count() > 0 then return true end if return false end function ' CanRotateByScreen and CanUseScreenModes return the same results. ' Split in two functions to make it more sense with where they are called. Function CanRotateByScreen(sign as object, videoMode as object) as boolean return CanUseScreenModes(sign, videoMode) end function ' HasMultiScreenOutputs will return a subset of CanUseScreenModes where the OS supports more than 1 video output Function HasMultiScreenOutputs(sign as object) as boolean videoMode = GetVideoMode() return CanUseScreenModes(sign, videoMode) and (videoMode.GetScreenModes().Count() > 1) end function Function GetVideoOutputIndexFromOS(name as string, screenModes as object) as integer for i% = 0 to screenModes.Count() - 1 if screenModes[i%].name = name then return i% next return -1 end function Function BuildBoseProductsByConnector(bsp as object, meta as object) gaa = GetGlobalAA() boseProductsByConnector = { } for each boseProductInPresentation in m.boseProducts boseProduct = { } boseProduct.productName$ = boseProductInPresentation.productName boseProduct.port$ = boseProductInPresentation.port boseProductSpec = bsp.GetBoseProductSpec(boseProduct.productName$) if type(meta.wssDeviceSpec) = "roAssociativeArray" then boseProduct.wssCommunicationSpec = ParseBoseWssCommunicationSpec(meta.wssDeviceSpec) endif if IsString(meta.bmapSpecAssetName) then bmapCommunicationSpecFileName$ = meta.bmapSpecAssetName if bmapCommunicationSpecFileName$ <> "" then bmapCommunicationPath$ = GetPoolFilePath(bsp.assetPoolFiles, bmapCommunicationSpecFileName$) if bmapCommunicationPath$ <> "" then boseProduct.bmapCommunicationSpec = ParseBoseBmapCommunicationSpec(bmapCommunicationPath$) endif endif endif boseProduct.isAudioDevice = boseProductSpec.isAudioDevice boseProduct.usbNetInterfaceIndex$ = boseProductSpec.usbNetInterfaceIndex$ boseProduct.usbInternalHub$ = boseProductSpec.usbInternalHub$ boseProductsByConnector.AddReplace(boseProduct.port$, boseProduct) if type(boseProductSpec) = "roAssociativeArray" then if boseProductSpec.bmapProtocol = "HID" then usbBMAPHIDPortConfiguration = { } usbBMAPHIDPortConfiguration.bmapProtocol = "HID" usbBMAPHIDPortConfiguration.usbTapInterfaceIndex$ = boseProductSpec.usbTapInterfaceIndex$ gaa.usbBMAPHIDPortConfigurations.AddReplace(boseProduct.port$, usbBMAPHIDPortConfiguration) end if if boseProductSpec.tapProtocol = "HID" then usbHIDPortConfiguration = { } usbHIDPortConfiguration.sendEol$ = GetEolFromSpec(boseProductSpec.sendEol$) usbHIDPortConfiguration.receiveEol$ = GetEolFromSpec(boseProductSpec.receiveEol$) usbHIDPortConfiguration.protocol$ = boseProductSpec.protocol$ usbHIDPortConfiguration.usbTapInterfaceIndex$ = boseProductSpec.usbTapInterfaceIndex$ usbHIDPortConfiguration.usbInternalHub$ = boseProductSpec.usbInternalHub$ gaa.usbHIDPortConfigurations.AddReplace(boseProduct.port$, usbHIDPortConfiguration) end if if boseProductSpec.usbAudioInterfaceIndex$ <> "" then usbAudioPortConfiguration = { } usbAudioPortConfiguration.usbAudioInterfaceIndex$ = boseProductSpec.usbAudioInterfaceIndex$ gaa.usbAudioPortConfigurations.AddReplace(boseProduct.port$, usbAudioPortConfiguration) end if end if next return boseProductsByConnector end function 'endregion 'region User Variable DB Sub ResetUserVariable(postMsg as boolean) m.currentValue$ = m.defaultValue$ m.bsp.UpdateDBVariable(m.bsp.GetCategoryIdFromAccess(m.access$), m.name$, m.currentValue$) if postMsg then userVariableChanged = { } userVariableChanged["EventType"] = "USER_VARIABLE_CHANGE" userVariableChanged["UserVariable"] = m m.bsp.msgPort.PostMessage(userVariableChanged) end if m.bsp.SendUDPNotification("refresh") end sub Sub SetCurrentUserVariableValue(value as object, postMsg as boolean) if IsString(value) then value$ = value else ' only convert integers currently value$ = stri(value) end if m.currentValue$ = value$ ' Bose special - BCN-9689 if m.bsp.sysinfo.deviceModel$ = "AU325" then print "**--**" print m.name$ print m.currentValue$ endif m.bsp.UpdateDBVariable(m.bsp.GetCategoryIdFromAccess(m.access$), m.name$, m.currentValue$) if postMsg then userVariableChanged = { } userVariableChanged["EventType"] = "USER_VARIABLE_CHANGE" userVariableChanged["UserVariable"] = m m.bsp.msgPort.PostMessage(userVariableChanged) m.bsp.SendUDPNotification("refresh") end if end sub Function GetCurrentUserVariableValue() as object return m.currentValue$ end function Sub IncrementUserVariable() currentValue% = int(val(m.currentValue$)) currentValue% = currentValue% + 1 m.currentValue$ = StripLeadingSpaces(stri(currentValue%)) m.bsp.UpdateDBVariable(m.bsp.GetCategoryIdFromAccess(m.access$), m.name$, m.currentValue$) m.bsp.SendUDPNotification("refresh") end sub Function newUserVariable(bsp as object, name$ as string, currentValue$ as string, defaultValue$ as string, mediaUrl$ as string, access$ as string, systemVariable$ as string) as object userVariable = { } userVariable.GetCurrentValue = GetCurrentUserVariableValue userVariable.SetCurrentValue = SetCurrentUserVariableValue userVariable.Increment = IncrementUserVariable userVariable.Reset = ResetUserVariable userVariable.bsp = bsp userVariable.name$ = name$ userVariable.currentValue$ = currentValue$ userVariable.defaultValue$ = defaultValue$ userVariable.mediaUrl$ = mediaUrl$ userVariable.access$ = access$ userVariable.liveDataFeed = invalid userVariable.systemVariable$ = systemVariable$ return userVariable end function Function GetCategoryIdFromAccess(access$ as string) as integer if lcase(access$) = "shared" then categoryId% = m.sharedBrightAuthorCategoryId% else categoryId% = m.privateBrightAuthorCategoryId% end if return categoryId% end function Sub UpdateDBVariable(categoryId% as integer, name$ as string, currentValue$ as string) params = { cv_param: currentValue$, vn_param: name$, cri_param: categoryId% } m.userVariablesDB.RunBackground("UPDATE Variables2 SET CurrentValue=:cv_param WHERE VariableName=:vn_param AND CategoryReferenceId=:cri_param;", params) end sub Sub UpdateDBVariableDefaultValue(categoryId% as integer, name$ as string, defaultValue$ as string) params = { dv_param: defaultValue$, vn_param: name$, cri_param: categoryId% } m.userVariablesDB.RunBackground("UPDATE Variables2 SET DefaultValue=:dv_param WHERE VariableName=:vn_param AND CategoryReferenceId=:cri_param;", params) end sub Sub UpdateDBVariableMediaUrl(categoryId% as integer, name$ as string, mediaUrl$ as string) params = { mu_param: mediaUrl$, vn_param: name$, cri_param: categoryId% } m.userVariablesDB.RunBackground("UPDATE Variables2 SET MediaUrl=:mu_param WHERE VariableName=:vn_param AND CategoryReferenceId=:cri_param;", params) end sub Sub UpdateDBVariablePosition(categoryId% as integer, name$ as string, position% as integer) params = { p_param: position%, vn_param: name$, cri_param: categoryId% } m.userVariablesDB.RunBackground("UPDATE Variables2 SET Position=:p_param WHERE VariableName=:vn_param AND CategoryReferenceId=:cri_param;", params) end sub Sub AddDBVariable(categoryId% as integer, name$ as string, defaultValue$ as string, mediaUrl$ as string, position% as integer) insertSQL$ = "INSERT INTO Variables2 (CategoryReferenceId, VariableName, CurrentValue, DefaultValue, MediaUrl, Position) VALUES(?,?,?,?,?,?);" params = CreateObject("roArray", 6, false) params[0] = categoryId% params[1] = name$ params[2] = defaultValue$ params[3] = defaultValue$ params[4] = mediaUrl$ params[5] = position% m.ExecuteDBInsert(insertSQL$, params) end sub Sub AddDBSection(sectionName$ as string) insertSQL$ = "INSERT INTO Sections (SectionName) VALUES(:name_param);" params = { name_param: sectionName$ } m.ExecuteDBInsert(insertSQL$, params) end sub Sub DeleteDBVariable(categoryId% as integer, variableName$ as string) SQLITE_COMPLETE = 100 params = { : uv_param: variableName$ } delete$ = "DELETE FROM Variables2 WHERE VariableName =:uv_param AND CategoryReferenceId = " + StripLeadingSpaces(stri(categoryId%)) + ";" deleteStatement = m.userVariablesDB.CreateStatement(delete$) if type(deleteStatement) <> "roSqliteStatement" then m.diagnostics.PrintDebug("DeleteStatement failure - " + delete$) stop end if bindResult = deleteStatement.BindByName(params) if not bindResult then m.diagnostics.PrintDebug("Bind failure") stop end if sqlResult = deleteStatement.Run() if sqlResult <> SQLITE_COMPLETE m.diagnostics.PrintDebug("sqlResult <> SQLITE_COMPLETE") end if deleteStatement.Finalise() end sub Sub AddDBCategory(sectionId% as integer, categoryName$ as string) insertSQL$ = "INSERT INTO Categories (SectionReferenceId, CategoryName) VALUES(?,?);" params = CreateObject("roArray", 2, false) params[0] = sectionId% params[1] = categoryName$ m.ExecuteDBInsert(insertSQL$, params) end sub Sub ExecuteDBInsert(insert$ as string, params as object) SQLITE_COMPLETE = 100 insertStatement = m.userVariablesDB.CreateStatement(insert$) if type(insertStatement) <> "roSqliteStatement" then m.diagnostics.PrintDebug("CreateStatement failure - " + insert$) stop end if if type(params) = "roArray" then bindResult = insertStatement.BindByOffset(params) else bindResult = insertStatement.BindByName(params) end if if not bindResult then m.diagnostics.PrintDebug("Bind failure") stop end if sqlResult = insertStatement.Run() if sqlResult <> SQLITE_COMPLETE m.diagnostics.PrintDebug("sqlResult <> SQLITE_COMPLETE") end if insertStatement.Finalise() end sub Sub ExecuteDBSelect(select$ as string, resultsCallback as object, selectData as object, params as object) SQLITE_ROWS = 102 selectStmt = m.userVariablesDB.CreateStatement(select$) if type(selectStmt) <> "roSqliteStatement" then m.diagnostics.PrintDebug("CreateStatement failure - " + select$) stop end if bindResult = true if type(params) = "roArray" then bindResult = selectStmt.BindByOffset(params) else if type(params) = "roAssociativeArray" then bindResult = selectStmt.BindByName(params) end if if not bindResult then m.diagnostics.PrintDebug("Bind failure") stop end if sqlResult = selectStmt.Run() while sqlResult = SQLITE_ROWS resultsData = selectStmt.GetData() resultsCallback(resultsData, selectData) sqlResult = selectStmt.Run() end while selectStmt.Finalise() end sub Sub GetDBVersionCallback(resultsData as object, selectData as object) selectData.version$ = resultsData["Version"] end sub Function GetDBVersion() as string selectData = { } selectData.version$ = "" select$ = "SELECT SchemaVersion.Version FROM SchemaVersion;" m.ExecuteDBSelect(select$, GetDBVersionCallback, selectData, invalid) return selectData.version$ end function Sub GetDBTableNamesCallback(resultsData as object, selectData as object) selectData.tableNames.AddReplace(resultsData["name"], true) end sub Function GetDBTableNames() as object selectData = { } selectData.tableNames = { } select$ = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;" m.ExecuteDBSelect(select$, GetDBTableNamesCallback, selectData, invalid) return selectData.tableNames end function Sub GetDBCategoryIdCallback(resultsData as object, selectData as object) selectData.categoryId% = resultsData["CategoryId"] end sub Function GetDBCategoryId(sectionId% as integer, categoryName$ as string) as object selectData = { } selectData.categoryId% = -1 select$ = "SELECT CategoryId FROM Categories WHERE SectionReferenceId = " + StripLeadingSpaces(stri(sectionId%)) + " AND CategoryName='" + categoryName$ + "';" m.ExecuteDBSelect(select$, GetDBCategoryIdCallback, selectData, invalid) return selectData.categoryId% end function Sub GetDBSectionNamesCallback(resultsData as object, selectData as object) selectData.sections.push(resultsData["SectionName"]) end sub Function GetDBSectionNames() as object selectData = { } selectData.sections = [] select$ = "SELECT Sections.SectionName FROM Sections;" m.ExecuteDBSelect(select$, GetDBSectionNamesCallback, selectData, invalid) return selectData.sections end function Sub GetDBSectionIdCallback(resultsData as object, selectData as object) selectData.sectionId% = resultsData["SectionId"] end sub Function GetDBSectionId(sectionName$ as string) as object selectData = { } selectData.sectionId% = -1 params = { sn_param: sectionName$ } select$ = "SELECT SectionId FROM Sections WHERE SectionName =:sn_param;" m.ExecuteDBSelect(select$, GetDBSectionIdCallback, selectData, params) return selectData.sectionId% end function Sub ReadSchema1TablesCallback(resultsData as object, selectData as object) sectionName$ = resultsData["SectionName"] if selectData.userVariableSets.DoesExist(sectionName$) then userVariables = selectData.userVariableSets.Lookup(sectionName$) else userVariables = [] selectData.userVariableSets.AddReplace(sectionName$, userVariables) end if variableName$ = resultsData["VariableName"] currentValue$ = resultsData["CurrentValue"] defaultValue$ = resultsData["DefaultValue"] userVariable = newUserVariable(m, variableName$, currentValue$, defaultValue$, "", "", "") userVariables.push(userVariable) end sub Function ReadSchema1Tables() as object selectData = { } selectData.userVariableSets = { } select$ = "SELECT Sections.SectionName, Variables.VariableName, Variables.CurrentValue, Variables.DefaultValue FROM Variables INNER JOIN Sections ON Sections.SectionId = Variables.SectionReferenceId ORDER BY Sections.SectionName, Variables.VariableId;" m.ExecuteDBSelect(select$, ReadSchema1TablesCallback, selectData, invalid) return selectData.userVariableSets end function Sub GetUserVariablesGivenCategoryCallback(resultsData as object, selectData as object) variableName$ = resultsData["VariableName"] currentValue$ = resultsData["CurrentValue"] defaultValue$ = resultsData["DefaultValue"] mediaUrl$ = resultsData["MediaUrl"] ' access level isn't needed for this list userVariable = newUserVariable(m, variableName$, currentValue$, defaultValue$, mediaUrl$, "", "") selectData.userVariablesList.push(userVariable) end sub Function GetUserVariablesGivenCategory(sectionName$ as string, includeSharedSection as boolean, categoryName$ as string, sortByPosition as boolean) as object selectData = { } selectData.userVariablesList = [] select$ = "SELECT Variables2.* FROM Variables2 INNER JOIN Sections ON Sections.SectionId=Categories.SectionReferenceId INNER JOIN Categories ON Categories.CategoryId=Variables2.CategoryReferenceId WHERE Categories.CategoryName='" + categoryName$ + "' AND (" if includeSharedSection then select$ = select$ + "Sections.SectionName='Shared' OR " end if select$ = select$ + "Sections.SectionName='" + sectionName$ + "')" if sortByPosition then select$ = select$ + " ORDER BY Variables2.Position" end if select$ = select$ + ";" m.ExecuteDBSelect(select$, GetUserVariablesGivenCategoryCallback, selectData, invalid) return selectData.userVariablesList end function Sub DoGetCategoriesCallback(resultsData as object, selectData as object) categoryName$ = resultsData["CategoryName"] if (categoryName$ <> "BrightAuthor") or (selectData.includeBrightAuthorCategory) then selectData.userVariableCategories.push(categoryName$) end if end sub Function DoGetCategories(sectionName$ as string, includeShared as boolean, includeBrightAuthorCategory) as object selectData = { } selectData.userVariableCategories = [] selectData.includeBrightAuthorCategory = includeBrightAuthorCategory params = { : sn_param: sectionName$ } select$ = "SELECT CategoryName FROM Categories INNER JOIN Sections ON Sections.SectionId = Categories.SectionReferenceId WHERE " if includeShared then select$ = select$ + "Sections.SectionName = 'Shared' OR " end if select$ = select$ + "Sections.SectionName =:sn_param;" selectStmt = m.userVariablesDB.CreateStatement(select$) if type(selectStmt) <> "roSqliteStatement" then m.diagnostics.PrintDebug("CreateStatement failure - " + select$) stop end if bindResult = selectStmt.BindByName(params) if not bindResult then m.diagnostics.PrintDebug("Bind failure") stop end if m.ExecuteDBSelect(select$, DoGetCategoriesCallback, selectData, params) return selectData.userVariableCategories end function Sub ReadVariablesCallback(resultsData as object, selectData as object) sectionName$ = resultsData["SectionName"] if sectionName$ = selectData.presentationName$ or sectionName$ = "Shared" then variableName$ = resultsData["VariableName"] currentValue$ = resultsData["CurrentValue"] defaultValue$ = resultsData["DefaultValue"] mediaUrl$ = resultsData["MediaUrl"] if sectionName$ = "Shared" then access$ = "Shared" else access$ = "Private" end if userVariable = newUserVariable(selectData.bsp, variableName$, currentValue$, defaultValue$, mediaUrl$, access$, "") userVariable.position% = -1 selectData.currentUserVariables.AddReplace(variableName$, userVariable) end if end sub Function ReadVariables(presentationName$ as string) as object selectData = { } selectData.bsp = m selectData.presentationName$ = presentationName$ selectData.currentUserVariables = { } select$ = "SELECT Sections.SectionName, Variables2.VariableName, Variables2.CurrentValue, Variables2.DefaultValue, Variables2.MediaUrl FROM Variables2 INNER JOIN Sections ON Sections.SectionId=Categories.SectionReferenceId INNER JOIN Categories ON Categories.CategoryId=Variables2.CategoryReferenceId WHERE Categories.CategoryName='BrightAuthor' ORDER BY Sections.SectionName;" m.ExecuteDBSelect(select$, ReadVariablesCallback, selectData, invalid) return selectData.currentUserVariables end function Sub SetDBVersion(version$ as string) insertSQL$ = "INSERT INTO SchemaVersion (Version) VALUES(:version_param);" params = { version_param: version$ } m.ExecuteDBInsert(insertSQL$, params) end sub Sub UpdateDBVersion(version$ as string) params = { v_param: version$ } m.userVariablesDB.RunBackground("UPDATE SchemaVersion SET Version=:v_param;", params) end sub Sub CreateDBTable(statement$ as string) SQLITE_COMPLETE = 100 createStmt = m.userVariablesDB.CreateStatement(statement$) if type(createStmt) <> "roSqliteStatement" then m.diagnostics.PrintDebug("CreateStatement failure - " + statement$) stop end if sqlResult = createStmt.Run() if sqlResult <> SQLITE_COMPLETE m.diagnostics.PrintDebug("sqlResult <> SQLITE_COMPLETE") end if createStmt.Finalise() end sub Sub DeleteDBTable(tableName$ as string) SQLITE_COMPLETE = 100 deleteStmt = m.userVariablesDB.CreateStatement("DROP TABLE " + tableName$) if type(deleteStmt) <> "roSqliteStatement" then m.diagnostics.PrintDebug("CreateStatement failure - DeleteDBTable") stop end if sqlResult = deleteStmt.Run() if sqlResult <> SQLITE_COMPLETE m.diagnostics.PrintDebug("sqlResult <> SQLITE_COMPLETE") end if deleteStmt.Finalise() end sub Sub CreateSchema2Tables() m.CreateDBTable("CREATE TABLE Categories (CategoryId INTEGER PRIMARY KEY AUTOINCREMENT, SectionReferenceId INT, CategoryName TEXT);") m.CreateDBTable("CREATE TABLE Variables2 (VariableId INTEGER PRIMARY KEY AUTOINCREMENT, CategoryReferenceId INT, VariableName text, CurrentValue TEXT, DefaultValue TEXT, MediaUrl TEXT, Position INT);") end sub Sub DropSchema1Tables() m.DeleteDBTable("Variables") end sub Function DBTablesExist(existingTables, expectedTables) as boolean for each tableName in expectedTables if not existingTables.DoesExist(tableName) then m.diagnostics.PrintDebug("Table " + tableName + " does not exist in userVariables.db - reset db") return false end if end for return true end function Function DbIsValid() as boolean ' Check for validity of userVariables DB - not an exhaustive check, it merely checks to ensure that the appropriate ' tables are present ' ' Check for existence of schema table. If it doesn't exist, db is invalid ' If it exists, check its value and base subsequent checks on the version ' Check to ensure that the expected tables exist tables = m.GetDBTableNames() ' common tables commonTables = ["SchemaVersion", "Sections"] schema1Tables = ["Variables"] schema2Tables = ["Variables2", "Categories"] if not m.DBTablesExist(tables, commonTables) then return false end if currentSchemaVersion$ = "2.0" existingSchemaVersion$ = m.GetDBVersion() ' check version of existing db schema to determine what tables to check for if existingSchemaVersion$ <> currentSchemaVersion$ then tablesExist = m.DBTablesExist(tables, schema1Tables) else tablesExist = m.DBTablesExist(tables, schema2Tables) end if return tablesExist end function Sub ReadVariablesDB(presentationName$ as string) SQLITE_ROWS = 102 m.variablesDBExists = true m.dbSchemaVersion$ = "2.0" if type(m.userVariablesDB) <> "roSqliteDatabase" then m.userVariablesDB = CreateObject("roSqliteDatabase") m.userVariablesDB.SetPort(m.msgPort) m.diagnostics.PrintDebug("Open userVariables.db") ok = m.userVariablesDB.Open("userVariables.db") if ok then ok = m.DBIsValid() end if if ok then version$ = m.GetDBVersion() if version$ <> m.dbSchemaVersion$ then userVariableSets = m.ReadSchema1Tables() m.CreateSchema2Tables() ' store old data in new tables for each sectionName in userVariableSets sectionId% = m.GetDBSectionId(sectionName) m.AddDBCategory(sectionId%, "BrightAuthor") categoryId% = m.GetDBCategoryId(sectionId%, "BrightAuthor") userVariables = userVariableSets.Lookup(sectionName) position% = 0 for each userVariable in userVariables m.AddDBVariable(categoryId%, userVariable.name$, userVariable.defaultValue$, "", position%) if userVariable.currentValue$ <> userVariable.defaultValue$ then m.UpdateDBVariable(categoryId%, userVariable.name$, userVariable.currentValue$) end if position% = position% + 1 next next ' drop old tables m.DropSchema1Tables() ' update version m.UpdateDBVersion(m.dbSchemaVersion$) end if else ' in case there is a corrupt file m.diagnostics.PrintDebug("Unable to open userVariables.db, attempt to delete file then create db.") m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_DELETE_USER_VARIABLES_DB, "") ok = DeleteFile("userVariables.db") ok = m.userVariablesDB.Create("userVariables.db") if not ok then m.diagnostics.PrintDebug("Unable to create userVariables.db") return end if m.CreateDBTable("CREATE TABLE SchemaVersion (Version TEXT);") m.SetDBVersion(m.dbSchemaVersion$) m.CreateDBTable("CREATE TABLE Sections (SectionId INTEGER PRIMARY KEY AUTOINCREMENT, SectionName TEXT);") m.CreateSchema2Tables() end if end if ' get sections, variables for BrightAuthor category m.currentUserVariables = m.ReadVariables(presentationName$) end sub Function GetUserVariablesByCategoryList(categoryName$ as string) as object return m.GetUserVariablesGivenCategory(m.activePresentation$, true, categoryName$, true) end function Function GetDBCategoryNames(sectionName$ as string) as object return m.DoGetCategories(sectionName$, false, true) end function Function GetUserVariableCategoryList(sectionName$ as string) as object return m.DoGetCategories(sectionName$, true, false) end function Function GetOrderedVariables(sectionName$ as string) as object return m.GetUserVariablesGivenCategory(m.activePresentation$, true, "BrightAuthor", false) end function Sub ExportVariablesDBToAsciiFile(file as object) file.SendLine("Version" + chr(9) + m.dbSchemaVersion$) sectionNames = m.GetDBSectionNames() for each sectionName in sectionNames file.SendLine("Section" + chr(9) + sectionName) sectionId% = m.GetDBSectionId(sectionName) if sectionId% > 0 then categoryNames = m.GetDBCategoryNames(sectionName) for each categoryName in categoryNames file.SendLine("Category" + chr(9) + categoryName) userVariablesList = m.GetUserVariablesGivenCategory(sectionName, false, categoryName, false) for each userVariable in userVariablesList file.SendLine(userVariable.name$ + chr(9) + userVariable.currentValue$ + chr(9) + userVariable.defaultValue$) next next end if next end sub Function GetUserVariable(variableName$ as string) as object userVariable = invalid if m.currentUserVariables.DoesExist(variableName$) then userVariable = m.currentUserVariables.Lookup(variableName$) end if return userVariable end function Sub DeleteVariable(variableName$ as string) userVariables = m.currentUserVariables if userVariables.DoesExist(variableName$) then userVariable = userVariables.Lookup(variableName$) if lcase(userVariable.access$) = "shared" then categoryId% = m.sharedBrightAuthorCategoryId% else categoryId% = m.privateBrightAuthorCategoryId% end if m.DeleteDBVariable(categoryId%, variableName$) userVariables.Delete(variableName$) end if m.SendUDPNotification("refresh") end sub Sub ResetVariables() userVariables = m.currentUserVariables userVariableList = CreateObject("roList") for each variableName in userVariables userVariable = userVariables.Lookup(variableName) userVariableList.AddTail(userVariable) next for each userVariable in userVariableList userVariable.Reset(false) next userVariablesReset = { } userVariablesReset["EventType"] = "USER_VARIABLES_RESET" m.msgPort.PostMessage(userVariablesReset) m.SendUDPNotification("refresh") end sub Sub AssignSystemVariableToUserVariables(userVariable as object) suffix$ = "$" videoConnector$ = getVarFromObj(userVariable, "videoConnector$", "roString", "") if videoConnector$ <> "" then suffix$ = "_" + videoConnector$ + "$" if userVariable.systemVariable$ = "SerialNumber" then userVariable.SetCurrentValue(m.sysInfo.deviceUniqueID$, false) else if userVariable.systemVariable$ = "IPAddressWired" then userVariable.SetCurrentValue(m.sysInfo.ipAddressWired$, false) else if userVariable.systemVariable$ = "IPAddressWireless" then userVariable.SetCurrentValue(m.sysInfo.ipAddressWireless$, false) else if userVariable.systemVariable$ = "FirmwareVersion" then userVariable.SetCurrentValue(m.sysInfo.deviceFWVersion$, false) else if userVariable.systemVariable$ = "ScriptVersion" then userVariable.SetCurrentValue(m.sysInfo.autorunVersion$, false) else if userVariable.systemVariable$ = "EdidMonitorSerialNumber" then userVariable.SetCurrentValue(m.sysInfo["edidMonitorSerialNumber"+suffix$], false) else if userVariable.systemVariable$ = "EdidYearOfManufacture" then userVariable.SetCurrentValue(m.sysInfo["edidYearOfManufacture"+suffix$], false) else if userVariable.systemVariable$ = "EdidMonitorName" then userVariable.SetCurrentValue(m.sysInfo["edidMonitorName"+suffix$], false) else if userVariable.systemVariable$ = "EdidManufacturer" then userVariable.SetCurrentValue(m.sysInfo["edidManufacturer"+suffix$], false) else if userVariable.systemVariable$ = "EdidUnspecifiedText" then userVariable.SetCurrentValue(m.sysInfo["edidUnspecifiedText"+suffix$], false) else if userVariable.systemVariable$ = "EdidSerialNumber" then userVariable.SetCurrentValue(m.sysInfo["edidSerialNumber"+suffix$], false) else if userVariable.systemVariable$ = "EdidManufacturerProductCode" then userVariable.SetCurrentValue(m.sysInfo["edidManufacturerProductCode"+suffix$], false) else if userVariable.systemVariable$ = "EdidWeekOfManufacture" then userVariable.SetCurrentValue(m.sysInfo["edidWeekOfManufacture"+suffix$], false) else if userVariable.systemVariable$ = "ActivePresentation" then if IsString(m.activePresentation$) then userVariable.SetCurrentValue(m.activePresentation$, false) else userVariable.SetCurrentValue("", false) end if else if userVariable.systemVariable$ = "BrightAuthorVersion" then userVariable.SetCurrentValue(m.sysInfo.baconVersion$, false) else if userVariable.systemVariable$ = "PlayerModelNumber" then userVariable.SetCurrentValue(m.sysInfo.deviceModel$, false) end if end sub Sub AssignSystemVariablesToUserVariables() for each variableName in m.currentUserVariables userVariable = m.currentUserVariables.Lookup(variableName) if type(userVariable) = "roAssociativeArray" then m.AssignSystemVariableToUserVariables(userVariable) end if next end sub Sub GenerateSessionGuid() ' Generate uuidv1 from scratch ' Ref: https://datatracker.ietf.org/doc/html/rfc4122 ' Ref: http://guid.one/guid/make ' Convert time stamp to bytes hexTime = GetHexTime() timeLow = Right(hexTime, 8) timeMid = Mid(hexTime, 4, 4) timeHiAndVersion = Left(hexTime, 4) ' Set the version. Take the 7th byte perform an and operation ' with 0x0f followed by an or operation of 0x10. seventhByte = Mid(timeHiAndVersion, 0, 2) seventhByte = HexLogicOperator(seventhByte, "0f", "and") seventhByte = HexLogicOperator(seventhByte, "10", "or") timeHiAndVersion = seventhByte + Right(timeHiAndVersion, 2) clockSeq = GetClockSequence() ' Set the variant. Take the 9th byte perform an and operation ' with 0x3f followed by an or operation of 0x80. ninthByte = Mid(clockSeq, 0, 2) ninthByte = HexLogicOperator(ninthByte, "3f", "and") ninthByte = HexLogicOperator(ninthByte, "80", "or") clockSeq = ninthByte + Right(clockSeq, 2) node = GetNode() uuid = lcase(timeLow + "-" + timeMid + "-" + timeHiAndVersion + "-" + clockSeq + "-" + node) m.sysInfo["sessionGuid$"] = uuid end sub Function HexLogicOperator(hex1 as String, hex2 as String, operator as String) as String if operator <> "and" and operator <> "or" then print "Invalid operator" return "" end if ' Convert the hex strings to decimal dec1 = HexToDec(hex1) dec2 = HexToDec(hex2) ' Perform the logical operation if operator = "and" then result = dec1 And dec2 else result = dec1 Or dec2 end if return DecToHex(result) end function Function HexToDec(hexString As String) As Integer hexString = UCase(hexString) decimal = 0 length = Len(hexString) for i = 1 to length digit = Mid(hexString, i, 1) value = HexDigitToDec(digit) if value = -1 then ' Invalid hexadecimal digit return -1 end if decimal = decimal * 16 + value next return decimal end function Function GetHexTime() as String systemTime = CreateObject("roSystemTime") currentTime = systemTime.GetUtcDateTime() unixSec = currentTime.ToSecondsSinceEpoch() unixMilisec = currentTime.GetMillisecond() hexSec = DecToHex(unixSec) hexMilisec = DecToHex(unixMilisec) ' Get the time between Epoch and 1582-10-15 00:00:00 UTC ' Seconds in decimal is 12219292800, hex is 2d8539c80 hexSecondsBetweenEpochAnd1582 = "2d8539c80" hexTime = HexAddition(hexSecondsBetweenEpochAnd1582, hexSec) ' Convert to ms and add the milliseconds, multiple by 1000 hexTime = HexMultiply(hexTime, "3e8") hexTime = HexAddition(hexTime, hexMilisec) ' Convert to 100-nanosecond, multiple by 10,000 hexTime = HexMultiply(hexTime, "2710") ' Check the length of the hexTime, and pad/trim to 16 bytes return FixHexTimeLength(hexTime) end function Function FixHexTimeLength(hexTime as String) as String ' Check the length of the hexTime, and pad/trim to 16 bytes if Len(hexTime) > 16 then hexTime = Left(hexTime, 16) else if Len(hexTime) < 16 then hexTime = PadLeft(hexTime, 16) end if return hexTime end function Function DecToHex(decNum as Double) as String ' Divide the nubmer by 16 if it is greater than largest 32-bit integer if decNum > 2147483647 then decNum = decNum / 16 hex = DecToHex(decNum) return HexMultiply(hex, "10") end if ' The function only supports positive integers or zero if decNum <= 0 then return "0" end if ' Convert a decimal number to hexadecimal hexNum = "" hexDigits = "0123456789ABCDEF" ' Perform division and append hex digits in reverse order while decNum > 0 remainder = decNum Mod 16 hexDigit = Mid(hexDigits, remainder + 1, 1) hexNum = hexDigit + hexNum decNum = int(decNum / 16) end while ' Return the hexadecimal result return hexNum end function Function HexAddition(hexNum1 as String, hexNum2 as String) as String ' Pad the shorter number with leading zeros maxLength = MaxInteger(Len(hexNum1), Len(hexNum2)) hexNum1 = PadLeft(hexNum1, maxLength) hexNum2 = PadLeft(hexNum2, maxLength) ' Perform hexadecimal addition digit by digit carry = 0 hexResult = "" for i = maxLength - 1 to 0 step -1 digit1 = HexDigitToDec(Mid(hexNum1, i + 1, 1)) digit2 = HexDigitToDec(Mid(hexNum2, i + 1, 1)) sum = digit1 + digit2 + carry ' Calculate the carry and the result digit carry = Int(sum / 16) resultDigit = DecToHex(sum Mod 16) hexResult = resultDigit + hexResult end for ' Add the final carry if necessary if carry > 0 then hexResult = DecToHex(carry) + hexResult end if ' Return the hexadecimal result return hexResult end function Function MaxInteger(a as Integer, b as Integer) as Integer if (b > a) then return b else return a end if end function Function HexMultiply(hexNum1 as String, hexNum2 as String) as String hexNum1 = UCase(hexNum1) hexNum2 = UCase(hexNum2) product = [] for i = Len(hexNum2) to 1 Step -1 digit2 = HexDigitToDec(Mid(hexNum2, i, 1)) carry = 0 tempProduct = [] for j = Len(hexNum1) to 1 Step -1 digit1 = HexDigitToDec(Mid(hexNum1, j, 1)) productDigit = digit1 * digit2 + carry remainder = productDigit Mod 16 carry = Int(productDigit / 16) tempProduct.unshift(DecToHex(remainder)) next if carry > 0 then tempProduct.unshift(DecToHex(carry)) end if padding = PadLeft("", Len(hexNum2) - i) tempProduct.Push(padding) product.Push(ArrayToString(tempProduct)) next result = "0" for each num In product result = HexAddition(result, num) next return result end function Function ArrayToString(arr as Object) as String ' Convert an array to a string str = "" for i = 0 to arr.Count() - 1 str = str + arr[i] next if str = "" return "0" return str end function Function HexDigitToDec(hexDigit as String) as Integer ' Convert a single hexadecimal digit to decimal hexDigits = "0123456789ABCDEF" return InStr(hexDigits, UCase(hexDigit)) - 1 end function Function PadLeft(str as String, length as Integer) as String ' Pad the left side of a string with leading zeros padding = String(length - Len(str), "0") return padding + str end function Function GetClockSequence() as String di = CreateObject("roDeviceInfo") return Left(di.GetDeviceUniqueId(), 4) end function Function GetNode() as String nc = CreateObject("roNetworkConfiguration", 0) address = nc.GetCurrentConfig().ethernet_mac address = ReplaceString(address, ":", "") return address end function Function ReplaceString (str as String, find as String, replace as String) as String regex = CreateObject("roRegEx", find, "i") return regex.ReplaceAll(str, replace) end function Sub ClearSessionGuid() m.sysInfo["sessionGuid$"] = "" end sub 'endregion 'region newSign helpers Function GetBPConfiguration(bpHardware$ as string, bp900ConfigureAutomatically as boolean, bp900Configuration% as integer, bp200ConfigureAutomatically as boolean, bp200Configuration% as integer) as integer if bpHardware$ = "BP900" then if bp900ConfigureAutomatically then return 0 else return bp900Configuration% end if else if bp200ConfigureAutomatically then return 0 else return bp200Configuration% end if end if end function Function GetYesNoFromBool(val as object) as string if IsBoolean(val) then if val then return "yes" endif else if IsInteger(val) then if val = 1 then return "yes" endif endif return "no" end function Function IsTruthy(value) valueType = type(value) if valueType = "Boolean" or valueType = "roBoolean" then if value = true or value = True then return true else if value = false or value = False then return false end if else if valueType = "String" or valueType = "roString" then if value = "true" or value = "True" or value = "1" or value = "on" or value = "On" or value = "yes" or value = "Yes" then return true else if value = "false" or value = "False" or value = "0" or value = "off" or value = "Off" or value = "no" or value = "No" then return false end if else if valueType = "Integer" or valueType = "roInteger" then if value = 1 then return true else if value = 0 then return false end if end if return invalid end function Function GetValidBool(value, defaultValue as boolean) as boolean returnValue = IsTruthy(value) if returnValue = invalid then returnValue = defaultValue endif return returnValue end function Function GetBoolFromString(value, defaultValue as boolean) as boolean if IsString(value) and value = "" then return defaultValue endif val = IsTruthy(value) if val = invalid then return defaultValue else return val endif end function Function GetStringFromBool(boolValue as boolean) as string if boolValue then return "true" else return "false" endif end function Function GetIntFromString(str$ as string) as integer if IsString(str$) and str$ <> "" then return int(val(str$)) else return 0 end if end function Function GetValidInt(val, defaultValue as Integer) as Integer if IsInteger(val) then return val else return defaultValue end if end function Function GetValidString(val, defaultValue as string) as String if IsString(val) then return val else return defaultValue end if end function 'endregion 'region ZoneHSM Function newZoneHSM(bsp as object, msgPort as object, sign as object, zoneDescription as object, globalVariables as object) as object zoneType$ = zoneDescription.type$ ' create objects and read zone specific parameters if zoneType$ = "VideoOrImages" then zoneHSM = newVideoOrImagesZoneHSM(bsp, zoneDescription) else if zoneType$ = "VideoOnly" then zoneHSM = newVideoZoneHSM(bsp, zoneDescription) else if zoneType$ = "Images" then zoneHSM = newImagesZoneHSM(bsp, zoneDescription) else if zoneType$ = "AudioOnly" then zoneHSM = newAudioZoneHSM(bsp, zoneDescription) else if zoneType$ = "EnhancedAudio" then zoneHSM = newEnhancedAudioZoneHSM(bsp, zoneDescription) else if zoneType$ = "Ticker" then zoneHSM = newTickerZoneHSM(bsp, sign, zoneDescription) else if zoneType$ = "Clock" then zoneHSM = newClockZoneHSM(bsp, zoneDescription) else if zoneType$ = "BackgroundImage" then zoneHSM = newBackgroundImageZoneHSM(bsp, zoneDescription) else if zoneType$ = "Control" then zoneHSM = newControlZoneHSM(bsp, zoneDescription) end if zoneHSM.type$ = zoneType$ zoneHSM.CreateObjects = CreateObjects zoneHSM.CreateCommunicationObjects = CreateCommunicationObjects zoneHSM.CreateObjectsNeededForTransitionCommands = CreateObjectsNeededForTransitionCommands zoneHSM.CreateObjectForTransitionCommand = CreateObjectForTransitionCommand zoneHSM.CreateSerial = CreateSerial zoneHSM.CreateUDPSender = CreateUDPSender zoneHSM.CheckForSyncEventInEventList = CheckForSyncEventInEventList zoneHSM.InitializeZoneCommon = InitializeZoneCommon zoneHSM.language$ = globalVariables.language$ zoneHSM.playlist = newPlaylist(bsp, zoneHSM, sign, zoneDescription.playlist) zoneHSM.playbackActive = false return zoneHSM end function Function newPlaylist(bsp as object, zoneHSM as object, sign as object, playlistDescription as object) as object playlistBS = { } playlistBS.name$ = playlistDescription.name ' get states stateDescriptionList = playlistDescription.stateDescriptions if type(stateDescriptionList) <> "roArray" then print "Invalid autoplay file - state list not found" : stop initialMediaStateName = playlistDescription.initialMediaStateName if zoneHSM.type$ = "Ticker" then zoneHSM.rssDataFeedItems = [] for each stateDescription in stateDescriptionList tickerItem = newTickerItem(bsp, zoneHSM, stateDescription) if tickerItem <> invalid then zoneHSM.rssDataFeedItems.push(tickerItem) end if next else zoneHSM.transitionDescriptions = [] zoneHSM.stateTable = { } for each stateDescription in stateDescriptionList bsState = newState(bsp, zoneHSM, sign, stateDescription, invalid) next initialMediaStateName = playlistDescription.initialMediaStateName ' find the initial state for the playlist for each stateName in zoneHSM.stateTable bsState = zoneHSM.stateTable[stateName] if bsState.id$ = initialMediaStateName then playlistBS.firstState = GetInitialState(zoneHSM, bsState) exit for end if next ' find the initial states for each superstate allStates = CreateObject("roArray", 1, true) for each stateName in zoneHSM.stateTable allStates.push(stateName) next for each stateName in allStates bsState = zoneHSM.stateTable[stateName] if bsState.type$ = "superState" then initialStateName$ = bsState.initialStateName$ for each innerStateName in zoneHSM.stateTable innerState = zoneHSM.stateTable[innerStateName] if innerState.id$ = initialStateName$ then bsState.firstState = GetInitialState(zoneHSM, innerState) exit for end if next end if next ' get transitions from top level states for each transitionDescription in playlistDescription.transitionDescriptions newTransition(bsp, zoneHSM, sign, transitionDescription) next ' get transitions from super states for each transitionDescription in zoneHSM.transitionDescriptions newTransition(bsp, zoneHSM, sign, transitionDescription) next end if return playlistBS end function Function GetInitialState(zoneHSM as object, state as object) as object if state.type$ <> "superState" then return state end if initialStateName$ = state.initialStateName$ for each stateName in zoneHSM.stateTable state = zoneHSM.stateTable[stateName] if state.id$ = initialStateName$ then return GetInitialState(zoneHSM, state) end if next end function Function ConvertToByteArray(input$ as string) as object inputSpec = CreateObject("roByteArray") ' convert serial$ into byte array byteString$ = StripLeadingSpaces(input$) commaPosition = -1 while commaPosition <> 0 commaPosition = instr(1, byteString$, ",") if commaPosition = 0 then byteValue = val(byteString$) else byteValue = val(left(byteString$, commaPosition - 1)) end if inputSpec.push(byteValue) byteString$ = mid(byteString$, commaPosition + 1) end while return inputSpec end function Function GetSimpleEventDataFromUserEvent(userEvent as object) as object return userEvent.data.data end function Sub newTransition(bsp as object, zoneHSM as object, sign as object, transitionSpec as object) stateTable = zoneHSM.stateTable sourceMediaState$ = transitionSpec.sourceMediaState ' given the sourceMediaState, find the associated bsState bsState = stateTable.Lookup(sourceMediaState$) userEventName$ = transitionSpec.userEvent.name userEventData = transitionSpec.userEvent.data nextMediaState$ = transitionSpec.targetMediaState transition = { } transition.AssignEventInputToUserVariable = AssignEventInputToUserVariable transition.AssignWildcardInputToUserVariable = AssignWildcardInputToUserVariable transition.targetMediaState$ = nextMediaState$ transition.targetMediaStateIsPreviousState = transitionSpec.targetMediaStateIsPreviousState transition.remainOnCurrentStateActions = transitionSpec.remainOnCurrentStateActions ' if the transition points to a superstate, point it to the first state for the superstate instead if transition.targetMediaState$ <> "" then targetState = zoneHSM.stateTable[transition.targetMediaState$] if targetState.type$ = "superState" and targetState.firstState <> invalid then transition.targetMediaState$ = targetState.firstState.id$ end if end if transition.assignInputToUserVariable = transitionSpec.assignInputToUserVariable if transition.assignInputToUserVariable then if IsString(transitionspec.variableToAssignFromInput$) then transition.variableToAssignFromInput$ = transitionSpec.variableToAssignFromInput$ transition.variableToAssignFromInput = bsp.GetUserVariable(transition.variableToAssignFromInput$) else transition.variableToAssignFromInput = invalid end if else transition.variableToAssign$ = "" end if transition.assignWildcardToUserVariable = transitionSpec.assignWildcardToUserVariable if transition.assignWildcardToUserVariable then transition.variableToAssignFromWildcard = invalid if IsString(transitionspec.variableToAssign$) then variableToAssign$ = transitionSpec.variableToAssign$ if variableToAssign$ <> "" then transition.variableToAssignFromWildcard = bsp.GetUserVariable(variableToAssign$) if transition.variableToAssignFromWildcard = invalid then bsp.diagnostics.PrintDebug("User variable " + variableToAssign$ + " not found.") end if end if end if end if if userEventName$ = "gpioUserEvent" then transition.buttonPanelIndex% = userEventData.buttonPanelIndex% transition.buttonNumber$ = userEventData.buttonNumber$ transition.buttonDirection$ = userEventData.buttonDirection$ transition.configuration$ = userEventData.configuration$ if transition.configuration$ = "pressContinuous" then transition.initialHoldoff$ = userEventData.initialHoldoff$ transition.repeatInterval$ = userEventData.repeatInterval$ end if bsp.ConfigureGPIOInput(transition.buttonNumber$) if transition.buttonDirection$ = "down" then bsState.gpioEvents[transition.buttonNumber$] = transition else bsState.gpioUpEvents[transition.buttonNumber$] = transition end if else if userEventName$ = "bp900AUserEvent" or userEventName$ = "bp900BUserEvent" or userEventName$ = "bp900CUserEvent" or userEventName$ = "bp900DUserEvent" or userEventName$ = "bp200AUserEvent" or userEventName$ = "bp200BUserEvent" or userEventName$ = "bp200CUserEvent" or userEventName$ = "bp200DUserEvent" then transition.configuration$ = userEventData.configuration$ transition.buttonPanelIndex% = userEventData.buttonPanelIndex% transition.buttonNumber$ = userEventData.buttonNumber$ if transition.configuration$ = "pressContinuous" then transition.initialHoldoff$ = userEventData.initialHoldoff$ transition.repeatInterval$ = userEventData.repeatInterval$ end if bsp.ConfigureBPInput(transition.buttonPanelIndex%, transition.buttonNumber$) bpEvents = bsState.bpEvents currentBPEvent = bpEvents[transition.buttonPanelIndex%] currentBPEvent.AddReplace(transition.buttonNumber$, transition) else if userEventName$ = "gpsEvent" then eventDirection$ = userEventData.regionEventData.direction transition.radiusInFeet = userEventData.regionEventData.radiusInFeet transition.latitude = userEventData.regionEventData.latitude transition.latitudeInRadians = ConvertDecimalDegtoRad(transition.latitude) transition.longitude = userEventData.regionEventData.longitude transition.longitudeInRadians = ConvertDecimalDegtoRad(transition.longitude) if lcase(eventDirection$) = "enter" then bsState.gpsEnterRegionEvents.push(transition) else bsState.gpsExitRegionEvents.push(transition) end if else if userEventName$ = "serial" then userEventData = transitionSpec.userEvent.data port$ = StripLeadingSpaces(userEventData.port) port$ = m.bsp.GetRuntimeUsbConnector(port$) serial$ = userEventData.data if IsUsbPort(port$) then usbSpec = GetGlobalAA().usbConnectorNameToUsbSpec.Lookup(port$) if GetGlobalAA().usbHIDPortConfigurations.DoesExist(port$) then usbHIDPortConfiguration = GetGlobalAA().usbHIDPortConfigurations[port$] protocol$ = usbHIDPortConfiguration.protocol$ else protocol$ = "" end if eventKey$ = port$ else port% = int(val(port$)) serialPortConfiguration = sign.serialPortConfigurations[port%] protocol$ = serialPortConfiguration.protocol$ eventKey$ = port$ end if serialEvents = bsState.serialEvents if type(serialEvents[eventKey$]) <> "roAssociativeArray" then serialEvents[eventKey$] = { } end if if protocol$ = "Binary" then if type(serialEvents[eventKey$].streamInputTransitionSpecs) <> "roArray" then serialEvents[eventKey$].streamInputTransitionSpecs = CreateObject("roArray", 1, true) end if streamInputTransitionSpec = { } streamInputTransitionSpec.transition = transition streamInputTransitionSpec.inputSpec = ConvertToByteArray(serial$) streamInputTransitionSpec.asciiSpec = serial$ serialEvents[eventKey$].streamInputTransitionSpecs.push(streamInputTransitionSpec) else if protocol$ <> "" then serialPortEvents = serialEvents[eventKey$] serialPortEvents[serial$] = transition end if else if userEventName$ = "timeout" then bsState.mstimeoutValue% = userEventData.interval * 1000 bsState.mstimeoutEvent = transition else if userEventName$ = "bmapHexEvent" then port$ = userEventData.port bmapHexString$ = userEventData.data bmapHexInputEvents = bsState.bmapHexInputEvents if type(bmapHexInputEvents[port$]) <> "roAssociativeArray" then bmapHexInputEvents[port$] = CreateObject("roAssociativeArray") end if bmapHexEventsForPort = bmapHexInputEvents[port$] bmapHexEventsForPort[bmapHexString$] = transition else if userEventName$ = "bmapEvent" then if type(bsState.bmapEvents) <> "roArray" then bsState.bmapEvents = [] endif bmapEvent = {} bmapEvent.transition = transition port$ = userEventData.port bmapEvent.port = port$ bmapEvent.functionBlockName = userEventData.functionBlock bmapEvent.functionName = userEventData.function bmapEvent.operatorName = userEventData.operator bmapEvent.fieldName = userEventData.field bmapEvent.bitfieldName = userEventData.bitfield bmapEvent.fieldValue = GetFieldValueAsStr(userEventData.data) bsState.bmapEvents.push(bmapEvent) else if userEventName$ = "wssEvent" then if type(bsState.wssEvents) <> "roAssociativeArray" then bsState.wssEvents = { } end if port$ = userEventData.port wssEvents = bsState.wssEvents if type(wssEvents[port$]) <> "roAssociativeArray" then wssEvents[port$] = { } end if wssEventData = transitionspec.userevent.data wssEventId = wssEventData.wssEventId wssEventName = wssEventData.wssEventName wssEventParameter = wssEventData.wssEventParameter wssPortEvents = wssEvents[port$] if type(wssPortEvents[wssEventId]) <> "roAssociativeArray" then wssPortEvents[wssEventId] = { } end if wssPortEvents[wssEventId].wssEventId = wssEventId wssPortEvents[wssEventId].wssEventName = wssEventName wssPortEvents[wssEventId].wssEventParameter = wssEventParameter if type(wssPortEvents[wssEventId].wssEventTransitionEventSpecs) <> "roAssociativeArray" then wssPortEvents[wssEventId].wssEventTransitionEventSpecs = { } end if wssEventTransitionEventSpecs = wssPortEvents[wssEventId].wssEventTransitionEventSpecs if type(wssEventParameter) = "roAssociativeArray" then propertyName = wssEventParameter.parameterName if not wssEventTransitionEventSpecs.DoesExist(propertyName) then wssEventTransitionEventSpecs.AddReplace(propertyName, { }) end if wssEventTransitionSpecByPropertyName = wssEventTransitionEventSpecs.Lookup(propertyName) keyPropertyValue = wssEventParameter.parameterValue wssEventTransitionSpecByPropertyName.AddReplace(keyPropertyValue, transition) endif if wssEventTransitionEventSpecs.IsEmpty() then wssEventTransitionSpec = { } wssEventTransitionSpec.transition = transition wssEventTransitionSpec.wssEventData = wssEventData wssPortEvents[wssEventId] = wssEventTransitionSpec end if else if userEventName$ = "timeClockEvent" then timeClockEventTransitionSpec = { } timeClockEventTransitionSpec.transition = transition if userEventData.type = "timeClockDateTime" then dateTime$ = userEventData.data.dateTime timeClockEventTransitionSpec.timeClockEventDateTime = FixDateTime(dateTime$) else if userEventData.type = "timeClockDailyOnce" then timeClockEventTransitionSpec.daysOfWeek% = userEventData.data.daysOfWeek timeClockEventTransitionSpec.timeClockDaily% = userEventData.data.time else if userEventData.type = "timeClockDailyPeriodic" then timeClockEventTransitionSpec.timeClockPeriodicInterval% = userEventData.data.interval timeClockEventTransitionSpec.timeClockPeriodicStartTime% = userEventData.data.startTime timeClockEventTransitionSpec.timeClockPeriodicEndTime% = userEventData.data.endTime timeClockEventTransitionSpec.daysOfWeek% = userEventData.data.daysOfWeek else if userEventData.type = "timeClockDateTimeByUserVariable" then userVariableName$ = userEventData.data.userVariableName timeClockEventTransitionSpec.userVariableName$ = userVariableName$ timeClockEventTransitionSpec.userVariable = bsp.GetUserVariable(userVariableName$) end if if type(bsState.timeClockEvents) <> "roArray" then bsState.timeClockEvents = CreateObject("roArray", 1, true) end if bsState.timeClockEvents.push(timeClockEventTransitionSpec) else if userEventName$ = "mediaEnd" then if bsState.type$ = "video" then bsState.videoEndEvent = transition else if bsState.type$ = "liveVideo" then bsState.videoEndEvent = transition else if bsState.type$ = "audio" then bsState.audioEndEvent = transition else if bsState.type$ = "mediaRSS" then bsState.signChannelEndEvent = transition else if bsState.type$ = "mediaList" then bsState.videoEndEvent = transition bsState.audioEndEvent = transition else if bsState.type$ = "playFile" then bsState.videoEndEvent = transition bsState.audioEndEvent = transition else if bsState.type$ = "stream" then bsState.videoEndEvent = transition bsState.audioEndEvent = transition else if bsState.type$ = "mjpeg" then bsState.videoEndEvent = transition else if bsState.type$ = "superState" then bsState.mediaEndEvent = transition end if else if userEventName$ = "mediaListEnd" then bsState.mediaListEndEvent = transition else if userEventName$ = "keyboard" then keyboardChar$ = userEventData.data if len(keyboardChar$) > 1 then keyboardChar$ = Lcase(keyboardChar$) end if if type(bsState.keyboardEvents) <> "roAssociativeArray" then bsState.keyboardEvents = { } end if bsState.keyboardEvents[keyboardChar$] = transition else if userEventName$ = "remote" then remote$ = ucase(userEventData.data) if type(bsState.remoteEvents) <> "roAssociativeArray" then bsState.remoteEvents = { } end if bsState.remoteEvents[remote$] = transition else if userEventName$ = "usb" then usbString$ = userEventData.data if type(bsState.usbStringEvents) <> "roAssociativeArray" then bsState.usbStringEvents = { } end if bsState.usbStringEvents[usbString$] = transition else if userEventName$ = "udp" then if type(bsState.udpEvents) <> "roAssociativeArray" then bsState.udpEvents = { } end if transition.udpLabel$ = userEventData.label transition.udpExport = userEventData.export bsState.udpEvents[userEventData.data] = transition else if userEventName$ = "synchronize" then bsp.IsSyncSlave = true synchronize$ = userEventData.data if type(bsState.synchronizeEvents) <> "roAssociativeArray" then bsState.synchronizeEvents = { } end if bsState.synchronizeEvents[synchronize$] = transition else if userEventName$ = "zoneMessage" then if type(bsState.zoneMessageEvents) <> "roAssociativeArray" then bsState.zoneMessageEvents = { } end if bsState.zoneMessageEvents[userEventData.data] = transition else if userEventName$ = "pluginMessageEvent" then pluginName$ = userEventData.name pluginMessage$ = userEventData.data ' unique key is concatenation of plugin name and plugin message key$ = pluginName$ + pluginMessage$ if type(bsState.pluginMessageEvents) <> "roAssociativeArray" then bsState.pluginMessageEvents = { } end if bsState.pluginMessageEvents[key$] = transition else if userEventName$ = "internalSynchronize" then internalSynchronize$ = userEventData.data if type(bsState.internalSynchronizeEvents) <> "roAssociativeArray" then bsState.internalSynchronizeEvents = { } end if bsState.internalSynchronizeEvents[internalSynchronize$] = transition else if userEventName$ = "rectangularTouchEvent" then if type(bsState.touchEvents) <> "roAssociativeArray" then bsState.touchEvents = { } end if transition.x% = userEventData.x transition.y% = userEventData.y transition.width% = userEventData.width transition.height% = userEventData.height if sign.flipCoordinates then videoMode = CreateObject("roVideoMode") resX = videoMode.GetResX() resY = videoMode.GetResY() videoMode = invalid transition.x% = resX - (transition.x% + transition.width%) transition.y% = resY - (transition.y% + transition.height%) end if bsState.touchEvents[stri(sign.numTouchEvents%)] = transition sign.numTouchEvents% = sign.numTouchEvents% + 1 else if userEventName$ = "audioTimeCodeEvent" then if type(bsState.audioTimeCodeEvents) <> "roAssociativeArray" then bsState.audioTimeCodeEvents = { } end if transition.timeInMS% = userEventData.eventTime bsState.audioTimeCodeEvents[stri(sign.numAudioTimeCodeEvents%)] = transition sign.numAudioTimeCodeEvents% = sign.numAudioTimeCodeEvents% + 1 else if userEventName$ = "videoTimeCodeEvent" then if type(bsState.videoTimeCodeEvents) <> "roAssociativeArray" then bsState.videoTimeCodeEvents = { } end if transition.timeInMS% = userEventData.eventTime bsState.videoTimeCodeEvents[stri(sign.numVideoTimeCodeEvents%)] = transition sign.numVideoTimeCodeEvents% = sign.numVideoTimeCodeEvents% + 1 end if ' get commands and conditional targets transition.conditionalTransitions = [] for each conditionalTransitionSpec in transitionSpec.conditionalTransitions newConditionalTransition(bsp, zoneHSM, conditionalTransitionSpec, transition.conditionalTransitions) next transition.transitionCmds = [] transitionCommands = transitionSpec.commands if transitionCommands.Count() > 0 then for each command in transitionCommands newCmd(bsp, command, transition.transitionCmds) next end if 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(transition.internalSynchronizeEventsMaster) <> "roAssociativeArray" then transition.internalSynchronizeEventsMaster = { } end if internalSynchronizeMasterTransition = { } internalSynchronizeMasterTransition.targetMediaState$ = nextMediaState$ internalSynchronizeMasterTransition.targetMediaStateIsPreviousState = false ' transition.internalSynchronizeEventsMaster[transitionCmd.parameters["synchronizeKeyword"].GetCurrentParameterValue()] = internalSynchronizeMasterTransition transition.internalSynchronizeEventsMaster[transitionCmd.parameters["message"].GetCurrentParameterValue()] = internalSynchronizeMasterTransition ' modify this state's transition to not go to the next media state transition.targetMediaState$ = "" end if next end sub Function newConditionalTransition(bsp as object, zoneHSM as object, conditionalTransitionSpec as object, conditionalTransitions as object) userVariableName$ = conditionalTransitionSpec.variableName operator$ = conditionalTransitionSpec.compareOperator if operator$ = "" then operator$ = "EQ" targetMediaState$ = conditionalTransitionSpec.targetMediaState parameterValueSpec = jsonParseParameterValue(conditionalTransitionSpec.compareValue1) userVariableValue = newParameterValue(bsp, parameterValueSpec) parameterValueSpec2 = jsonParseParameterValue(conditionalTransitionSpec.compareValue2) userVariableValue2 = newParameterValue(bsp, parameterValueSpec2) userVariable = bsp.GetUserVariable(userVariableName$) if type(userVariable) = "roAssociativeArray" then conditionalTransition = { } conditionalTransition.userVariable = userVariable conditionalTransition.operator$ = operator$ conditionalTransition.userVariableValue = userVariableValue conditionalTransition.userVariableValue2 = userVariableValue2 conditionalTransition.targetMediaState$ = targetMediaState$ if conditionalTransition.targetMediaState$ <> "" then targetState = zoneHSM.stateTable[conditionalTransition.targetMediaState$] if targetState.type$ = "superState" and targetState.firstState <> invalid then conditionalTransition.targetMediaState$ = targetState.firstState.id$ end if end if targetAction = GetTargetActionFromEventAction(conditionalTransitionSpec.conditionalAction) conditionalTransition.remainOnCurrentStateActions = targetAction.remainOnCurrentStateActions conditionalTransition.targetMediaStateIsPreviousState = targetAction.targetMediaStateIsPreviousState conditionalTransition.transitionCmds = [] for each command in conditionalTransitionSpec.commands newCmd(bsp, command, conditionalTransition.transitionCmds) next conditionalTransitions.push(conditionalTransition) else bsp.diagnostics.PrintDebug("User variable " + userVariableName$ + " not found.") end if end function Function GetTargetActionFromEventAction(eventAction as string) as object targetAction = { } if eventAction = "StopPlayback" then targetAction.targetMediaStateIsPreviousState = false targetAction.remainOnCurrentStateActions = "stop" else if eventAction = "StopPlaybackAndClearScreen" then targetAction.targetMediaStateIsPreviousState = false targetAction.remainOnCurrentStateActions = "stopclear" else if eventAction = "ReturnToPriorState" then targetAction.targetMediaStateIsPreviousState = true targetAction.remainOnCurrentStateActions = "none" ' if transitionSpec.eventAction = "None" then else targetAction.targetMediaStateIsPreviousState = false targetAction.remainOnCurrentStateActions = "none" end if return targetAction end function Function newTickerItem(bsp as object, zoneHSM as object, stateDescription as object) as object item = invalid if type(stateDescription.rssDataFeedPlaylistItem) = "roAssociativeArray" then item = newRSSDataFeedPlaylistItem(bsp, stateDescription) else if type(stateDescription.twitterItem) = "roAssociativeArray" then item = newTwitterPlaylistItem(bsp, stateDescription) else if type(stateDescription.textItem) = "roAssociativeArray" then item = newTextPlaylistItem(stateDescription) else if type(stateDescription.userVariableInTickerItem) = "roAssociativeArray" item = newUserVariablePlaylistItem(stateDescription, bsp, zoneHSM) end if return item end function Function newState(bsp as object, zoneHSM as object, sign as object, stateDescription as object, superState as object) as object ' get the name stateName$ = stateDescription.name state = zoneHSM.newHState(bsp, stateName$) state.name$ = stateName$ if type(superState) = "roAssociativeArray" then state.superState = superState else state.superState = zoneHSM.stTop end if ' create data structures for arrays of specific events state.gpioEvents = { } state.gpioUpEvents = { } state.bpEvents = CreateObject("roArray", 3, true) state.bpEvents[0] = { } state.bpEvents[1] = { } state.bpEvents[2] = { } state.bpEvents[3] = { } state.serialEvents = { } state.gpsEnterRegionEvents = CreateObject("roArray", 1, true) state.gpsExitRegionEvents = CreateObject("roArray", 1, true) state.bmapHexInputEvents = {} ' get the item item = { } if type(stateDescription.imageItem) = "roAssociativeArray" then newImagePlaylistItem(bsp, stateDescription.imageItem, zoneHSM, state, item) state.imageItem = item state.type$ = "image" zoneHSM.numImageItems% = zoneHSM.numImageItems% + 1 else if type(stateDescription.videoItem) = "roAssociativeArray" then newVideoPlaylistItem(bsp, stateDescription.videoItem, state, item) state.videoItem = item state.type$ = "video" ' TODO - Bacon '' any idea why the following was here? copy / paste error?' '' zoneHSM.numImageItems% = zoneHSM.numImageItems% + 1 else if type(stateDescription.audioItem) = "roAssociativeArray" then newAudioPlaylistItem(bsp, stateDescription.audioItem, state, item) state.audioItem = item state.type$ = "audio" else if type(stateDescription.liveVideoItem) = "roAssociativeArray" then newLiveVideoPlaylistItem(stateDescription.liveVideoItem, state) state.type$ = "liveVideo" else if type(stateDescription.eventHandlerItem) = "roAssociativeArray" then newEventHandlerPlaylistItem(stateDescription.eventHandlerItem, state) state.type$ = "eventHandler" else if type(stateDescription.eventHandler2Item) = "roAssociativeArray" then newEventHandlerPlaylistItem(stateDescription.eventHandler2Item, state) state.type$ = "eventHandler" else if type(stateDescription.templatePlaylistItem) = "roAssociativeArray" then newTemplatePlaylistItem(bsp, stateDescription.templatePlaylistItem, state) state.type$ = "template" else if type(stateDescription.mrssDataFeedPlaylistItem) = "roAssociativeArray" then ' require that the storage is writable if bsp.sysInfo.storageIsWriteProtected then DisplayStorageDeviceLockedMessage() newMRSSPlaylistItem(bsp, zoneHSM, stateDescription.mrssDataFeedPlaylistItem, state) state.type$ = "mediaRSS" if zoneHSM.type$ = "VideoOrImages" or zoneHSM.type$ = "Images" then zoneHSM.numImageItems% = zoneHSM.numImageItems% + 1 end if else if type(stateDescription.localPlaylistItem) = "roAssociativeArray" then newLocalPlaylistItem(bsp, stateDescription.localPlaylistItem, state) state.type$ = "mediaRSS" if zoneHSM.type$ = "VideoOrImages" or zoneHSM.type$ = "Images" then zoneHSM.numImageItems% = zoneHSM.numImageItems% + 1 end if else if type(stateDescription.mediaSuperState) = "roAssociativeArray" then state.HStateEventHandler = MediaItemEventHandler SetMediaItemEventHandlers(state) state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames else if type(stateDescription.backgroundImageItem) = "roAssociativeArray" then newBackgroundImagePlaylistItem(bsp, stateDescription.backgroundImageItem, state, item) state.backgroundImageItem = item else if type(stateDescription.mediaListItem) = "roAssociativeArray" then newMediaListPlaylistItem(bsp, sign, stateDescription.mediaListItem, zoneHSM, state, item) state.type$ = "mediaList" else if type(stateDescription.playFileItem) = "roAssociativeArray" then newPlayFilePlaylistItem(bsp, stateDescription.playFileItem, state) state.type$ = "playFile" if zoneHSM.type$ = "VideoOrImages" or zoneHSM.type$ = "Images" then zoneHSM.numImageItems% = zoneHSM.numImageItems% + 1 end if else if type(stateDescription.videoStreamItem) = "roAssociativeArray" then newStreamPlaylistItem(bsp, stateDescription.videoStreamItem, state) state.mediaType$ = "video" state.type$ = "stream" else if type(stateDescription.audioStreamItem) = "roAssociativeArray" then newStreamPlaylistItem(bsp, stateDescription.audioStreamItem, state) state.mediaType$ = "audio" state.type$ = "stream" else if type(stateDescription.mjpegStreamItem) = "roAssociativeArray" then newMjpegStreamPlaylistItem(bsp, stateDescription.mjpegStreamItem, state) state.type$ = "mjpeg" else if type(stateDescription.html5Item) = "roAssociativeArray" then newHtml5PlaylistItem(bsp, stateDescription.html5Item, state) state.type$ = "html5" else if type(stateDescription.superStateItem) = "roAssociativeArray" then newSuperStateItem(bsp, zoneHSM, sign, stateDescription.superStateItem, state) state.type$ = "superState" end if ' get any media state commands (commands that are executed when a state is entered) state.cmds = CreateObject("roArray", 1, true) ' new style commands cmds = stateDescription.brightSignCmd if type(cmds) = "roArray" and cmds.Count() > 0 then for each cmd in cmds newCmd(bsp, cmd, state.cmds) next end if ' get media state exit commands state.exitCmds = CreateObject("roArray", 1, true) exitCmds = stateDescription.brightSignExitCommands if type(exitCmds) = "roArray" and exitCmds.Count() > 0 then for each cmd in exitCmds newCmd(bsp, cmd, state.exitCmds) next end if zoneHSM.stateTable[state.id$] = state state.processBMapMessageEvent = processBMapMessageEvent state.MatchBMapHexInputEvent = MatchBMapHexInputEvent state.MatchBMapEvent = MatchBMapEvent return state end function Function newTextParameterValue(value$ as string) as object parameterValue = { } parameterValue.GetCurrentParameterValue = GetCurrentParameterValue parameterValue.parameterValueItems = CreateObject("roArray", 1, true) parameterValue.parameterValueItems.push(newParameterValueItemFromTextConstant(value$)) return parameterValue end function Function GetCurrentTextParameterValue() as string return m.textValue$ end function Function newParameterValueItemText(value$ as string) as object parameterValueItem = { } parameterValueItem.GetCurrentValue = GetCurrentTextParameterValue parameterValueItem.type$ = "text" parameterValueItem.textValue$ = value$ return parameterValueItem end function Function newParameterValueItemFromTextConstant(textValue$ as string) as object parameterValueItem = { } parameterValueItem.GetCurrentValue = GetCurrentTextParameterValue parameterValueItem.type$ = "text" parameterValueItem.textValue$ = textValue$ return parameterValueItem end function Function GetCurrentUserVariableParameterValue() as string if type(m.userVariable) = "roAssociativeArray" then return m.userVariable.GetCurrentValue() else return "" end if end function ' BACONTODO - looks like this is unused currently, but it might still be used in ba Function newParameterValueItemMediaCounterVariable(bsp as object, parameterValueItemMediaCounterVariable as object) as object stop parameterValueItem = { } parameterValueItem.GetCurrentValue = GetCurrentUserVariableParameterValue parameterValueItem.type$ = "userVariable" variableName$ = parameterValueItemMediaCounterVariable.fileName.GetText() userVariableName$ = mid(variableName$, 2) parameterValueItem.userVariable = bsp.GetUserVariable(userVariableName$) if type(parameterValueItem.userVariable) <> "roAssociativeArray" then bsp.diagnostics.PrintDebug("Media counter variable " + userVariableName$ + " not found.") bsp.logging.WriteDiagnosticLogEntry(bsp.diagnosticCodes.EVENT_MEDIA_COUNTER_VARIABLE_NOT_FOUND, userVariableName$) end if return parameterValueItem end function Function GetCurrentParameterValue() as string value$ = "" for each parameterValueItem in m.parameterValueItems if type(parameterValueItem) = "roAssociativeArray" then value$ = value$ + parameterValueItem.GetCurrentValue() end if next return value$ end function Function GetVariableName() as string variableName$ = "" if m.parameterValueItems.Count() = 1 then parameterValueItem = m.parameterValueItems[0] if type(parameterValueItem) = "roAssociativeArray" then if parameterValueItem.type$ = "userVariable" then userVariable = parameterValueItem.userVariable variableName$ = userVariable.name$ end if end if end if return variableName$ end function ' cmdDescription is an aa with a single entry - the key is the command name; the value is the command Sub newCmd(bsp as object, cmdDescription as object, cmds as object) usbConnectorNameToUsbSpec = GetGlobalAA().usbConnectorNameToUsbSpec for each cmdName in cmdDescription bsCmd = { } bsCmd.name$ = cmdName bsCmd.parameters = { } cmdParametersArray = cmdDescription[cmdName] for each cmdParameterAA in cmdParametersArray pviSpecs = [] for each cmdParameterName in cmdParameterAA cmdParameterValueItemArray = cmdParameterAA[cmdParameterName] for each cmdParameterValueItem in cmdParameterValueItemArray ' Safe guarding backwards compatibility for 'setAllAudioOutputs' if bsCmd.name$ = "setAllAudioOutputs" and cmdParameterValueItem.type = "text" and cmdParameterValueItem.value = invalid then m.bsp.diagnostics.PrintDebug("### Assigning default value for 'setAllAudioOutputs' command's 'text' parameter 'value'") m.bsp.diagnostics.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_VARIABLE_REFERENCE_FAILURE, cmdParameterValueItem) cmdParameterValueItem.value = "none" ' default text value for 'setAllAudioOutputs' command end if pviSpecs.push(cmdParameterValueItem) next parameterValueSpec = jsonParseParameterValue(pviSpecs) parameterValue = newParameterValue(bsp, parameterValueSpec) bsCmd.parameters.AddReplace(cmdParameterName, parameterValue) next next cmds.push(bsCmd) next end sub Sub UpdateWidgetVisibility(showImage as boolean, hideImage as boolean, clearImage as boolean, showCanvas as boolean, hideCanvas as boolean, showHtml as boolean, hideHtml as boolean) if hideImage then if type(m.imagePlayer) = "roImageWidget" then m.imagePlayer.Hide() end if end if if clearImage then if type(m.imagePlayer) = "roImageWidget" then m.imagePlayer.StopDisplay() end if end if if hideCanvas then if type(m.canvasWidget) = "roCanvasWidget" then m.canvasWidget.Hide() end if end if if hideHtml then if type(m.displayedHtmlWidget) = "roHtmlWidget" then ' m.displayedHtmlWidget.Hide() m.loadingHtmlWidget = invalid m.displayedHtmlWidget = invalid end if end if if showImage then if type(m.imagePlayer) = "roImageWidget" and m.isVisible then m.imagePlayer.Show() end if end if if showCanvas then if type(m.canvasWidget) = "roCanvasWidget" and m.isVisible then m.canvasWidget.Show() end if end if if showHtml then if type(m.displayedHtmlWidget) = "roHtmlWidget" and m.isVisible then m.displayedHtmlWidget.Show() end if end if end sub Sub ShowImageWidget() m.UpdateWidgetVisibility(true, false, false, false, true, false, true) m.imageHidden = false m.canvasHidden = true m.htmlHidden = true end sub Sub ClearImagePlane() m.UpdateWidgetVisibility(false, false, true, false, true, false, true) end sub Sub ShowCanvasWidget() m.UpdateWidgetVisibility(false, true, false, true, false, false, true) m.imageHidden = true m.canvasHidden = false m.htmlHidden = true end sub Sub ShowHtmlWidget() m.UpdateWidgetVisibility(false, true, true, false, true, true, false) m.imageHidden = true m.canvasHidden = true m.htmlHidden = false end sub Sub LogPlayStart(itemType$ as string, fileName$ as string) if m.playbackActive then m.playbackEndTime$ = m.bsp.systemTime.GetLocalDateTime().GetString() m.bsp.logging.WritePlaybackLogEntry(m.name$, m.playbackStartTime$, m.playbackEndTime$, m.playbackItemType$, m.playbackFileName$) end if m.playbackActive = true m.playbackStartTime$ = m.bsp.systemTime.GetLocalDateTime().GetString() m.playbackItemType$ = itemType$ m.playbackFileName$ = fileName$ end sub Sub newMediaPlaylistItem(bsp as object, playlistItemDescription as object, state as object, playlistItemBS as object) playlistItemBS.fileName$ = playlistItemDescription.fileName playlistItemBS.userVariable = bsp.GetUserVariable(playlistItemBS.fileName$) if type(bsp.encryptionByFile) = "roAssociativeArray" then playlistItemBS.isEncrypted = bsp.encryptionByFile.DoesExist(playlistItemBS.fileName$) else playlistItemBS.isEncrypted = false end if SetMediaItemEventHandlers(state) state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames state.LaunchTimer = LaunchTimer state.PreloadItem = PreloadItem end sub Sub newImagePlaylistItem(bsp as object, playlistItemDescription as object, zoneHSM as object, state as object, playlistItemBS as object) newMediaPlaylistItem(bsp, playlistItemDescription, state, playlistItemBS) playlistItemBS.slideTransition% = GetSlideTransitionValue(playlistItemDescription.transitionEffect.transitionType) playlistItemBS.transitionDuration% = playlistItemDescription.transitionEffect.transitionDuration state.HStateEventHandler = STDisplayingImageEventHandler state.DisplayImage = DisplayImage state.PreDrawImage = PreDrawImage state.DrawImage = DrawImage state.PostDrawImage = PostDrawImage state.ClearVideo = ClearVideo state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons end sub Function GetProbeData(assetPoolFiles as object, fileName$ as string) as object probe = invalid poolFileInfo = assetPoolFiles.GetPoolFileInfo(fileName$) if type(poolFileInfo) = "roAssociativeArray" then probe = poolFileInfo.Lookup("probe") end if return probe end function Sub newVideoPlaylistItem(bsp as object, playlistItemDescription as object, state as object, playlistItemBS as object) newMediaPlaylistItem(bsp, playlistItemDescription, state, playlistItemBS) playlistItemBS.automaticallyLoop = playlistItemDescription.automaticallyLoop playlistItemBS.probeData = GetProbeData(bsp.assetPoolFiles, playlistItemBS.fileName$) playlistItemBS.videoDisplayMode% = 0 playlistItemBS.volume% = 100 state.HStateEventHandler = STVideoPlayingEventHandler state.AddVideoTimeCodeEvent = AddVideoTimeCodeEvent state.SetVideoTimeCodeEvents = SetVideoTimeCodeEvents state.LaunchVideo = LaunchVideo state.PrePlayVideo = PrePlayVideo state.PlayVideo = PlayVideo state.PostPlayVideo = PostPlayVideo state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons newMediaPlaylistItem(bsp, playlistItemDescription, state, playlistItemBS) playlistItemBS.automaticallyLoop = playlistItemDescription.automaticallyLoop end sub Sub newSuperStateItem(bsp as object, zoneHSM as object, sign as object, playlistItemDescription as object, state as object) state.initialStateName$ = playlistItemDescription.initialState for each subState in playlistItemDescription.states stateDescription = jsonParseState(subState) newState(bsp, zoneHSM, sign, stateDescription, state) next state.HStateEventHandler = STSuperStateEventHandler SetMediaItemEventHandlers(state) state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames state.LaunchTimer = LaunchTimer state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons end sub Sub newEventHandlerPlaylistItem(playlistItemDescription as object, state as object) state.stopPlayback = playlistItemDescription.stopPlayback state.HStateEventHandler = STEventHandlerEventHandler state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames state.LaunchTimer = LaunchTimer state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons SetMediaItemEventHandlers(state) end sub Sub newHtml5PlaylistItem(bsp as object, playlistItemDescription as object, state as object) state.name$ = playlistItemDescription.name$ state.htmlSiteName$ = playlistItemDescription.htmlSiteName$ ' get the associated html site if bsp.htmlSites.DoesExist(state.htmlSiteName$) then htmlSite = bsp.htmlSites.Lookup(state.htmlSiteName$) state.isNodeServer = htmlSite.isNodeServer state.contentIsLocal = htmlSite.contentIsLocal if state.contentIsLocal then state.prefix$ = htmlSite.prefix$ state.filePath$ = htmlSite.filePath$ state.url = invalid else state.url = htmlSite.url end if state.queryString = htmlSite.queryString else ' what to do here? stop end if state.enableBrightSignJavascriptObjects = playlistItemDescription.enableBrightSignJavascriptObjects state.enableCrossDomainPolicyChecks = playlistItemDescription.enableCrossDomainPolicyChecks state.ignoreHttpsCertificateErrors = playlistItemDescription.ignoreHttpsCertificateErrors if type(playlistItemDescription.enableCamera) = "Boolean" then state.enableCamera = playlistItemDescription.enableCamera else state.enableCamera = false endif state.enableMouseEvents = playlistItemDescription.enableMouseEvents state.displayCursor = playlistItemDescription.displayCursor state.hwzOn = playlistItemDescription.hwzOn state.useUserStylesheet = playlistItemDescription.useUserStylesheet if state.useUserStylesheet then state.userStylesheet = playlistItemDescription.userStylesheet end if state.customFonts = [] customFonts = playlistItemDescription.customFonts for each customFont in customFonts state.customFonts.push(customFont) next state.HStateEventHandler = STHTML5PlayingEventHandler state.LoadHtmlWidgetStaticInitialization = LoadHtmlWidgetStaticInitialization SetMediaItemEventHandlers(state) state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons state.LaunchTimer = LaunchTimer state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames end sub Sub newLiveVideoPlaylistItem(playlistItemDescription as object, state as object) state.volume% = playlistItemDescription.volume state.overscan = playlistItemDescription.overscan state.HStateEventHandler = STLiveVideoPlayingEventHandler SetMediaItemEventHandlers(state) state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames state.LaunchTimer = LaunchTimer end sub Function ParseFileNameXML(fileNameXML as object) as string stop if fileNameXML.Count() = 1 then fileElement = fileNameXML[0] fileAttributes = fileElement.GetAttributes() fileName$ = fileAttributes.Lookup("name") else fileName$ = "" end if return fileName$ end function Function ParseBoolAttribute(elementXML as object, attr$ as string) as boolean stop element = elementXML attributes = element.GetAttributes() val = attributes.Lookup(attr$) if IsString(val) and lcase(val) = "true" then return true end if return false end function Function ParseNavigation(bsp as object, sign as object, navigationXML as object) as object stop navigation = invalid if navigationXML.Count() = 1 then navigationElement = navigationXML[0] userEventsList = navigationElement.userEvent if userEventsList.Count() > 0 then navigation = { } for each userEvent in userEventsList ParseUserEvent(bsp, sign, navigation, userEvent) next end if end if return navigation end function Sub ParseUserEvent(bsp as object, sign as object, aa as object, userEvent as object) stop userEventName$ = userEvent.name.GetText() if userEventName$ = "keyboard" then aa.keyboardChar$ = userEvent.parameters.parameter.GetText() if len(aa.keyboardChar$) > 1 then aa.keyboardChar$ = Lcase(aa.keyboardChar$) end if else if userEventName$ = "zoneMessage" then aa.zoneMessage$ = userEvent.parameters.parameter.GetText() else if userEventName$ = "remote" then aa.remoteEvent$ = ucase(userEvent.parameters.parameter.GetText()) if type(bsp.remote) <> "roIRRemote" then bsp.remote = CreateObject("roIRRemote") bsp.remote.SetPort(bsp.msgPort) end if else if userEventName$ = "serial" then port$ = userEvent.parameters.parameter.GetText() ' convert USB port if necessary if bsp.usbDevicesByConnector.DoesExist(port$) then port$ = bsp.usbDevicesByConnector.Lookup(port$) end if serial$ = userEvent.parameters.parameter2.GetText() if IsUsbPort(port$) then usbHIDPortConfiguration = GetGlobalAA().usbHIDPortConfigurations[port$] protocol$ = usbHIDPortConfiguration.protocol$ end if aa.serialEvent = { } aa.serialEvent.port$ = port$ aa.serialEvent.protocol$ = protocol$ if protocol$ = "Binary" then aa.serialEvent.inputSpec = ConvertToByteArray(serial$) aa.serialEvent.asciiSpec = serial$ else aa.serialEvent.serial$ = serial$ end if bsp.CreateSerial(bsp, aa.serialEvent.port$, false) else if userEventName$ = "bp900AUserEvent" or userEventName$ = "bp900BUserEvent" or userEventName$ = "bp900CUserEvent" or userEventName$ = "bp900DUserEvent" or userEventName$ = "bp200AUserEvent" or userEventName$ = "bp200BUserEvent" or userEventName$ = "bp200CUserEvent" or userEventName$ = "bp200DUserEvent" then aa.bpEvent = { } aa.bpEvent.buttonPanelIndex% = int(val(userEvent.parameters.buttonPanelIndex.GetText())) aa.bpEvent.buttonNumber$ = userEvent.parameters.buttonNumber.GetText() bsp.ConfigureBPInput(aa.bpEvent.buttonPanelIndex%, aa.bpEvent.buttonNumber$) else if userEventName$ = "gpioUserEvent" then if userEvent.parameters.buttonNumber.GetText() <> "" then buttonNumber$ = userEvent.parameters.buttonNumber.GetText() else buttonNumber$ = userEvent.parameters.parameter.GetText() end if aa.gpioEvent = { } aa.gpioEvent.buttonNumber$ = buttonNumber$ bsp.ConfigureGPIOInput(buttonNumber$) end if end sub Sub scaleScreenElement(bsp as object, setDimensions as boolean, item as object, itemDescription as object) if bsp.configuredResX <> bsp.actualResX or bsp.configuredResY <> bsp.actualResY then xOffset = itemDescription.x / bsp.configuredResX x% = xOffset * bsp.actualResX item.x% = x% yOffset = itemDescription.Y / bsp.configuredResY y% = yOffset * bsp.actualResY item.y% = y% if setDimensions then width% = bsp.actualResX / bsp.configuredResX * itemDescription.width item.width% = width% height% = bsp.actualResY / bsp.configuredResY * itemDescription.height item.height% = height% end if else item.x% = itemDescription.x item.y% = itemDescription.y if setDimensions then item.width% = itemDescription.width item.height% = itemDescription.height end if end if end sub Sub newBaseTemplateItem(templateItem as object, templateItemDescription as object) ' scale text item if necessary bsp = GetGlobalAA().bsp scaleScreenElement(bsp, true, templateItem, templateItemDescription) templateItem.layer% = templateItemDescription.layer end sub Sub ParseTemplateWidgets(item as object, itemDescription as object) ' text widget items item.numberOfLines% = itemDescription.textWidget.numberOfLines item.rotation$ = "0" rotation$ = itemDescription.textWidget.rotation if rotation$ = "90" then item.rotation$ = "270" else if rotation$ = "180" then item.rotation$ = "180" else if rotation$ = "270" then item.rotation$ = "90" end if item.alignment$ = lcase(itemDescription.textWidget.alignment) ' widget items item.foregroundTextColor$ = GetHexColor(itemDescription.widget.foregroundTextColor) item.backgroundTextColor$ = GetHexColor(itemDescription.widget.backgroundTextColor) item.font$ = itemDescription.widget.font if item.font$ = "" then item.font$ = "System" item.fontSize% = itemDescription.widget.fontSize item.backgroundColorSpecified = itemDescription.backgroundColorSpecified end sub Sub newTextTemplateItem(templateItem as object, templateItemDescription as object) ' fill in base class members newBaseTemplateItem(templateItem, templateItemDescription) ParseTemplateWidgets(templateItem, templateItemDescription) end sub Sub newConstantTextTemplateItem(bsp as object, state as object, templateItems as object, templateItemDescription as object) templateItem = { } templateItem.type$ = "constantTextTemplateItem" newTextTemplateItem(templateItem, templateItemDescription) templateItem.textString$ = templateItemDescription.text templateItems.push(templateItem) end sub Sub newSystemVariableTemplateItem(bsp as object, state as object, templateItems as object, templateItemDescription as object) templateItem = { } templateItem.type$ = "systemVariableTemplateItem" newTextTemplateItem(templateItem, templateItemDescription) templateItem.systemVariableType$ = templateItemDescription.systemVariable templateItem.videoConnector$ = getVarFromObj(templateItemDescription, "data.videoConnector", "String", "") templateItems.push(templateItem) end sub Sub newUserVariableTemplateItem(bsp as object, templateItems as object, templateItemDescription as object) templateItem = { } templateItem.type$ = "userVariableTemplateItem" newTextTemplateItem(templateItem, templateItemDescription) name$ = templateItemDescription.name templateItem.userVariable = bsp.GetUserVariable(name$) if type(templateItem.userVariable) <> "roAssociativeArray" then bsp.diagnostics.PrintDebug("User variable " + name$ + " not found.") bsp.logging.WriteDiagnosticLogEntry(bsp.diagnosticCodes.EVENT_USER_VARIABLE_NOT_FOUND, name$) end if templateItem.videoConnector$ = getVarFromObj(templateItemDescription, "data.videoConnector", "String", "") templateItems.push(templateItem) end sub Sub newMediaCounterTextTemplateItem(bsp as object, templateItems as object, templateItemDescription as object) templateItem = { } templateItem.type$ = "mediaCounterTemplateItem" newTextTemplateItem(templateItem, templateItemDescription) fileName$ = templateItemDescription.fileName templateItem.userVariable = bsp.GetUserVariable(fileName$) if type(templateItem.userVariable) <> "roAssociativeArray" then bsp.diagnostics.PrintDebug("Media counter variable " + fileName$ + " not found.") bsp.logging.WriteDiagnosticLogEntry(bsp.diagnosticCodes.EVENT_MEDIA_COUNTER_VARIABLE_NOT_FOUND, fileName$) end if templateItems.push(templateItem) end sub Sub newImageTemplateItem(bsp as object, templateItems as object, templateItemDescription as object) templateItem = { } templateItem.type$ = "imageTemplateItem" newBaseTemplateItem(templateItem, templateItemDescription) templateItem.fileName$ = templateItemDescription.fileName templateItems.push(templateItem) end sub Function newLiveTextDataEntryTemplateItem(bsp as object, templateItems as object, templateItemDescription as object) templateItem = { } newTextTemplateItem(templateItem, templateItemDescription) liveDataFeedId$ = CleanName(templateItemDescription.liveDataFeedId) templateItem.liveDataFeed = bsp.liveDataFeeds.Lookup(liveDataFeedId$) return templateItem end function Sub newIndexedLiveTextDataItem(bsp as object, templateItems as object, templateItemDescription as object) templateItem = newLiveTextDataEntryTemplateItem(bsp, templateItems, templateItemDescription) templateItem.type$ = "indexedLiveTextDataItem" parameterValueSpec = jsonParseParameterValue(templateItemDescription.indexSpec) templateItem.index = newParameterValue(bsp, parameterValueSpec) templateItems.push(templateItem) end sub Sub newTitledLiveTextDataItem(bsp as object, templateItems as object, templateItemDescription as object) templateItem = newLiveTextDataEntryTemplateItem(bsp, templateItems, templateItemDescription) templateItem.type$ = "titledLiveTextDataItem" ' liveDataFeedDescription.urlPV = jsonParseParameterValue(liveDataFeedJson.url) parameterValueSpec = jsonParseParameterValue(templateItemDescription.titleSpec) templateItem.title = newParameterValue(bsp, parameterValueSpec) templateItems.push(templateItem) end sub Function newSimpleRssTemplateItem(bsp as object, templateItems as object, templateItemDescription as object) as object templateItem = { } templateItem.type$ = "simpleRssTemplateItem" newTextTemplateItem(templateItem, templateItemDescription) templateItem.id$ = templateItemDescription.id templateItem.elementName$ = templateItemDescription.elementName templateItems.push(templateItem) return templateItem end function Function newMediaRssTextTemplateItem(bsp as object, templateItems as object, templateItemDescription as object) as object templateItem = { } templateItem.type$ = "mrssTextTemplateItem" newTextTemplateItem(templateItem, templateItemDescription) templateItems.push(templateItem) return templateItem end function Function newMediaRssMediaTemplateItem(bsp as object, templateItems as object, templateItemDescription as object) as object templateItem = { } templateItem.type$ = "mrssMediaTemplateItem" newBaseTemplateItem(templateItem, templateItemDescription) templateItems.push(templateItem) return templateItem end function Sub ParseTemplateBackgroundImage(bsp as object, state as object, templateItemDescription as object) backgroundImageFile = templateItemDescription.backgroundImageFile if type(backgroundImageFile) = "roAssociativeArray" then state.backgroundImage$ = backgroundImageFile.fileName state.backgroundImageWidth% = backgroundImageFile.width state.backgroundImageHeight% = backgroundImageFile.height else state.backgroundImage$ = "" end if state.backgroundImageUserVariable = bsp.GetUserVariable(state.backgroundImage$) end sub Sub SetTemplateHandlers(state as object) state.HStateEventHandler = STTemplatePlayingEventHandler SetMediaItemEventHandlers(state) state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames state.LaunchTimer = LaunchTimer state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons state.PreloadItem = PreloadItem state.SetBackgroundImageSizeLocation = SetBackgroundImageSizeLocation state.ScaleBackgroundImageToFit = ScaleBackgroundImageToFit state.SetupTemplateMRSS = SetupTemplateMRSS state.FindMRSSContent = FindMRSSContent state.GetNextMRSSTemplateItem = GetNextMRSSTemplateItem state.GetMRSSTemplateItem = GetMRSSTemplateItem state.ClearTemplateItems = ClearTemplateItems state.RedisplayTemplateItems = RedisplayTemplateItems state.BuildTemplateItems = BuildTemplateItems state.BuildTemplateItem = BuildTemplateItem state.BuildTextTemplateItem = BuildTextTemplateItem state.TemplateUsesAnyUserVariable = TemplateUsesAnyUserVariable state.TemplateUsesUserVariable = TemplateUsesUserVariable state.TemplateUsesSystemVariable = TemplateUsesSystemVariable state.PlayVideo = PlayVideo state.SetVideoTimeCodeEvents = SetVideoTimeCodeEvents state.LaunchWaitForContentTimer = LaunchWaitForContentTimer end sub Sub newTemplatePlaylistItem(bsp as object, templateItemDescription as object, state as object) ParseTemplateBackgroundImage(bsp, state, templateItemDescription) if templateItemDescription.mediaRssLiveDataFeedIds.Count() > 0 then state.mrssActive = true state.mrssLiveDataFeeds = [] for each mediaRssLiveDataFeedId in templateItemDescription.mediaRssLiveDataFeedIds liveDataFeedId$ = CleanName(mediaRssLiveDataFeedId) liveDataFeed = bsp.liveDataFeeds.Lookup(liveDataFeedId$) if type(liveDataFeed) = "roAssociativeArray" then state.mrssLiveDataFeeds.push(liveDataFeed) end if next else state.mrssActive = false end if ' retrieve template items state.templateItems = CreateObject("roArray", 1, true) for each constantTextTemplateItem in templateItemDescription.constantTextTemplateItems newConstantTextTemplateItem(bsp, state, state.templateItems, constantTextTemplateItem) next for each systemVariableTemplateItem in templateItemDescription.systemVariableTemplateItems newSystemVariableTemplateItem(bsp, state, state.templateItems, systemVariableTemplateItem) next for each userVariableTemplateItem in templateItemDescription.userVariableTemplateItems newUserVariableTemplateItem(bsp, state.templateItems, userVariableTemplateItem) next for each mediaCounterLiveTextItem in templateItemDescription.mediaCounterLiveTextItems newMediaCounterTextTemplateItem(bsp, state.templateItems, mediaCounterLiveTextItem) next for each imageTemplateItem in templateItemDescription.imageTemplateItems newImageTemplateItem(bsp, state.templateItems, imageTemplateItem) next for each titledLiveTextDataItem in templateItemDescription.titledLiveTextDataItems newTitledLiveTextDataItem(bsp, state.templateItems, titledLiveTextDataItem) next for each indexedLiveTextDataItem in templateItemDescription.indexedLiveTextDataItems newIndexedLiveTextDataItem(bsp, state.templateItems, indexedLiveTextDataItem) next for each mediaRssTextTemplateItem in templateItemDescription.mediaRssTextTemplateItems templateItem = newMediaRssTextTemplateItem(bsp, state.templateItems, mediaRssTextTemplateItem) elementName$ = mediaRssTextTemplateItem.elementName if lcase(elementName$) = "title" then state.mrssTitleTemplateItem = templateItem else if lcase(elementName$) = "description" then state.mrssDescriptionTemplateItem = templateItem else ' custom field if type(state.mrssCustomFieldTemplateItems) <> "roAssociativeArray" then state.mrssCustomFieldTemplateItems = { } end if state.mrssCustomFieldTemplateItems.AddReplace(elementName$, templateItem) end if next for each mediaRssCustomFieldTemplateItem in templateItemDescription.mediaRssCustomFieldTemplateItems templateItem = newMediaRssTextTemplateItem(bsp, state.templateItems, mediaRssCustomFieldTemplateItem) elementName$ = mediaRssCustomFieldTemplateItem.elementName if type(state.mrssCustomFieldTemplateItems) <> "roAssociativeArray" then state.mrssCustomFieldTemplateItems = { } end if state.mrssCustomFieldTemplateItems.AddReplace(elementName$, templateItem) next if (not templateItemDescription.mediaRssMediaTemplateItem.IsEmpty()) then state.mrssMediaTemplateItem = newMediaRssMediaTemplateItem(bsp, state.templateItems, templateItemDescription.mediaRssMediaTemplateItem) end if for each simpleRssTemplateItem in templateItemDescription.simpleRssTemplateItems templateItem = newSimpleRssTemplateItem(bsp, state.templateItems, simpleRssTemplateItem) if type(state.simpleRSSTemplateItems) <> "roAssociativeArray" then state.simpleRSSTemplateItems = { } end if simpleRSS = state.simpleRSSTemplateItems.Lookup(templateItem.id$) if type(simpleRSS) <> "roAssociativeArray" then simpleRSS = { } simpleRSS.currentIndex% = 0 simpleRSS.displayTime% = simpleRssTemplateItem.displayTime simpleRSS.rssLiveDataFeeds = [] rssLiveDataFeedIds = simpleRssTemplateItem.rssLiveDataFeedIds if rssLiveDataFeedIds.Count() > 0 then for each rssLiveDataFeedId in rssLiveDataFeedIds liveDataFeedId$ = CleanName(rssLiveDataFeedId) liveDataFeed = bsp.liveDataFeeds.Lookup(liveDataFeedId$) if type(liveDataFeed) = "roAssociativeArray" then simpleRSS.rssLiveDataFeeds.push(liveDataFeed) end if next end if simpleRSS.currentLiveDataFeedIndex% = 0 simpleRSS.items = [] state.simpleRSSTemplateItems.AddReplace(templateItem.id$, simpleRSS) end if simpleRSS.items.push(templateItem) next SetTemplateHandlers(state) end sub Sub newBaseTemplateItemFromTextItem(templateItem as object, textItem as object) templateItem.x% = textItem.x templateItem.y% = textItem.y templateItem.width% = textItem.width templateItem.height% = textItem.height templateItem.layer% = 1 end sub Sub newTextTemplateItemFromTextItem(templateItem as object, textItem as object) ' fill in base class members newBaseTemplateItemFromTextItem(templateItem, textItem) ' text widget items templateItem.numberOfLines% = textItem.numberOfLines% templateItem.rotation$ = textItem.rotation$ templateItem.alignment$ = textItem.alignment$ ' widget items templateItem.foregroundTextColor$ = textItem.foregroundTextColor$ templateItem.backgroundTextColor$ = textItem.backgroundTextColor$ templateItem.font$ = textItem.font$ templateItem.fontSize% = textItem.fontSize% templateItem.backgroundColorSpecified = textItem.backgroundColorSpecified end sub Sub newMRSSPlaylistItem(bsp as object, zoneHSM as object, playlistItemDescription as object, state as object) state.slideTransition% = playlistItemDescription.slideTransition% liveDataFeedId$ = playlistItemDescription.liveDataFeedId if liveDataFeedId$ <> "" then state.liveDataFeed = bsp.liveDataFeeds.Lookup(liveDataFeedId$) end if SetMRSSHandlers(state) end sub Sub newLocalPlaylistItem(bsp as object, playlistItemXML as object, state as object) stop state.slideTransition% = 0 attrs = playlistItemXML.defaultPlaylist.mediaRSSSpec.GetAttributes() if attrs = invalid then ' new format liveDataFeedId$ = playlistItemXML.defaultPlaylistDataFeedId.GetText() devicePlaylists = playlistItemXML.devicePlaylist for each devicePlaylist in devicePlaylists deviceName$ = devicePlaylist.deviceName.GetText() deviceLiveDataFeedId$ = devicePlaylist.dynamicPlaylistDataFeedId.GetText() if deviceName$ = bsp.sysInfo.deviceUniqueID$ then liveDataFeedId$ = CleanName(deviceLiveDataFeedId$) exit for end if next if liveDataFeedId$ <> "" then state.liveDataFeed = bsp.liveDataFeeds.Lookup(liveDataFeedId$) end if else url$ = attrs["url"] name$ = attrs["name"] devicePlaylists = playlistItemXML.devicePlaylist for each devicePlaylist in devicePlaylists deviceName$ = devicePlaylist.deviceName.GetText() attrs = devicePlaylist.dynamicPlaylist.mediaRSSSpec.GetAttributes() dynamicPlaylistUrl$ = attrs["url"] if deviceName$ = bsp.sysInfo.deviceUniqueID$ then url$ = dynamicPlaylistUrl$ name$ = attrs["name"] exit for end if next liveDataFeedId$ = CleanName(name$) liveDataFeed = bsp.liveDataFeeds.Lookup(liveDataFeedId$) if type(liveDataFeed) <> "roAssociativeArray" then liveDataFeed = newLiveDataFeedFromMRSSFormat(bsp, liveDataFeedId$, url$, true) stop bsp.liveDataFeeds.AddReplace(liveDataFeed.id$, liveDataFeed) end if state.liveDataFeed = liveDataFeed end if SetMRSSHandlers(state) end sub Sub SetMRSSHandlers(state as object) state.HStateEventHandler = STPlayingMediaRSSEventHandler SetMediaItemEventHandlers(state) state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames state.LaunchTimer = LaunchTimer state.PreloadItem = PreloadItem state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons state.LaunchWaitForContentTimer = LaunchWaitForContentTimer state.ProtectMRSSFeed = ProtectMRSSFeed state.ProtectMRSSItem = ProtectMRSSItem state.DisplayMRSSItem = DisplayMRSSItem state.AdvanceToNextMRSSItem = AdvanceToNextMRSSItem state.AtEndOfFeed = AtEndOfFeed state.GetHtmlWidgetFilePath = GetHtmlWidgetFilePath state.DisplayImage = DisplayImage state.PreDrawImage = PreDrawImage state.DrawImage = DrawImage state.PostDrawImage = PostDrawImage state.ClearVideo = ClearVideo state.LaunchVideo = LaunchVideo state.PrePlayVideo = PrePlayVideo state.PlayVideo = PlayVideo state.PostPlayVideo = PostPlayVideo state.SetVideoTimeCodeEvents = SetVideoTimeCodeEvents state.GetAnyMediaRSSTransition = GetAnyMediaRSSTransition end sub Function GetBoolFromXML(xmlValue$ as string) as boolean value$ = lcase(xmlValue$) if value$ = "true" then return true else return false end if end function Sub newPlayFilePlaylistItem(bsp as object, playlistItemDescription as object, state as object) state.filesTable = { } ' BACONTODO' '' state.slideTransition% = playlistItemDescription.slideTransition% state.slideTransition% = 0 state.payload$ = "" state.specifyLocalFiles = not playlistItemDescription.useDataFeed state.useDefaultMedia = playlistItemDescription.useDefaultMedia if state.useDefaultMedia then state.defaultMediaFileName$ = playlistItemDescription.defaultMediaContentItem.fileName state.defaultMediaFileType$ = playlistItemDescription.defaultMediaContentItem.type end if state.useUserVariable = playlistItemDescription.useUserVariable if state.useUserVariable then state.userVariable = bsp.GetUserVariable(playlistItemDescription.userVariableName) if type(state.userVariable) <> "roAssociativeArray" then state.useUserVariable = false end if end if liveDataFeedId$ = playlistItemDescription.dataFeedId if liveDataFeedId$ <> "" then state.liveDataFeed = bsp.liveDataFeeds.Lookup(CleanName(liveDataFeedId$)) else state.liveDataFeed = invalid end if if state.specifyLocalFiles then playFileItems = playlistItemDescription.contentItems for each playFileItem in playFileItems fileTableEntry = { } fileTableEntry.label$ = playFileItem.label fileTableEntry.export = playFileItem.export contentItem = playFileItem.contentItem fileTableEntry.fileName$ = contentItem.fileName fileTableEntry.fileType$ = contentItem.type if fileTableEntry.fileType$ = "video" or fileTableEntry.fileType$ = "audio" then fileTableEntry.probeData = GetProbeData(bsp.assetPoolFiles, fileTableEntry.fileName$) end if fileTableEntry.userVariable = bsp.GetUserVariable(contentItem.fileName) fileTableEntry.automaticallyLoop = true fileTableEntry.isEncrypted = false fileTableEntry.videoDisplayMode% = 0 state.filesTable.AddReplace(playFileItem.key, fileTableEntry) next end if state.HStateEventHandler = STPlayFileEventHandler state.PopulatePlayFileFromLiveDataFeed = PopulatePlayFileFromLiveDataFeed SetMediaItemEventHandlers(state) state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames state.LaunchTimer = LaunchTimer state.DisplayImage = DisplayImage state.PreDrawImage = PreDrawImage state.DrawImage = DrawImage state.PostDrawImage = PostDrawImage state.ClearVideo = ClearVideo state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons state.LaunchVideo = LaunchVideo state.PrePlayVideo = PrePlayVideo state.PlayVideo = PlayVideo state.PostPlayVideo = PostPlayVideo state.SetVideoTimeCodeEvents = SetVideoTimeCodeEvents state.LaunchAudio = LaunchAudio state.PrePlayAudio = PrePlayAudio state.PlayAudio = PlayAudio state.PostPlayAudio = PostPlayAudio state.SetAudioTimeCodeEvents = SetAudioTimeCodeEvents state.PreloadItem = PreloadItem end sub Sub newStreamPlaylistItem(bsp as object, playlistItemDescription as object, state as object) state.url = newParameterValue(bsp, playlistItemDescription.url) state.HStateEventHandler = STStreamPlayingEventHandler SetMediaItemEventHandlers(state) state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons state.LaunchTimer = LaunchTimer state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames end sub Sub newMjpegStreamPlaylistItem(bsp as object, playlistItemDescription as object, state as object) state.url = newParameterValue(bsp, playlistItemDescription.url) state.rotation% = int(val(playlistItemDescription.rotation)) state.HStateEventHandler = STMjpegPlayingEventHandler SetMediaItemEventHandlers(state) state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons state.LaunchTimer = LaunchTimer state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames end sub Sub newAudioPlaylistItem(bsp as object, playlistItemDescription as object, state as object, playlistItemBS as object) newMediaPlaylistItem(bsp, playlistItemDescription, state, playlistItemBS) playlistItemBS.probeData = GetProbeData(bsp.assetPoolFiles, playlistItemBS.fileName$) playlistItemBS.volume% = playlistItemDescription.volume state.HStateEventHandler = STAudioPlayingEventHandler state.AddAudioTimeCodeEvent = AddAudioTimeCodeEvent state.SetAudioTimeCodeEvents = SetAudioTimeCodeEvents state.LaunchAudio = LaunchAudio state.PrePlayAudio = PrePlayAudio state.PlayAudio = PlayAudio state.PostPlayAudio = PostPlayAudio state.LaunchMixerAudio = LaunchMixerAudio state.PlayMixerAudio = PlayMixerAudio state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons end sub Function newUserVariablePlaylistItem(playlistItemDescription as object, bsp as object, zoneHSM as object) as object item = { } item.textStrings = [] item.userVariableName = playlistItemDescription.userVariableInTickerItem.userVariableName userVariables = bsp.currentUserVariables for each userVariableKey in userVariables userVariable = userVariables.Lookup(userVariableKey) if userVariable.name$ = item.userVariableName then tickerItem = userVariable.GetCurrentValue() item.textStrings.push(tickerItem) end if next item.isRSSFeed = false item.isUserVariable = true return item end function Function newTextPlaylistItem(playlistItemDescription as object) as object item = { } ' TODO - may need to do some adjustment to make this work for XML strings = playlistItemDescription.textItem.strings item.textStrings = CreateObject("roArray", strings.count(), true) for each textString in strings item.textStrings.push(textString.textString) next item.isRSSFeed = false item.isUserVariable = false return item end function Function newTwitterPlaylistItem(bsp as object, playlistItemDescription as object) as object item = { } twitterItem = playlistItemDescription.twitterItem twitterUserName$ = twitterItem.userName jsonUrl$ = "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" + twitterUserName$ + "&tweet_mode=extended" url = newTextParameterValue(jsonUrl$) authData = { } authData.AuthType = "OAuth 1.0a" authData.AuthToken = twitterItem.authToken authData.ConsumerKey = twitterItem.BSConsumerKey authData.EncryptedTwitterSecrets = twitterItem.encryptedTwitterSecrets updateInterval% = twitterItem.updateInterval liveDataFeed = newLiveDataFeedWithAuthData(bsp, url, authData, updateInterval%) if not bsp.liveDataFeeds.DoesExist(liveDataFeed.id$) then liveDataFeed.isJSON = true liveDataFeed.isTwitterFeed = true if twitterItem.restrictNumberOfTweets = "ByCount" then liveDataFeed.restrictNumberOfItems = true liveDataFeed.numberOfItemsToDisplay% = twitterItem.numberOfTweetsToShow else if twitterItem.restrictNumberOfTweets = "ByRecentDays" then ' TODO - implement restriction by number of days liveDataFeed.restrictNumberOfItems = false liveDataFeed.numberOfRecentDaysToDisplay% = twitterItem.numberOfRecentDaysForTweets else liveDataFeed.restrictNumberOfItems = false end if stop bsp.liveDataFeeds.AddReplace(liveDataFeed.id$, liveDataFeed) else liveDataFeed = bsp.liveDataFeeds.Lookup(liveDataFeed.id$) end if item.liveDataFeed = liveDataFeed item.rssTitle$ = item.liveDataFeed.id$ item.twitterUserName$ = twitterUserName$ + ": " item.isRSSFeed = true item.isUserVariable = false return item end function Function newRSSDataFeedPlaylistItem(bsp as object, stateDescription as object) as object item = { } liveDataFeedId$ = stateDescription.rssDataFeedPlaylistItem.liveDataFeedId$ item.liveDataFeed = bsp.liveDataFeeds.Lookup(liveDataFeedId$) item.rssTitle$ = item.liveDataFeed.id$ item.isRSSFeed = true item.isUserVariable = false return item end function Sub newBackgroundImagePlaylistItem(bsp as object, playlistItemDescription as object, state as object, playlistItemBS as object) newMediaPlaylistItem(bsp, playlistItemDescription, state, playlistItemBS) state.HStateEventHandler = STDisplayingBackgroundImageEventHandler 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 end if return viewMode% 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 end if 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 top left" then slideTransition% = 6 else if slideTransitionSpec$ = "Explode top right" then slideTransition% = 7 else if slideTransitionSpec$ = "Explode bottom left" then slideTransition% = 8 else if slideTransitionSpec$ = "Explode 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 end if return slideTransition% end function 'endregion 'region BSP Methods ' ************************************************* ' ' BSP Methods ' ' ************************************************* 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() ' If graphics are scaled, use video resolution to set the coordinates ' so that no need to scale in AddRectangleRegion useVideoResolution = ShouldScaleGraphicElements(videoMode) if useVideoResolution then resX = videoMode.GetVideoResX() resY = videoMode.GetVideoResY() else resX = videoMode.GetResX() resY = videoMode.GetResY() end if videoMode = invalid m.touchScreen.SetResolution(resX, resY) m.touchScreen.SetCursorPosition(resX / 2, resY / 2) end if if type(zone.enabledRegions) <> "roList" then zone.enabledRegions = CreateObject("roList") end if 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) end sub Sub SetTouchRegions(state as object) zone = state.stateMachine 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" then return end if ' clear out all regions in the active zone if type(zone.enabledRegions) = "roList" then for each eventNum in zone.enabledRegions m.touchScreen.EnableRegion(eventNum, false) next zone.enabledRegions.Clear() end if 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 end if if state.type$ = "html5" and state.displayCursor then m.touchScreen.EnableCursor(true) m.diagnostics.PrintDebug("Html5 state - Cursor enabled") else 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") end if else if m.sign.touchCursorDisplayMode$ = "display" and m.sign.numTouchEvents% > 0 then m.touchScreen.EnableCursor(true) m.diagnostics.PrintDebug("Cursor enabled") else m.touchScreen.EnableCursor(false) m.diagnostics.PrintDebug("Cursor disabled") end if return end sub Sub SetAudioVolumeLimits(zone as object, audioSettings as object) audioSettings.minVolume% = zone.minimumVolume% audioSettings.maxVolume% = zone.maximumVolume% end sub Sub SetAudioMode(parameters as object) parameter = parameters["zoneId"] zoneId$ = parameter.GetCurrentParameterValue() parameter = parameters["mode"] mode$ = parameter.GetCurrentParameterValue() zone = m.GetZone(zoneId$) if type(zone) = "roAssociativeArray" then if lcase(mode$) = "passthrough" then mode% = 0 else if lcase(mode$) = "left" then mode% = 3 else if lcase(mode$) = "right" then mode% = 4 else mode% = 1 end if if type(zone.videoPlayer) = "roVideoPlayer" then zone.videoPlayer.SetAudioMode(mode%) end if if IsAudioPlayer(zone.audioPlayer) then zone.audioPlayer.SetAudioMode(mode%) end if end if end sub Function GetUsbAudioType(parameters as object, usbConnectorName$ as string) as string if parameters.DoesExist(usbConnectorName$) then parameter = parameters[usbConnectorName$] return parameter.GetCurrentParameterValue() else return "none" end if end function Function GetAudioOutputConnector(connectorSpec$ as string) as string connector$ = connectorSpec$ if connectorSpec$ = "HDMI" then di = CreateObject("roDeviceInfo") if di.GetModel() = "AU335" then connector$ = "earc" endif endif return connector$ end function Sub ExecuteSetAllAudioOutputsCommand(parameters as object) parameter = parameters["zoneId"] zoneId$ = parameter.GetCurrentParameterValue() parameter = parameters["analog1"] analog$ = parameter.GetCurrentParameterValue() parameter = parameters["hdmi"] hdmi$ = parameter.GetCurrentParameterValue() parameter = parameters["hdmi1"] hdmi1$ = "" if type(parameter) = "roAssociativeArray" and IsString(parameter.GetCurrentParameterValue()) then hdmi1$ = parameter.GetCurrentParameterValue() end if parameter = parameters["hdmi2"] hdmi2$ = "" if type(parameter) = "roAssociativeArray" and IsString(parameter.GetCurrentParameterValue()) then hdmi2$ = parameter.GetCurrentParameterValue() end if parameter = parameters["hdmi3"] hdmi3$ = "" if type(parameter) = "roAssociativeArray" and IsString(parameter.GetCurrentParameterValue()) then hdmi3$ = parameter.GetCurrentParameterValue() end if parameter = parameters["hdmi4"] hdmi4$ = "" if type(parameter) = "roAssociativeArray" and IsString(parameter.GetCurrentParameterValue()) then hdmi4$ = parameter.GetCurrentParameterValue() end if parameter = parameters["spdif"] spdif$ = parameter.GetCurrentParameterValue() hasMultiScreenOutputsBool = HasMultiScreenOutputs(m.Sign) usbTypeA$ = GetUsbAudioType(parameters, "usbTypeA") usbTypeC$ = GetUsbAudioType(parameters, "usbTypeC") usb700_1$ = GetUsbAudioType(parameters, "usb700_1") usb700_2$ = GetUsbAudioType(parameters, "usb700_2") usb700_3$ = GetUsbAudioType(parameters, "usb700_3") usb700_4$ = GetUsbAudioType(parameters, "usb700_4") usb700_5$ = GetUsbAudioType(parameters, "usb700_5") usb700_6$ = GetUsbAudioType(parameters, "usb700_6") usb700_7$ = GetUsbAudioType(parameters, "usb700_7") usb_1$ = GetUsbAudioType(parameters, "usb_1") usb_2$ = GetUsbAudioType(parameters, "usb_2") usb_3$ = GetUsbAudioType(parameters, "usb_3") usb_4$ = GetUsbAudioType(parameters, "usb_4") usb_5$ = GetUsbAudioType(parameters, "usb_5") usb_6$ = GetUsbAudioType(parameters, "usb_6") pcm = CreateObject("roArray", 1, true) compressed = CreateObject("roArray", 1, true) multichannel = CreateObject("roArray", 1, true) analogAudioOutput = CreateObject("roAudioOutput", "Analog:1") if hasMultiScreenOutputsBool = true then hdmi1AudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI:1")) hdmi2AudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI:2")) hdmi3AudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI:3")) hdmi4AudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI:4")) else hdmiAudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI")) end if spdifAudioOutput = CreateObject("roAudioOutput", "SPDIF") if lcase(analog$) <> "none" and lcase(analog$) <> "multichannel" then pcm.push(analogAudioOutput) end if if lcase(analog$) = "multichannel" then multichannel.push(analogAudioOutput) end if if hasMultiScreenOutputsBool = true then AddAudioOutputByType(hdmi1$, hdmi1AudioOutput, compressed, pcm) AddAudioOutputByType(hdmi2$, hdmi2AudioOutput, compressed, pcm) AddAudioOutputByType(hdmi3$, hdmi3AudioOutput, compressed, pcm) AddAudioOutputByType(hdmi4$, hdmi4AudioOutput, compressed, pcm) else AddAudioOutputByType(hdmi$, hdmiAudioOutput, compressed, pcm) end if AddAudioOutputByType(spdif$, spdifAudioOutput, compressed, pcm) m.SetUSBAudioOutput("usbTypeC", usbTypeC$, pcm, multichannel) m.SetUSBAudioOutput("usbTypeA", usbTypeA$, pcm, multichannel) m.SetUSBAudioOutput("usb700_1", usb700_1$, pcm, multichannel) m.SetUSBAudioOutput("usb700_2", usb700_2$, pcm, multichannel) m.SetUSBAudioOutput("usb700_3", usb700_3$, pcm, multichannel) m.SetUSBAudioOutput("usb700_4", usb700_4$, pcm, multichannel) m.SetUSBAudioOutput("usb700_5", usb700_5$, pcm, multichannel) m.SetUSBAudioOutput("usb700_6", usb700_6$, pcm, multichannel) m.SetUSBAudioOutput("usb700_7", usb700_7$, pcm, multichannel) m.SetUSBAudioOutput("usb_1", usb_1$, pcm, multichannel) m.SetUSBAudioOutput("usb_2", usb_2$, pcm, multichannel) m.SetUSBAudioOutput("usb_3", usb_3$, pcm, multichannel) m.SetUSBAudioOutput("usb_4", usb_4$, pcm, multichannel) m.SetUSBAudioOutput("usb_5", usb_5$, pcm, multichannel) m.SetUSBAudioOutput("usb_6", usb_6$, pcm, multichannel) if pcm.Count() = 0 then noPCMAudioOutput = CreateObject("roAudioOutput", "none") pcm.push(noPCMAudioOutput) end if if compressed.Count() = 0 then noCompressedAudioOutput = CreateObject("roAudioOutput", "none") compressed.push(noCompressedAudioOutput) end if if multichannel.Count() = 0 then noMultichannelAudioOutput = CreateObject("roAudioOutput", "none") multichannel.push(noMultichannelAudioOutput) end if zone = m.GetZone(zoneId$) if type(zone) = "roAssociativeArray" then if type(zone.videoPlayer) = "roVideoPlayer" then zone.videoPlayer.SetPcmAudioOutputs(pcm) zone.videoPlayer.SetCompressedAudioOutputs(compressed) zone.videoPlayer.SetMultichannelAudioOutputs(multichannel) end if if IsAudioPlayer(zone.audioPlayer) then zone.audioPlayer.SetPcmAudioOutputs(pcm) zone.audioPlayer.SetCompressedAudioOutputs(compressed) zone.audioPlayer.SetMultichannelAudioOutputs(multichannel) end if end if end sub Sub SetUSBAudioOutput(connectorName$ as string, audioType$ as string, pcm as object, multichannel as object) connectorName$ = m.GetRuntimeUsbConnector(connectorName$) if IsUsbPort(connectorName$) then usbSpec = GetGlobalAA().usbConnectorNameToUsbSpec.Lookup(connectorName$) if usbSpec.audioOutputSpec <> "" then usbAudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector(usbSpec.audioOutputSpec)) if type(usbAudioOutput) = "roAudioOutput" then if lcase(audioType$) = "pcm" then pcm.push(usbAudioOutput) else if lcase(audioType$) = "multichannel" then multichannel.push(usbAudioOutput) end if end if endif end if end sub Sub UnmuteAudioConnector(connector$ as string) audioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector(connector$)) if type(audioOutput) = "roAudioOutput" then audioOutput.SetMute(false) end if end sub Function DeviceMatchesFid(boseProductPort as string, portName as string, fid as string, fidSubstring as string) as boolean if boseProductPort = portName and instr(1, fid, fidSubstring) = 1 then return true end if return false end function Function GetUsbPort(usbConnectorName as string, usbInternalHub as string, fid as string, category as string, usbTypeATargetFid as string) as boolean ' externalHub = GetGlobalAA().externalHub externalHub = "" ' TEDTODO - is the first one true if the usb700 is plugged into a USBTypeA on an XT? (USB B)? ' TEDTODO - look more carefully at the trailing / in the brightSignUsbPort usb700Port = mid(usbConnectorName, len("usb700") + 2) if instr(1, usbConnectorName, "usb700") = 1 then targetFid = "A/" + usb700Port targetFidWithExternalHub = "A" + externalHub + "/" + usb700Port brightSignUsbPort = "A" brightSignHub = "/" + usb700Port else if lcase(usbConnectorName) = "usbtypec" then targetFid = "A" targetFidWithExternalHub = "A" + externalHub brightSignUsbPort = "A" brightSignHub = "" else if lcase(usbConnectorName) = "usbtypea" then targetFid = usbTypeATargetFid targetFidWithExternalHub = usbTypeATargetFid + externalHub brightSignUsbPort = usbTypeATargetFid brightSignHub = "" else if lcase(usbConnectorName) = "usb_1" then targetFid = "A" targetFidWithExternalHub = "A" + externalHub brightSignUsbPort = "A" brightSignHub = "" else if lcase(usbConnectorName) = "usb_2" then targetFid = "B" targetFidWithExternalHub = "B" + externalHub brightSignUsbPort = "B" brightSignHub = "" else if lcase(usbConnectorName) = "usb_3" then targetFid = "C" targetFidWithExternalHub = "C" + externalHub brightSignUsbPort = "C" brightSignHub = "" else if lcase(usbConnectorName) = "usb_4" then targetFid = "D" targetFidWithExternalHub = "D" + externalHub brightSignUsbPort = "D" brightSignHub = "" else if lcase(usbConnectorName) = "usb_5" then targetFid = "E" targetFidWithExternalHub = "E" + externalHub brightSignUsbPort = "E" brightSignHub = "" else if lcase(usbConnectorName) = "usb_6" then targetFid = "F" targetFidWithExternalHub = "F" + externalHub brightSignUsbPort = "F" brightSignHub = "" else return false end if targetFidWithoutExtension = targetFidWithExternalHub + usbInternalHub if fid = (targetFidWithoutExtension + ".0") then spec = "USB:" + targetFidWithoutExtension + ".0" else if fid = (targetFidWithoutExtension + ".2") then spec = "USB:" + targetFidWithoutExtension + ".2" else return false endif gaa = GetGlobalAA() if gaa.usbConnectorNameToUsbSpec.DoesExist(usbConnectorName) then usbSpec = gaa.usbConnectorNameToUsbSpec.Lookup(usbConnectorName) else usbSpec = {} usbSpec.brightSignUsbPort = brightSignUsbPort usbSpec.brightSignHub = brightSignHub usbSpec.internalHub = usbInternalHub usbSpec.externalHub = "" ' TEDTODO externalHub usbSpec.audioOutputSpec = "" usbSpec.hidOutputSpec = "" gaa.usbConnectorNameToUsbSpec.AddReplace(UsbConnectorName, usbSpec) endif if category = "HID" or category = "NET" then usbSpec.hidOutputSpec = spec else usbSpec.audioOutputSpec = spec endif return true end function Sub GetConnectedUSBDeviceName(bsp as object, model as string, connectedUSBDevices as object, usbConnectorName as string, usbInternalHub as string) if lcase(model) = "au325" or lcase(model) = "hd1024" or lcase(model) = "xt1143" or lcase(model) = "xd1033" or lcase(model) = "xt1144" or lcase(model) = "xd1034" or lcase(model) = "ls423" or lcase(model) = "ls424" or lcase(model) = "ls425" or lcase(model) = "ls445" or lcase(model) = "hs124" or lcase(model) = "hs123" or lcase(model) = "hd225" or lcase(model) = "hd1025" then usbTypeATargetFid = "B" if lcase(model) = "hd1025" or lcase(model) = "hd1024" or lcase(model) = "hs124" or lcase(model) = "hs123" then usbTypeATargetFid = "A" endif for each connectedUSBDevice in connectedUSBDevices fid = connectedUSBDevice.fid category = connectedUSBDevice.category if fid <> "" and (ucase(category) = "HID" or ucase(category) = "AUDIO" or ucase(category) = "NET") then usbDeviceFound = GetUsbPort(usbConnectorName, usbInternalHub, fid, ucase(category), usbTypeATargetFid) if not usbDeviceFound then ' check for case where a Bose product is specified for Type C connector but device is instead plugged into USB 700-1 or vice versa if lcase(usbConnectorName) = "usb700_1" then usbDeviceFound = GetUsbPort("usbTypeC", usbInternalHub, fid, ucase(category), usbTypeATargetFid) if usbDeviceFound then bsp.replaceUSB700_1_with_USB_C = true endif else if lcase(usbConnectorName) = "usbtypec" then usbDeviceFound = GetUsbPort("usb700_1", usbInternalHub, fid, ucase(category), usbTypeATargetFid) if usbDeviceFound then bsp.replaceUSB_C_with_USB700_1 = true endif endif end if end if next end if end sub Sub BuildUSBDevicesByConnector(bsp as object, sign as object) di = CreateObject("roDeviceInfo") connectedUSBDevices = di.GetUSBTopology({ array : true }) gaa = GetGlobalAA() gaa.usbConnectorNameToUsbSpec = {} for each connector in sign.boseProductsByConnector boseProduct = sign.boseProductsByConnector[connector] GetConnectedUSBDeviceName(bsp, di.GetModel(), connectedUSBDevices, connector, boseProduct.usbInternalHub$) next end sub Sub UnmuteAllAudio() m.UnmuteAudioConnector("Analog:1") m.UnmuteAudioConnector("Analog:2") m.UnmuteAudioConnector("Analog:3") m.UnmuteAudioConnector("HDMI") m.UnmuteAudioConnector("HDMI:1") m.UnmuteAudioConnector("HDMI:2") m.UnmuteAudioConnector("HDMI:3") m.UnmuteAudioConnector("HDMI:4") m.UnmuteAudioConnector("SPDIF") for each usbConnectorName in GetGlobalAA().usbConnectorNameToUsbSpec usbSpec = GetGlobalAA().usbConnectorNameToUsbSpec.Lookup(usbConnectorName) if usbSpec.audioOutputSpec <> "" then m.UnmuteAudioConnector(usbSpec.audioOutputSpec) endif next end sub Sub MuteAudioOutput(muteOn as boolean, parameters as object, parameterName$ as string, objectName$ as string) if parameters.DoesExist(parameterName$) then parameter = parameters[parameterName$] mute$ = parameter.GetCurrentParameterValue() if lcase(mute$) = "true" then audioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector(objectName$)) if type(audioOutput) = "roAudioOutput" then audioOutput.SetMute(muteOn) end if end if end if end sub Sub MuteAudioOutputs(muteOn as boolean, parameters as object) m.MuteAudioOutput(muteOn, parameters, "analog1", "Analog:1") m.MuteAudioOutput(muteOn, parameters, "hdmi", "HDMI") m.MuteAudioOutput(muteOn, parameters, "hdmi:1", "HDMI:1") m.MuteAudioOutput(muteOn, parameters, "hdmi:2", "HDMI:2") m.MuteAudioOutput(muteOn, parameters, "hdmi:3", "HDMI:3") m.MuteAudioOutput(muteOn, parameters, "hdmi:4", "HDMI:4") m.MuteAudioOutput(muteOn, parameters, "spdif", "SPDIF") for each connector in parameters runtimeConnector$ = m.GetRuntimeUsbConnector(connector) if IsUsbPort(runtimeConnector$) then usbSpec = GetGlobalAA().usbConnectorNameToUsbSpec.Lookup(runtimeConnector$) if usbSpec.audioOutputSpec <> "" then m.MuteAudioOutput(muteOn, parameters, connector, usbSpec.audioOutputSpec) endif endif next end sub Sub SetConnectorVolume(parameters as object) parameter = parameters["connector"] specifiedConnector$ = parameter.GetCurrentParameterValue() connector$ = m.GetRuntimeUsbConnector(specifiedConnector$) parameter = parameters["volume"] volume$ = parameter.GetCurrentParameterValue() volume% = int(val(volume$)) if lcase(connector$) = "analog" or lcase(connector$) = "analog1" then m.analogVolume% = ExecuteChangeConnectorVolume("Analog:1", volume%, m.sign.audio1MinVolume%, m.sign.audio1MaxVolume%) else if lcase(connector$) = "hdmi" then m.hdmiVolume% = ExecuteChangeConnectorVolume("HDMI", volume%, m.sign.hdmiMinVolume%, m.sign.hdmiMaxVolume%) else if lcase(connector$) = "hdmi1" then m.hdmi1Volume% = ExecuteChangeConnectorVolume("HDMI:1", volume%, m.sign.hdmi1MinVolume%, m.sign.hdmi1MaxVolume%) else if lcase(connector$) = "hdmi2" then m.hdmi2Volume% = ExecuteChangeConnectorVolume("HDMI:2", volume%, m.sign.hdmi2MinVolume%, m.sign.hdmi2MaxVolume%) else if lcase(connector$) = "hdmi3" then m.hdmi3Volume% = ExecuteChangeConnectorVolume("HDMI:3", volume%, m.sign.hdmi3MinVolume%, m.sign.hdmi3MaxVolume%) else if lcase(connector$) = "hdmi4" then m.hdmi4Volume% = ExecuteChangeConnectorVolume("HDMI:4", volume%, m.sign.hdmi4MinVolume%, m.sign.hdmi4MaxVolume%) else if lcase(connector$) = "spdif" then m.spdifVolume% = ExecuteChangeConnectorVolume("SPDIF", volume%, m.sign.spdifMinVolume%, m.sign.spdifMaxVolume%) else if IsUsbPort(connector$) then usbSpec = GetGlobalAA().usbConnectorNameToUsbSpec.Lookup(connector$) key$ = usbSpec.brightSignUsbPort + usbSpec.brightSignHub if usbSpec.audioOutputSpec <> "" then if specifiedConnector$ = "usbTypeA" then m.usbVolumeA% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usbTypeAMinVolume%, m.sign.usbTypeAMaxVolume%) else if specifiedConnector$ = "usbTypeC" then m.usbVolumeB% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usbTypeCMinVolume%, m.sign.usbTypeCMaxVolume%) else if specifiedConnector$ = "usb700_1" then m.usbVolumeA1% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb700_1MinVolume%, m.sign.usb700_1MaxVolume%) else if specifiedConnector$ = "usb700_2" then m.usbVolumeA2% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb700_2MinVolume%, m.sign.usb700_2MaxVolume%) else if specifiedConnector$ = "usb700_3" then m.usbVolumeA3% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb700_3MinVolume%, m.sign.usb700_3MaxVolume%) else if specifiedConnector$ = "usb700_4" then m.usbVolumeA4% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb700_4MinVolume%, m.sign.usb700_4MaxVolume%) else if specifiedConnector$ = "usb700_5" then m.usbVolumeA5% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb700_5MinVolume%, m.sign.usb700_5MaxVolume%) else if specifiedConnector$ = "usb700_6" then m.usbVolumeA6% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb700_6MinVolume%, m.sign.usb700_6MaxVolume%) else if specifiedConnector$ = "usb700_7" then m.usbVolumeA7% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb700_7MinVolume%, m.sign.usb700_7MaxVolume%) else if specifiedConnector$ = "usb_1" then m.usbVolumeA1% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb_1MinVolume%, m.sign.usb_1MaxVolume%) else if specifiedConnector$ = "usb_2" then m.usbVolumeA2% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb_2MinVolume%, m.sign.usb_2MaxVolume%) else if specifiedConnector$ = "usb_3" then m.usbVolumeA3% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb_3MinVolume%, m.sign.usb_3MaxVolume%) else if specifiedConnector$ = "usb_4" then m.usbVolumeA4% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb_4MinVolume%, m.sign.usb_4MaxVolume%) else if specifiedConnector$ = "usb_5" then m.usbVolumeA5% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb_5MinVolume%, m.sign.usb_5MaxVolume%) else if specifiedConnector$ = "usb_6" then m.usbVolumeA6% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, volume%, m.sign.usb_6MinVolume%, m.sign.usb_6MaxVolume%) else stop end if endif end if end sub Sub ChangeConnectorVolume(multiplier% as integer, parameters as object) parameter = parameters["connector"] specifiedConnector$ = parameter.GetCurrentParameterValue() connector$ = m.GetRuntimeUsbConnector(specifiedConnector$) parameter = parameters["volume"] volumeDelta$ = parameter.GetCurrentParameterValue() volumeDelta% = int(val(volumeDelta$)) * multiplier% if lcase(connector$) = "analog" or lcase(connector$) = "analog1" then m.analogVolume% = ExecuteChangeConnectorVolume("Analog:1", m.analogVolume% + volumeDelta%, m.sign.audio1MinVolume%, m.sign.audio1MaxVolume%) else if lcase(connector$) = "hdmi" then m.hdmiVolume% = ExecuteChangeConnectorVolume("HDMI", m.hdmiVolume% + volumeDelta%, m.sign.hdmiMinVolume%, m.sign.hdmiMaxVolume%) else if lcase(connector$) = "hdmi1" then m.hdmi1Volume% = ExecuteChangeConnectorVolume("HDMI:1", m.hdmiVolume% + volumeDelta%, m.sign.hdmi1MinVolume%, m.sign.hdmi1MaxVolume%) else if lcase(connector$) = "hdmi2" then m.hdmi2Volume% = ExecuteChangeConnectorVolume("HDMI:2", m.hdmiVolume% + volumeDelta%, m.sign.hdmi2MinVolume%, m.sign.hdmi2MaxVolume%) else if lcase(connector$) = "hdmi3" then m.hdmi3Volume% = ExecuteChangeConnectorVolume("HDMI:3", m.hdmiVolume% + volumeDelta%, m.sign.hdmi3MinVolume%, m.sign.hdmi3MaxVolume%) else if lcase(connector$) = "hdmi4" then m.hdmi4Volume% = ExecuteChangeConnectorVolume("HDMI:4", m.hdmiVolume% + volumeDelta%, m.sign.hdmi4MinVolume%, m.sign.hdmi4MaxVolume%) else if lcase(connector$) = "spdif" then m.spdifVolume% = ExecuteChangeConnectorVolume("SPDIF", m.spdifVolume% + volumeDelta%, m.sign.spdifMinVolume%, m.sign.spdifMaxVolume%) else if IsUsbPort(connector$) then usbSpec = GetGlobalAA().usbConnectorNameToUsbSpec.Lookup(connector$) key$ = usbSpec.brightSignUsbPort + usbSpec.brightSignHub if usbSpec.audioOutputSpec <> "" then if specifiedConnector$ = "usbTypeA" then m.usbVolumeTypeA% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeTypeA% + volumeDelta%, m.sign.usbTypeAMinVolume%, m.sign.usbTypeAMaxVolume%) else if specifiedConnector$ = "usbTypeC" then m.usbVolumeTypeC% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeTypeC% + volumeDelta%, m.sign.usbTypeCMinVolume%, m.sign.usbTypeCMaxVolume%) else if specifiedConnector$ = "usb700_1" then m.usbVolumeA1% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA1% + volumeDelta%, m.sign.usb700_1MinVolume%, m.sign.usb700_1MaxVolume%) else if specifiedConnector$ = "usb700_2" then m.usbVolumeA2% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA2% + volumeDelta%, m.sign.usb700_2MinVolume%, m.sign.usb700_2MaxVolume%) else if specifiedConnector$ = "usb700_3" then m.usbVolumeA3% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA3% + volumeDelta%, m.sign.usb700_3MinVolume%, m.sign.usb700_3MaxVolume%) else if specifiedConnector$ = "usb700_4" then m.usbVolumeA4% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA4% + volumeDelta%, m.sign.usb700_4MinVolume%, m.sign.usb700_4MaxVolume%) else if specifiedConnector$ = "usb700_5" then m.usbVolumeA5% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA5% + volumeDelta%, m.sign.usb700_5MinVolume%, m.sign.usb700_5MaxVolume%) else if specifiedConnector$ = "usb700_6" then m.usbVolumeA6% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA6% + volumeDelta%, m.sign.usb700_6MinVolume%, m.sign.usb700_6MaxVolume%) else if specifiedConnector$ = "usb700_7" then m.usbVolumeA7% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA7% + volumeDelta%, m.sign.usb700_7MinVolume%, m.sign.usb700_7MaxVolume%) else if specifiedConnector$ = "usb_1" then m.usbVolumeA1% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA1% + volumeDelta%, m.sign.usb_1MinVolume%, m.sign.usb_1MaxVolume%) else if specifiedConnector$ = "usb_2" then m.usbVolumeA2% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA2% + volumeDelta%, m.sign.usb_2MinVolume%, m.sign.usb_2MaxVolume%) else if specifiedConnector$ = "usb_3" then m.usbVolumeA3% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA3% + volumeDelta%, m.sign.usb_3MinVolume%, m.sign.usb_3MaxVolume%) else if specifiedConnector$ = "usb_4" then m.usbVolumeA4% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA4% + volumeDelta%, m.sign.usb_4MinVolume%, m.sign.usb_4MaxVolume%) else if specifiedConnector$ = "usb_5" then m.usbVolumeA5% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA5% + volumeDelta%, m.sign.usb_5MinVolume%, m.sign.usb_5MaxVolume%) else if specifiedConnector$ = "usb_6" then m.usbVolumeA6% = ExecuteChangeConnectorVolume(usbSpec.audioOutputSpec, m.usbVolumeA6% + volumeDelta%, m.sign.usb_6MinVolume%, m.sign.usb_6MaxVolume%) else stop end if end if end if end sub Function ExecuteChangeConnectorVolume(audioOutputSpec as string, newVolume% as integer, minVolume% as integer, maxVolume% as integer) as integer audioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector(audioOutputSpec)) if type(audioOutput) = "roAudioOutput" then if newVolume% > maxVolume% then newVolume% = maxVolume% else if newVolume% < minVolume% newVolume% = minVolume% end if audioOutput.SetVolume(newVolume%) end if return newVolume% end function Sub SetZoneVolume(parameters as object) parameter = parameters["zoneId"] zoneId$ = parameter.GetCurrentParameterValue() parameter = parameters["volume"] volume$ = parameter.GetCurrentParameterValue() volume% = int(val(volume$)) zone = m.GetZone(zoneId$) 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 end if if IsAudioPlayer(zone.audioPlayer) then zone.audioPlayer.SetVolume(volume%) for i% = 0 to 5 zone.audioChannelVolumes[i%] = volume% next end if end if end sub Sub ChangeZoneVolume(multiplier% as integer, parameters as object) parameter = parameters["zoneId"] zoneId$ = parameter.GetCurrentParameterValue() parameter = parameters["volume"] volumeDelta$ = parameter.GetCurrentParameterValue() volumeDelta% = int(val(volumeDelta$)) * multiplier% zone = m.GetZone(zoneId$) if type(zone) = "roAssociativeArray" then if type(zone.videoPlayer) = "roVideoPlayer" then if multiplier% > 0 then minVolume% = zone.videoPlayerAudioSettings.minVolume% maxVolume% = zone.videoPlayerAudioSettings.maxVolume% else minVolume% = zone.videoPlayerAudioSettings.minVolume% maxVolume% = zone.videoPlayerAudioSettings.maxVolume% end if m.ChangeChannelVolumes(zone.videoPlayer, zone.videoChannelVolumes, 63, volumeDelta%, minVolume%, maxVolume%) end if if IsAudioPlayer(zone.audioPlayer) then if multiplier% > 0 then minVolume% = zone.audioPlayerAudioSettings.minVolume% maxVolume% = zone.audioPlayerAudioSettings.maxVolume% else minVolume% = zone.audioPlayerAudioSettings.minVolume% maxVolume% = zone.audioPlayerAudioSettings.maxVolume% end if m.ChangeChannelVolumes(zone.audioPlayer, zone.audioChannelVolumes, 63, volumeDelta%, minVolume%, maxVolume%) end if end if end sub Sub SetZoneChannelVolume(parameters as object) parameter = parameters["zoneId"] zoneId$ = parameter.GetCurrentParameterValue() parameter = parameters["channel"] channelMask$ = parameter.GetCurrentParameterValue() parameter = parameters["volume"] volume$ = parameter.GetCurrentParameterValue() volume% = int(val(volume$)) zone = m.GetZone(zoneId$) if type(zone) = "roAssociativeArray" then if type(zone.videoPlayer) = "roVideoPlayer" then player = zone.videoPlayer channelVolumes = zone.videoChannelVolumes else if IsAudioPlayer(zone.audioPlayer) then player = zone.audioPlayer channelVolumes = zone.audioChannelVolumes end if m.SetChannelVolumes(player, channelVolumes, int(val(channelMask$)), int(val(volume$))) end if end sub Sub ChangeZoneChannelVolume(multiplier% as integer, parameters as object) parameter = parameters["zoneId"] zoneId$ = parameter.GetCurrentParameterValue() parameter = parameters["channel"] channelMask$ = parameter.GetCurrentParameterValue() parameter = parameters["volume"] volumeDelta$ = parameter.GetCurrentParameterValue() volumeDelta% = int(val(volumeDelta$)) * multiplier% zone = m.GetZone(zoneId$) if type(zone) = "roAssociativeArray" then if type(zone.videoPlayer) = "roVideoPlayer" then player = zone.videoPlayer channelVolumes = zone.videoChannelVolumes minVolume% = zone.videoPlayerAudioSettings.minVolume% maxVolume% = zone.videoPlayerAudioSettings.maxVolume% else if IsAudioPlayer(zone.audioPlayer) then player = zone.audioPlayer channelVolumes = zone.audioChannelVolumes minVolume% = zone.audioPlayerAudioSettings.minVolume% maxVolume% = zone.audioPlayerAudioSettings.maxVolume% end if m.ChangeChannelVolumes(player, channelVolumes, int(val(channelMask$)), volumeDelta%, minVolume%, maxVolume%) end if end sub Sub SetVideoChannnelVolume(zone as object, channelMask$ as string, volume$ as string) zone = m.GetVideoZone(zone) if type(zone) = "roAssociativeArray" then m.SetChannelVolumes(zone.videoPlayer, zone.videoChannelVolumes, int(val(channelMask$)), int(val(volume$))) end if end sub Sub IncrementVideoChannnelVolumes(zone as object, channelMask$ as string, volumeDelta$ as string) zone = m.GetVideoZone(zone) if type(zone) = "roAssociativeArray" then channelMask% = int(val(channelMask$)) m.ChangeVideoVolume(zone, channelMask%, int(val(volumeDelta$)), zone.videoPlayerAudioSettings.minVolume%, zone.videoPlayerAudioSettings.maxVolume%) end if end sub Sub DecrementVideoChannnelVolumes(zone as object, channelMask$ as string, volumeDelta$ as string) zone = m.GetVideoZone(zone) if type(zone) = "roAssociativeArray" then channelMask% = int(val(channelMask$)) delta% = int(val(volumeDelta$)) m.ChangeVideoVolume(zone, channelMask%, - delta%, zone.videoPlayerAudioSettings.minVolume%, zone.videoPlayerAudioSettings.maxVolume%) end if end sub Sub SetAudioVolume(zone as object, parameter$ as string) volume% = int(val(parameter$)) if type(zone) = "roAssociativeArray" then if IsAudioPlayer(zone.audioPlayer) then zone.audioPlayer.SetVolume(volume%) for i% = 0 to 5 zone.audioChannelVolumes[i%] = volume% next end if end if end sub Sub SetAudioChannnelVolume(zone as object, channelMask$ as string, volume$ as string) if type(zone) = "roAssociativeArray" then if IsAudioPlayer(zone.audioPlayer) then m.SetChannelVolumes(zone.audioPlayer, zone.audioChannelVolumes, int(val(channelMask$)), int(val(volume$))) end if end if end sub Sub IncrementAudioVolume(zone as object, parameter$ as string, maxVolume% as integer) m.ChangeAudioVolume(zone, 63, int(val(parameter$)), 0, maxVolume%) end sub Sub DecrementAudioVolume(zone as object, parameter$ as string, minVolume% as integer) delta% = int(val(parameter$)) m.ChangeAudioVolume(zone, 63, - delta%, minVolume%, 100) 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%]) end if next end sub Function GetVideoZone(zone as object) as object if type(zone) = "roAssociativeArray" then if type(zone.videoPlayer) = "roVideoPlayer" then return zone end if end if if type(m.sign) = "roAssociativeArray" then if type(m.sign.videoZoneHSM) = "roAssociativeArray" and type(m.sign.videoZoneHSM.videoPlayer) = "roVideoPlayer" then return m.sign.videoZoneHSM end if end if return invalid end function Function GetZone(zoneId$ as string) as object for each zone in m.sign.zonesHSM if zone.id$ = zoneId$ then return zone end if next return invalid end function Sub ChangeChannelVolumes(player as object, channelVolumes as object, channelMask% as integer, delta% as integer, minVolume% as integer, maxVolume% as integer) for i% = 0 to 5 mask% = 2 ^ i% if channelMask% and mask% then channelVolumes[i%] = channelVolumes[i%] + delta% if channelVolumes[i%] > maxVolume% then channelVolumes[i%] = maxVolume% else if channelVolumes[i%] < minVolume% then channelVolumes[i%] = minVolume% end if player.SetChannelVolumes(mask%, channelVolumes[i%]) end if next end sub Sub ConfigureAudioResources() if type(m.videoPlayer) = "roVideoPlayer" then m.videoPlayer.ConfigureAudioResources() else if IsAudioPlayer(m.audioPlayer) then m.audioPlayer.ConfigureAudioResources() end if end sub ' Send the CEC display on command through the specified video connector Sub CecDisplayOn(parameters as object) videoConnector$ = getTextParameterFallbackToEmpty(parameters, "videoConnector") m.SendCecCommand("400D", true, videoConnector$) end sub ' Send the CEC display off command through the specified video connector Sub CecDisplayOff(parameters as object) videoConnector$ = getTextParameterFallbackToEmpty(parameters, "videoConnector") m.SendCecCommand("4036", true, videoConnector$) end sub ' Send the CEC set source command through the specified video connector Sub CecSetSourceToBrightSign(parameters as object) videoConnector$ = getTextParameterFallbackToEmpty(parameters, "videoConnector") m.SendCecCommand("4F821000", false, videoConnector$) end sub ' Helper function of ExecuteCecPhilipsSetVolumeCommand Sub CecPhilipsSetVolume(volume% as integer, videoConnector$ as string) b = CreateObject("roByteArray") b[0] = volume% volumeAsAscii$ = b.ToHexString() b = invalid setVolume$ = "40A0000C3022" + volumeAsAscii$ SendCecCommand(setVolume$, true, videoConnector$) end sub ' Helper function to send the CEC command Sub SendCecCommand(cecCommand$ as string, cecSubstituteSourceAddress as boolean, videoConnector$ as string) if videoConnector$ <> "" then cec = CreateObject("roCecInterface", videoConnector$) else cec = CreateObject("roCecInterface") end if if type(cec) = "roCecInterface" then if not cecSubstituteSourceAddress then cec.UseInitiatorAddressFromPacket(true) end if b = CreateObject("roByteArray") b.fromhexstring(cecCommand$) cec.SendRawMessage(b) cec = invalid end if end sub Sub PauseVideo(zone as object) zone = m.GetVideoZone(zone) if type(zone) = "roAssociativeArray" then zone.videoPlayer.Pause() end if end sub Sub ResumeVideo(zone as object) zone = m.GetVideoZone(zone) if type(zone) = "roAssociativeArray" then zone.videoPlayer.Resume() end if end sub Sub SetPowerSaveMode(enablePowerSaveMode as boolean) videoMode = CreateObject("roVideoMode") if type(videoMode) = "roVideoMode" then videoMode.SetPowerSaveMode(enablePowerSaveMode) end if videoMode = invalid end sub Function GetAttachedFiles() as object return m.additionalPublishedFiles end function Function PostponeRestart() as boolean if m.dontChangePresentationUntilMediaEndEventReceived then m.restartPendingMediaEnd = true end if return m.dontChangePresentationUntilMediaEndEventReceived end function Function ProcessMediaEndEvent() as boolean executeContentRestart = m.restartPendingMediaEnd if executeContentRestart then m.diagnostics.PrintDebug("ProcessMediaEndEvent - execute content update") m.restartPendingMediaEnd = false ' send internal message to prepare for restart prepareForRestartEvent = { } prepareForRestartEvent["EventType"] = "PREPARE_FOR_RESTART" m.msgPort.PostMessage(prepareForRestartEvent) ' send internal message indicating that new content is available contentUpdatedEvent = { } contentUpdatedEvent["EventType"] = "CONTENT_UPDATED" m.msgPort.PostMessage(contentUpdatedEvent) end if return executeContentRestart end function 'endregion 'region Common Zone State Machine Methods ' ************************************************* ' ' Common Zone State Machine Methods ' ' ************************************************* Sub newZoneCommon(bsp as object, zoneDescription as object, zoneHSM as object) zoneHSM.audioPlayer = invalid zoneHSM.videoPlayer = invalid zoneHSM.name$ = zoneDescription.name$ ' retrieve values from supplied bsdm parameters zoneHSM.originalWidth% = zoneDescription.originalWidth% zoneHSM.originalHeight% = zoneDescription.originalHeight% ' scale the zones if necessary scaleScreenElement(bsp, true, zoneHSM, zoneDescription) zoneHSM.type$ = zoneDescription.type zoneHSM.id$ = zoneDescription.id$ '' else '' zoneHSM.name$ = zoneXML.name.GetText() ' scale the zones if necessary '' zoneHSM.originalWidth% = int(val(zone.width.GetText())) '' zoneHSM.originalHeight% = int(val(zone.height.GetText())) '' scaleScreenElement(bsp, true, zoneHSM, zone) '' zoneHSM.type$ = zone.type.GetText() '' zoneHSM.id$ = zone.id.GetText() '' endif zoneHSM.isVisible = true zoneHSM.imageHidden = false zoneHSM.canvasHidden = false zoneHSM.htmlHidden = false zoneHSM.mosaicDecoderName = "" zoneHSM.bsp = bsp zoneHSM.ConfigureAudioResources = ConfigureAudioResources zoneHSM.SetAudioOutputAndMode = SetAudioOutputAndMode zoneHSM.LogPlayStart = LogPlayStart zoneHSM.ClearImagePlane = ClearImagePlane zoneHSM.ShowImageWidget = ShowImageWidget zoneHSM.ShowCanvasWidget = ShowCanvasWidget zoneHSM.ShowHtmlWidget = ShowHtmlWidget zoneHSM.UpdateWidgetVisibility = UpdateWidgetVisibility zoneHSM.stTop = zoneHSM.newHState(bsp, "Top") zoneHSM.stTop.HStateEventHandler = STTopEventHandler zoneHSM.topState = zoneHSM.stTop end sub Sub InitializeZoneCommon(msgPort as object) zoneHSM = m zoneHSM.msgPort = msgPort zoneHSM.isVideoZone = false zoneHSM.preloadState = invalid zoneHSM.preloadedStateName$ = "" zoneHSM.rectangle = CreateScaledRectangle(zoneHSM.x%, zoneHSM.y%, zoneHSM.width%, zoneHSM.height%) ' byte arrays to store stream byte input zoneHSM.serialStreamInputBuffers = CreateObject("roArray", 8, true) for i% = 0 to 7 zoneHSM.serialStreamInputBuffers[i%] = CreateObject("roByteArray") next end sub Function CreateScaledRectangle(x% as integer, y% as integer, width% as integer, height% as integer) as object rectangle = { x%: x%, y%: y%, width%: width%, height%: height%, } ScaleResolutionGraphics(rectangle) return CreateObject("roRectangle", rectangle.x%, rectangle.y%, rectangle.width%, rectangle.height%) end function Sub ScaleResolutionGraphics(rectangle as object) videoMode = GetVideoMode() ' No need to scale if cannot create video mode class if type(videoMode) <> "roVideoMode" then return if ShouldScaleGraphicElements(videoMode) then ' Scale x, y, width and height if exists scaleRatio = GetGraphicScaleRatio(videoMode) if IsInteger(rectangle.x%) then rectangle.x% = rectangle.x% * scaleRatio if IsInteger(rectangle.y%) then rectangle.y% = rectangle.y% * scaleRatio if IsInteger(rectangle.width%) then rectangle.width% = rectangle.width% * scaleRatio if IsInteger(rectangle.height%) then rectangle.height% = rectangle.height% * scaleRatio end if videoMode = invalid end sub Function ShouldScaleGraphicElements(videoMode as object) as boolean ' Check if we are running presentation on model that supports multi screen globalAA = getGlobalAA() useScreenModes = CanUseScreenModes(globalAA.bsp.sign, videoMode) ' If the graphics resolution differs from video resolution, we need to scale the other elements. ' Screen modes check is used to verify only scale for series 5 players. return videoMode.GetResX() <> videoMode.GetVideoResX() and useScreenModes end function Function GetGraphicScaleRatio(videoMode as object) as float return videoMode.GetResX() / videoMode.GetVideoResX() end function 'endregion 'region MediaItem Methods ' ************************************************* ' ' MediaItem Methods ' ' ************************************************* Function GetNextStateName(transition as object) as object nextState = { } if type(transition.conditionalTransitions) = "roArray" then for each conditionalTransition in transition.conditionalTransitions matchFound = false currentValue% = val(conditionalTransition.userVariable.GetCurrentValue()) userVariableValue = conditionalTransition.userVariableValue.GetCurrentParameterValue() userVariableValue% = val(userVariableValue) if conditionalTransition.operator$ = "EQ" then if conditionalTransition.userVariable.GetCurrentValue() = userVariableValue then matchFound = true end if else if conditionalTransition.operator$ = "NEQ" then if conditionalTransition.userVariable.GetCurrentValue() <> userVariableValue then matchFound = true end if else if conditionalTransition.operator$ = "LT" then if currentValue% < userVariableValue% then matchFound = true end if else if conditionalTransition.operator$ = "LTE" then if currentValue% <= userVariableValue% then matchFound = true end if else if conditionalTransition.operator$ = "GT" then if currentValue% > userVariableValue% then matchFound = true end if else if conditionalTransition.operator$ = "GTE" then if currentValue% >= userVariableValue% then matchFound = true end if else if conditionalTransition.operator$ = "BTW" then userVariableValue2 = conditionalTransition.userVariableValue2.GetCurrentParameterValue() userVariableValue2% = val(userVariableValue2) if currentValue% >= userVariableValue% and currentValue% <= userVariableValue2% then matchFound = true end if end if if matchFound then if conditionalTransition.targetMediaStateIsPreviousState then nextState$ = m.stateMachine.previousStateName$ else nextState$ = conditionalTransition.targetMediaState$ end if nextState.nextState$ = nextState$ nextState.actualTarget = conditionalTransition return nextState end if next end if if transition.targetMediaStateIsPreviousState then nextState$ = m.stateMachine.previousStateName$ else nextState$ = transition.targetMediaState$ end if nextState.nextState$ = nextState$ nextState.actualTarget = transition return nextState end function Sub UpdatePreviousCurrentStateNames() m.stateMachine.previousStateName$ = m.id$ end sub Function GetAnyMediaRSSTransition() as object transition = invalid ' support others? if type(m.signChannelEndEvent) = "roAssociativeArray" then transition = m.signChannelEndEvent else if type(m.mstimeoutEvent) = "roAssociativeArray" then transition = m.mstimeoutEvent end if return transition end function Function ExecuteTransition(transition as object, stateData as object, payload$ as string) as string nextState$ = "init" while nextState$ <> "" ' before transitioning to next state, ensure that the transition is allowed nextState = m.GetNextStateName(transition) nextState$ = nextState.nextState$ actualTarget = nextState.actualTarget if nextState$ <> "" then nextState = m.stateMachine.stateTable[nextState$] if nextState.type$ = "mediaRSS" and nextState.rssURL$ = "" then ' skip an empty localized playlist m.bsp.diagnostics.PrintDebug("Unassigned local playlist " + nextState.name$ + " encountered, attempt to navigate to next state.") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_UNASSIGNED_LOCAL_PLAYLIST, nextState.name$) defaultTransition = nextState.GetAnyMediaRSSTransition() if defaultTransition <> invalid then transition = defaultTransition else ' no transition found - not sure what to do m.bsp.diagnostics.PrintDebug("Unable to navigate from unassigned local playlist " + nextState.name$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_UNASSIGNED_LOCAL_PLAYLIST_NO_NAVIGATION, nextState.name$) exit while end if else if nextState.type$ = "playFile" then if nextState.useUserVariable then userVariable = nextState.userVariable payload$ = userVariable.GetCurrentValue() end if if not nextState.useDefaultMedia and not nextState.filesTable.DoesExist(payload$) then m.bsp.diagnostics.PrintDebug("transition cancelled - payload " + payload$ + " not found in target state's table") return "HANDLED" else ' set payload$ member before ExecuteTransitionCommands is called - needed if there is a synchronize transition command nextState.payload$ = payload$ end if end if exit while end if end if end while switchToNewPresentation = m.bsp.ExecuteTransitionCommands(m.stateMachine, actualTarget) if switchToNewPresentation then return "HANDLED" end if if nextState$ = "" then if transition.remainOnCurrentStateActions = "stop" then if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.Stop() end if else if transition.remainOnCurrentStateActions = "stopclear" then if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if if type(m.stateMachine.imagePlayer) = "roImageWidget" then m.stateMachine.imagePlayer.StopDisplay() end if end if return "HANDLED" else stateData.nextState = m.stateMachine.stateTable[nextState$] stateData.nextState.payload$ = payload$ m.UpdatePreviousCurrentStateNames() return "TRANSITION" end if end function Sub AssignWildcardInputToUserVariable(bsp as object, input$ as string) if type(m.variableToAssignFromWildcard) = "roAssociativeArray" then m.variableToAssignFromWildcard.SetCurrentValue(input$, true) end if end sub Sub AssignEventInputToUserVariable(bsp as object, input$ as string) if type(m.variableToAssignFromInput) = "roAssociativeArray" then m.variableToAssignFromInput.SetCurrentValue(input$, true) else userVariablesUpdated = false regex = CreateObject("roRegEx", "!!", "i") variableAssignments = regex.Split(input$) if variableAssignments.Count() > 0 then for each variableAssignment in variableAssignments regex = CreateObject("roRegEx", ":", "i") parts = regex.Split(variableAssignment) if parts.Count() = 2 then variableToAssign$ = parts[0] newValue$ = parts[1] variableToAssign = bsp.GetUserVariable(variableToAssign$) if variableToAssign = invalid then bsp.diagnostics.PrintDebug("User variable " + variableToAssign$ + " not found.") else variableToAssign.SetCurrentValue(newValue$, false) userVariablesUpdated = true end if end if next end if if userVariablesUpdated then userVariablesChanged = { } userVariablesChanged["EventType"] = "USER_VARIABLES_UPDATED" bsp.msgPort.PostMessage(userVariablesChanged) ' Notify controlling devices to refresh bsp.SendUDPNotification("refresh") end if end if end sub Sub SetMediaItemEventHandlers(state as object) state.MediaItemEventHandler = MediaItemEventHandler state.KeyboardPressEventHandler = KeyboardPressEventHandler state.DatagramEventHandler = DatagramEventHandler state.SerialStreamLineEventHandler = SerialStreamLineEventHandler state.GpsEventHandler = GpsEventHandler state.RemoteDownEventHandler = RemoteDownEventHandler state.WssEventHandler = WssEventHandler end sub Function GpsEventHandler(event as object, stateData as object) as string gpsData = ParseGPSdataGPRMCformat(event) if gpsData.valid then ' log GPS events on first event, then no more frequently than every 30 seconds logGPSEvent = false currentTime = m.bsp.systemTime.GetLocalDateTime() if type(m.nextTimeToLogGPSEvent$) = "roString" then if currentTime.GetString() > m.nextTimeToLogGPSEvent$ then logGPSEvent = true end if else logGPSEvent = true end if if logGPSEvent then currentTime.AddSeconds(30) m.nextTimeToLogGPSEvent$ = currentTime.GetString() end if if gpsData.fixActive then if logGPSEvent then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_GPS_LOCATION, str(gpsData.latitude) + ":" + str(gpsData.longitude)) end if m.bsp.diagnostics.PrintDebug("GPS location: " + str(gpsData.latitude) + "," + str(gpsData.longitude)) m.bsp.gpsLocation.latitude = gpsData.latitude m.bsp.gpsLocation.longitude = gpsData.longitude latitudeInRadians = ConvertDecimalDegtoRad(m.bsp.gpsLocation.latitude) longitudeInRadians = ConvertDecimalDegtoRad(m.bsp.gpsLocation.longitude) for each transition in m.gpsEnterRegionEvents distance = CalcGPSDistance (latitudeInRadians, longitudeInRadians, transition.latitudeInRadians, transition.longitudeInRadians) m.bsp.diagnostics.PrintDebug("GPS distance from longitude " + str(transition.longitude) + ", latitude " + str(transition.latitude) + " = " + str(distance)) if distance < transition.radiusInFeet then m.bsp.diagnostics.PrintDebug("GPS enter region") m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "gpsEnterRegion", str(gpsData.latitude) + ":" + str(gpsData.longitude), "1") return m.ExecuteTransition(transition, stateData, "") end if next for each transition in m.gpsExitRegionEvents distance = CalcGPSDistance (latitudeInRadians, longitudeInRadians, transition.latitudeInRadians, transition.longitudeInRadians) m.bsp.diagnostics.PrintDebug("GPS distance from longitude " + str(transition.longitude) + ", latitude " + str(transition.latitude) + " = " + str(distance)) if distance > transition.radiusInFeet then m.bsp.diagnostics.PrintDebug("GPS exit region") m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "gpsExitRegion", str(gpsData.latitude) + ":" + str(gpsData.longitude), "1") return m.ExecuteTransition(transition, stateData, "") end if next else if logGPSEvent then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_GPS_NOT_LOCKED, "") end if m.bsp.gpsLocation.latitude = invalid m.bsp.gpsLocation.longitude = invalid end if else ' print "GPS not valid" end if stateData.nextState = m.superState return "SUPER" end function Function SerialStreamLineEventHandler(event as object, stateData as object) as string serialEvent$ = event.GetString() port$ = event.GetUserData() if not IsUsbPort(port$) then port% = int(val(port$)) if m.bsp.gpsConfigured and m.bsp.sign.serialPortConfigurations[port%].gps then return m.GpsEventHandler(event, stateData) end if end if m.bsp.diagnostics.PrintDebug("Serial Line Event " + event.GetString()) serialEvents = m.serialEvents if type(serialEvents) = "roAssociativeArray" then if type(serialEvents[port$]) = "roAssociativeArray" then ' look for an exact match first if type(serialEvents[port$][serialEvent$]) = "roAssociativeArray" then transition = serialEvents[port$][serialEvent$] m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "serial", port$ + " " + serialEvent$, "1") return m.ExecuteTransition(transition, stateData, serialEvent$) else ' look for regular expression match with each of the possible serial events for the current state for each serialEventSpec in serialEvents[port$] ' only look for regular expressions if spec includes wildcard if instr(1, serialEventSpec, "(.*)") > 0 then r = CreateObject("roRegEx", serialEventSpec, "i") if type(r) = "roRegex" then matches = r.match(serialEvent$) if matches.Count() > 0 then transition = serialEvents[port$][serialEventSpec] m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "serial", port$ + " " + serialEvent$, "1") if transition.assignInputToUserVariable then transition.AssignEventInputToUserVariable(m.bsp, serialEvent$) end if if matches.Count() > 1 and transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m.bsp, matches[1]) end if return m.ExecuteTransition(transition, stateData, serialEvent$) end if end if end if next end if end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "serial", port$ + " " + serialEvent$, "0") stateData.nextState = m.superState return "SUPER" end function Function DatagramEventHandler(event as object, stateData as object) as string ' could be either a udp event or a synchronize event m.bsp.diagnostics.PrintDebug("UDP Event " + event.GetString()) udpEvent$ = event.GetString() synchronizeEvents = m.synchronizeEvents udpEvents = m.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 nextState$ = synchronizeEvents[synchronizeEvent$].targetMediaState$ nextState = m.stateMachine.stateTable[nextState$] preloadRequired = true if type(m.stateMachine.preloadState) = "roAssociativeArray" then if m.stateMachine.preloadedStateName$ = nextState.name$ preloadRequired = false end if end if ' set this variable so that launchVideo knows what has been preloaded m.stateMachine.preloadState = nextState ' currently only support preload / synchronizing with images and videos if preloadRequired then m.stateMachine.preloadState.PreloadItem() end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "synchronize-pre", synchronizeEvent$, "1") ' ?? return "HANDLED" ?? end if end if index% = instr(1, udpEvent$, "ply-") if index% = 1 then ' just transition to the next state where the file will be played synchronizeEvent$ = mid(udpEvent$, 5) if type(synchronizeEvents[synchronizeEvent$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "synchronize-play", synchronizeEvent$, "1") return m.ExecuteTransition(m.synchronizeEvents[synchronizeEvent$], stateData, "") end if end if end if if type(udpEvents) = "roAssociativeArray" then if type(udpEvents[udpEvent$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "udp", udpEvent$, "1") transition = udpEvents[udpEvent$] return m.ExecuteTransition(transition, stateData, udpEvent$) else ' look for regular expression match with each of the possible udp events for the current state for each udpEventSpec in udpEvents ' only look for regular expressions if spec includes wildcard if instr(1, udpEventSpec, "(.*)") > 0 then r = CreateObject("roRegEx", udpEventSpec, "i") if type(r) = "roRegex" then matches = r.match(udpEvent$) if matches.Count() > 0 then transition = udpEvents[udpEventSpec] m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "udp", udpEvent$, "1") if transition.assignInputToUserVariable then transition.AssignEventInputToUserVariable(m.bsp, event.GetString()) end if if matches.Count() > 1 and transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m.bsp, matches[1]) end if return m.ExecuteTransition(transition, stateData, udpEvent$) end if end if end if next end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "udp", event.GetString(), "0") stateData.nextState = m.superState return "SUPER" end function Function RemoteDownEventHandler(event as object, stateData as object) as string m.bsp.diagnostics.PrintDebug("Remote Event" + stri(event)) irRemoteControl = m.bsp.sign.irRemoteControl manufacturerCode = irRemoteControl.manufacturerCode buttons = irRemoteControl.buttons targetButtonCode = event - (manufacturerCode * 256) buttonCode$ = StripLeadingSpaces(stri(targetButtonCode)) remoteEvents = m.remoteEvents if type(remoteEvents) = "roAssociativeArray" then if buttons.DoesExist(buttonCode$) then button = buttons[buttonCode$] buttonDescription = button.buttonDescription if type(remoteEvents[buttonDescription]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "remote", buttonDescription, "1") return m.ExecuteTransition(m.remoteEvents[buttonDescription], stateData, buttonDescription) end if endif endif m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "remote", buttonCode$, "0") stateData.nextState = m.superState return "SUPER" end function Function KeyboardPressEventHandler(event as object, stateData as object) as string ' 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.bsp.diagnostics.PrintDebug("Keyboard Press" + str(event.GetInt())) keyboardChar$ = chr(event.GetInt()) keyboardCharForLog$ = StripLeadingSpaces(stri(event.GetInt())) usbStringEvents = m.usbStringEvents keyboardEvents = m.keyboardEvents checkUSBInputString = false if type(usbStringEvents) = "roAssociativeArray" then if event.GetInt() <> 13 then m.usbInputBuffer$ = m.usbInputBuffer$ + keyboardChar$ if m.usbInputLogBuffer$ = "" then m.usbInputLogBuffer$ = keyboardCharForLog$ else m.usbInputLogBuffer$ = m.usbInputLogBuffer$ + "," + keyboardCharForLog$ end if checkUSBInputString = false else checkUSBInputString = true end if end if ' check for bar code input (usb characters terminated by an Enter key) if type(usbStringEvents) = "roAssociativeArray" then if checkUSBInputString then if type(usbStringEvents[m.usbInputBuffer$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "usb", m.usbInputLogBuffer$, "1") action$ = m.ExecuteTransition(m.usbStringEvents[m.usbInputBuffer$], stateData, m.usbInputBuffer$) if event.GetInt() = 13 then m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" end if return action$ else ' check for wildcards ' look for regular expression match with each of the possible usb events for the current state for each usbEventSpec in usbStringEvents ' only look for regular expressions if spec includes wildcard if instr(1, usbEventSpec, "(.*)") > 0 then r = CreateObject("roRegEx", usbEventSpec, "i") if type(r) = "roRegex" then matches = r.match(m.usbInputBuffer$) if matches.Count() > 0 then transition = usbStringEvents[usbEventSpec] m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "usb", m.usbInputBuffer$, "1") if transition.assignInputToUserVariable then transition.AssignEventInputToUserVariable(m.bsp, m.usbInputBuffer$) end if if matches.Count() > 1 and transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m.bsp, matches[1]) end if action$ = m.ExecuteTransition(transition, stateData, m.usbInputBuffer$) if event.GetInt() = 13 then m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" end if return action$ end if end if end if next ' legacy wildcard support usbInputBuffer$ = "" if type(usbStringEvents[usbInputBuffer$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "usb", m.usbInputLogBuffer$, "1") action$ = m.ExecuteTransition(m.usbStringEvents[usbInputBuffer$], stateData, m.usbInputBuffer$) if event.GetInt() = 13 then m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" end if return action$ end if end if end if end if ' check for single keyboard characters keyboardPayload$ = keyboardChar$ if type(keyboardEvents) = "roAssociativeArray" then ' if keyboard input is non printable character, convert it to the special code keyboardCode$ = m.bsp.GetNonPrintableKeyboardCode(event.GetInt()) if keyboardCode$ <> "" then keyboardChar$ = keyboardCode$ keyboardPayload$ = keyboardChar$ end if if type(keyboardEvents[keyboardChar$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "keyboard", keyboardCharForLog$, "1") action$ = m.ExecuteTransition(m.keyboardEvents[keyboardChar$], stateData, keyboardPayload$) if event.GetInt() = 13 then m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" end if return action$ else if type(keyboardEvents[""]) = "roAssociativeArray" then keyboardChar$ = "" m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "keyboard", keyboardCharForLog$, "1") action$ = m.ExecuteTransition(m.keyboardEvents[keyboardChar$], stateData, keyboardPayload$) if event.GetInt() = 13 then m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" end if return action$ end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "usb", keyboardCharForLog$, "0") ' clear the buffer when the user presses enter if event.GetInt() = 13 then m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" end if stateData.nextState = m.superState return "SUPER" end function Function WssEventHandler(event as object, stateData as object, pluginMessage$ as string) as string if pluginMessage$ = "webSocketEvent" and type(event["WebsocketEvent"]) = "roAssociativeArray" then if type(m.wssEvents) = "roAssociativeArray" then webSocketEvent = event["WebsocketEvent"] ' event from Eddie rivieraPort$ = "" globalAA = getGlobalAA() ' globalAA.eddieDumpFile.sendLine(event["WebsocketEvent"]) m.bsp.diagnostics.PrintDebug("WEBSOCKET EVENT:") m.bsp.diagnostics.PrintDebug("header") m.bsp.diagnostics.PrintDebug(formatJson(event["WebsocketEvent"].header)) m.bsp.diagnostics.PrintDebug("body") m.bsp.diagnostics.PrintDebug(formatJson(event["WebsocketEvent"].body)) ' print "AUTORUN RECEIVED EVENT:" ' eventDateTime = m.bsp.systemTime.GetLocalDateTime() ' print eventDateTime.GetString() 'print formatJson(event["WebsocketEvent"].body) 'dumpJsonBody(event["WebsocketEvent"].body) 'globalAA.eddieDumpFile.sendLine("**** NEW WebSocketEvent") 'dumpJsonBody(globalAA.eddieDumpFile, event["WebsocketEvent"]) ' find USB device that corresponds to this webSocketEvent ' eventFid$ = "USB " + webSocketEvent.fid for each usbConnector in m.bsp.sign.boseProductsByConnector boseProduct = m.bsp.sign.boseProductsByConnector[usbConnector] if type(boseProduct.usbSpec) = "roAssociativeArray" then outputSpec = boseProduct.usbSpec.hidOutputSpec fid$ = mid(outputSpec, 5) ' port$ = boseProduct.port$ ' fid$ = port$ + "." + boseProduct.usbNetInterfaceIndex$ if webSocketEvent.fid = fid$ then rivieraPort$ = usbConnector exit for endif end if next ' if fid specified in webSocketEvent doesn't match USB connector specified in bpf, discard event if rivieraPort$ <> "" then resource = webSocketEvent.header.resource ' event end point - definitely needs to be matched wssPortEvents = m.wssEvents[rivieraPort$] ' wss events for this state for this port if type(wssPortEvents) = "roAssociativeArray" and wssPortEvents.DoesExist(resource) then ' is this state waiting for an event from this resource? wssEventInfo = wssPortEvents.Lookup(resource) boseProduct = m.bsp.sign.boseProductsByConnector[rivieraPort$] wssCommunicationSpec = boseProduct.wssCommunicationSpec wssCommunicationSpecEvent = GetWssCommunicationSpecEventFromWebSocketEvent(wssCommunicationSpec, webSocketEvent) if wssCommunicationSpecEvent <> invalid then wssEventInfo = wssPortEvents.Lookup(resource) ' if yes, get the additional parameters for this event (from the autoplay) ' Look for a match if type(wssEventInfo.wssEventTransitionEventSpecs) = "roAssociativeArray" then ' multiple transitions exist for this state / event - see if the event matches one of them ' look for a match for the event and any of the event/transition pairs for this state' for each propertyName in wssEventInfo.wssEventTransitionEventSpecs ' check for an exact match wssEventTransitionEventSpec = wssEventInfo.wssEventTransitionEventSpecs[propertyName] paramAttrs = m.MatchWssEvent(wssCommunicationSpecEvent, webSocketEvent, wssEventInfo.wssEventParameter, wssEventTransitionEventSpec, propertyName) if paramAttrs.matchFound then return m.ExecuteTransition(paramAttrs.transition, stateData, event.PluginMessage) end if next else ' **** is it necessary to do additional matching at this point? transition = wssEventInfo.transition return m.ExecuteTransition(transition, stateData, event.PluginMessage) end if end if end if end if end if end if stateData.nextState = m.superState return "SUPER" end function Function MediaItemEventHandler(event as object, stateData as object) as object if type(event) = "roControlDown" and IsControlPort(m.bsp.controlPort) and type(m.auxDisconnectEvents) = "roAssociativeArray" and stri(event.GetSourceIdentity()) = stri(m.bsp.controlPort.GetIdentity()) then m.bsp.diagnostics.PrintDebug("Control Down" + str(event.GetInt())) gpioNum$ = StripLeadingSpaces(str(event.GetInt())) if gpioNum$ = "31" then if type(m.auxDisconnectEvents["BrightSignAuxIn"]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "auxDisconnect", gpioNum$, "1") return m.ExecuteTransition(m.auxDisconnectEvents["BrightSignAuxIn"], stateData, "") end if end if else if type(event) = "roControlUp" and IsControlPort(m.bsp.controlPort) and type(m.auxConnectEvents) = "roAssociativeArray" and stri(event.GetSourceIdentity()) = stri(m.bsp.controlPort.GetIdentity()) then m.bsp.diagnostics.PrintDebug("Control Up" + str(event.GetInt())) gpioNum$ = StripLeadingSpaces(str(event.GetInt())) if gpioNum$ = "31" then if type(m.auxConnectEvents["BrightSignAuxIn"]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "auxConnect", gpioNum$, "1") return m.ExecuteTransition(m.auxConnectEvents["BrightSignAuxIn"], stateData, "") end if end if else if type(event) = "roTimerEvent" then if type(m.mstimeoutEvent) = "roAssociativeArray" then if type(m.mstimeoutTimer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(m.mstimeoutTimer.GetIdentity()) then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "timer", "", "1") return m.ExecuteTransition(m.mstimeoutEvent, stateData, "") end if end if end if if type(m.timeClockEvents) = "roArray" then for each timeClockEvent in m.timeClockEvents if type(timeClockEvent.timer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(timeClockEvent.timer.GetIdentity()) then systemTime = CreateObject("roSystemTime") currentDateTime = systemTime.GetLocalDateTime() ' daily timer if type(timeClockEvent.timeClockDaily%) = "roInt" then triggerEvent = EventActiveToday(currentDateTime, timeClockEvent.daysOfWeek%) ' restart timer LaunchTimeClockEventTimer(m, timeClockEvent) if not triggerEvent then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "timeClock", "", "0") return "HANDLED" end if ' periodic timer else if type(timeClockEvent.timeClockPeriodicInterval%) = "roInt" then ' units in seconds rather than minutes? currentTime% = currentDateTime.GetHour() * 60 + currentDateTime.GetMinute() startTime% = timeClockEvent.timeClockPeriodicStartTime% endTime% = timeClockEvent.timeClockPeriodicEndTime% intervalTime% = timeClockEvent.timeClockPeriodicInterval% triggerEvent = false withinWindow = TimeWithinWindow(currentTime%, startTime%, endTime%) if withinWindow then triggerEvent = EventActiveToday(currentDateTime, timeClockEvent.daysOfWeek%) end if ' restart timer LaunchTimeClockEventTimer(m, timeClockEvent) if not triggerEvent then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "timeClock", "", "0") return "HANDLED" end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "timeClock", "", "1") return m.ExecuteTransition(timeClockEvent.transition, stateData, "") end if end if next end if userData = event.GetUserData() if type(userData) = "roAssociativeArray" then if IsString(userData.id) then if userData.id = "mediaList" then mediaListState = userData.state mediaListState.playbackIndex% = mediaListState.startIndex% return "HANDLED" end if end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "timer", "", "0") else if type(event) = "roTouchEvent" then touchIndex$ = str(event.GetInt()) m.bsp.diagnostics.PrintDebug("Touch event" + touchIndex$) if type(m.touchEvents) = "roAssociativeArray" then touchEvent = m.touchEvents[touchIndex$] if type(touchEvent) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "touch", touchIndex$, "1") return m.ExecuteTransition(touchEvent, stateData, "") end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "touch", touchIndex$, "0") else if type(event) = "roBmapDisconnectedEvent" then port$ = event.GetUserData() m.bsp.diagnostics.PrintDebug("roBmapDisconnectedEvent received on port " + port$) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "bmapDisconnected", port$, "0") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BMAP_DISCONNECTED, port$) m.bsp.diagnostics.PrintDebug("process roBmapDisconnectedEvent") if type(m.bsp.bmapByPort) = "roAssociativeArray" and m.bsp.bmapByPort.DoesExist(port$) then m.bsp.bmapByPort[port$] = invalid end if m.bsp.ScheduleRetryCreateBMap(port$) else if type(event) = "roStreamEndEvent" then port$ = event.GetUserData() m.bsp.diagnostics.PrintDebug("roStreamEndEvent received on port " + port$) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "streamEnd", port$, "0") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_STREAM_END, port$) if not m.bsp.pluginProcessesStreamEndEvent then m.bsp.diagnostics.PrintDebug("process roStreamEndEvent") outputOnly = false if type(m.bsp.serial) = "roAssociativeArray" and m.bsp.serial.DoesExist(port$) then m.bsp.serial[port$] = invalid end if if type(m.bsp.serialOutputOnlySpec) = "roAssociativeArray" and m.bsp.serialOutputOnlySpec.DoesExist(port$) then outputOnly = m.bsp.serialOutputOnlySpec[port$] end if m.bsp.ScheduleRetryCreateSerial(port$, outputOnly) else m.bsp.diagnostics.PrintDebug("ignore roStreamEndEvent") endif else if type(event) = "roStreamLineEvent" then return m.SerialStreamLineEventHandler(event, stateData) else if type(event) = "roStreamByteEvent" then m.bsp.diagnostics.PrintDebug("Serial Byte Event " + str(event.GetInt())) serialByte% = event.GetInt() port$ = event.GetUserData() ' compare the serialStreamInput to all expected inputs. execute transition if a match is found. if type(m.serialEvents[port$]) = "roAssociativeArray" then port% = int(val(port$)) serialStreamInput = m.stateMachine.serialStreamInputBuffers[port%] while serialStreamInput.Count() >= 64 serialStreamInput.Shift() end while serialStreamInput.push(serialByte%) if type(m.serialEvents[port$].streamInputTransitionSpecs) = "roArray" then streamInputTransitionSpecs = m.serialEvents[port$].streamInputTransitionSpecs for i% = 0 to streamInputTransitionSpecs.Count() - 1 streamInputTransitionSpec = streamInputTransitionSpecs[i%] streamInputSpec = streamInputTransitionSpec.inputSpec if ByteArraysMatch(serialStreamInput, streamInputSpec) then serialStreamInput.Clear() m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "serialBytes", port$ + " " + streamInputTransitionSpec.asciiSpec, "1") return m.ExecuteTransition(streamInputTransitionSpec.transition, stateData, "") end if next end if end if else if type(event) = "roIRDownEvent" then return m.RemoteDownEventHandler(event, stateData) else if type(event) = "roKeyboardPress" then return m.KeyboardPressEventHandler(event, stateData) else if type(event) = "roSyncManagerEvent" then synchronizeEvent$ = event.GetId() synchronizeEvents = m.synchronizeEvents if type(synchronizeEvents) = "roAssociativeArray" then if type(synchronizeEvents[synchronizeEvent$]) = "roAssociativeArray" or type(synchronizeEvents[""]) = "roAssociativeArray" then m.stateMachine.syncInfo = { } m.stateMachine.syncInfo.SyncDomain = event.GetDomain() m.stateMachine.syncInfo.SyncId = event.GetId() m.stateMachine.syncInfo.SyncIsoTimestamp = event.GetIsoTimestamp() m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "enhancedSynchronize", synchronizeEvent$, "1") if type(synchronizeEvents[synchronizeEvent$]) = "roAssociativeArray" return m.ExecuteTransition(m.synchronizeEvents[synchronizeEvent$], stateData, synchronizeEvent$) else ' Synchronising with a PlayFile state return m.ExecuteTransition(m.synchronizeEvents[""], stateData, synchronizeEvent$) end if end if end if ' Check user data to distinguish between presentation udp messages and bootstrap udp messages else if type(event) = "roDatagramEvent" and IsString(event.getUserData()) and event.GetUserData() = "receiver" then return m.DatagramEventHandler(event, stateData) else if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "BPControlDown" then bpIndex$ = event["ButtonPanelIndex"] bpIndex% = int(val(bpIndex$)) bpNum$ = event["ButtonNumber"] bpNum% = int(val(bpNum$)) m.bsp.diagnostics.PrintDebug("BP Press" + bpNum$ + " on button panel" + bpIndex$) bpEvents = m.bpEvents ' bpEvents["-1"] => any bp button currentBPEvent = bpEvents[bpIndex%] transition = currentBPEvent[bpNum$] if type(transition) <> "roAssociativeArray" then transition = currentBPEvent["-1"] end if if type(transition) = "roAssociativeArray" then payload$ = bpIndex$ + "-" + StripLeadingSpaces(stri(bpNum% + 1)) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "bpDown", bpIndex$ + " " + bpNum$, "1") return m.ExecuteTransition(transition, stateData, payload$) end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "bpDown", bpIndex$ + " " + bpNum$, "0") else if event["EventType"] = "GPIOControlDown" then gpioNum$ = event["ButtonNumber"] m.bsp.diagnostics.PrintDebug("Control Down " + gpioNum$) gpioEvents = m.gpioEvents if type(gpioEvents[gpioNum$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "gpioButton", gpioNum$, "1") return m.ExecuteTransition(gpioEvents[gpioNum$], stateData, "") end if else if event["EventType"] = "GPIOControlUp" then gpioNum$ = event["ButtonNumber"] m.bsp.diagnostics.PrintDebug("Control Up " + gpioNum$) gpioUpEvents = m.gpioUpEvents if type(gpioUpEvents[gpioNum$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "gpioButton", gpioNum$, "1") return m.ExecuteTransition(gpioUpEvents[gpioNum$], stateData, "") end if else if event["EventType"] = "SEND_ZONE_MESSAGE" then sendZoneMessageParameter$ = event["EventParameter"] m.bsp.diagnostics.PrintDebug("ZoneMessageEvent " + sendZoneMessageParameter$) if type(m.zoneMessageEvents) = "roAssociativeArray" then if type(m.zoneMessageEvents[sendZoneMessageParameter$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "sendZoneMessage", sendZoneMessageParameter$, "1") return m.ExecuteTransition(m.zoneMessageEvents[sendZoneMessageParameter$], stateData, "") else ' look for regular expression match with each of the possible zone message events for the current state for each sendZoneMessageSpec in m.zoneMessageEvents ' only look for regular expressions if spec includes wildcard if instr(1, sendZoneMessageSpec, "(.*)") > 0 then r = CreateObject("roRegEx", sendZoneMessageSpec, "i") if type(r) = "roRegex" then matches = r.match(sendZoneMessageParameter$) if matches.Count() > 0 then transition = m.zoneMessageEvents[sendZoneMessageSpec] m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "sendZoneMessage", sendZoneMessageParameter$, "1") if transition.assignInputToUserVariable then transition.AssignEventInputToUserVariable(m.bsp, sendZoneMessageParameter$) end if if matches.Count() > 1 and transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m.bsp, matches[1]) end if return m.ExecuteTransition(transition, stateData, sendZoneMessageParameter$) end if end if end if next end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "sendZoneMessage", sendZoneMessageParameter$, "0") else if event["EventType"] = "EVENT_PLUGIN_MESSAGE" then pluginName$ = event["PluginName"] pluginMessage$ = event["PluginMessage"] m.bsp.diagnostics.PrintDebug("PluginMessageEvent " + pluginName$ + " " + pluginMessage$) if pluginName$ = "initNode" then return m.WssEventHandler(event, stateData, pluginMessage$) end if key$ = pluginName$ + pluginMessage$ if type(m.pluginMessageEvents) = "roAssociativeArray" then if type(m.pluginMessageEvents[key$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "sendPluginMessage", key$, "1") return m.ExecuteTransition(m.pluginMessageEvents[key$], stateData, "") else key$ = pluginName$ + "" if type(m.pluginMessageEvents[key$]) = "roAssociativeArray" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "sendPluginMessage", key$, "1") return m.ExecuteTransition(m.pluginMessageEvents[key$], stateData, "") else for each pluginMessageEvent in m.pluginMessageEvents if instr(1, pluginMessageEvent, "(.*)") > 0 then r = CreateObject("roRegEx", pluginMessageEvent, "i") if type(r) = "roRegex" then key$ = pluginName$ + pluginMessage$ matches = r.match(key$) if matches.Count() > 0 then transition = m.pluginMessageEvents[pluginMessageEvent] m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "sendPluginMessage", key$, "1") if transition.assignInputToUserVariable then transition.AssignEventInputToUserVariable(m.bsp, event.PluginMessage) end if if matches.Count() > 1 and transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m.bsp, matches[1]) end if return m.ExecuteTransition(transition, stateData, event.PluginMessage) end if end if end if next end if end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "sendPluginMessage", key$, "0") else if event["EventType"] = "INTERNAL_SYNC_PRELOAD" then internalSyncParameter$ = event["EventParameter"] m.bsp.diagnostics.PrintDebug("InternalSyncPreloadEvent " + internalSyncParameter$) actedOn$ = "0" if type(m.internalSynchronizeEvents) = "roAssociativeArray" then if type(m.internalSynchronizeEvents[internalSyncParameter$]) = "roAssociativeArray" then nextState$ = m.internalSynchronizeEvents[internalSyncParameter$].targetMediaState$ m.stateMachine.preloadState = m.stateMachine.stateTable[nextState$] m.stateMachine.preloadState.PreloadItem() actedOn$ = "1" end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "internalSyncPreload", internalSyncParameter$, actedOn$) return "HANDLED" else if event["EventType"] = "INTERNAL_SYNC_MASTER_PRELOAD" then internalSyncParameter$ = event["EventParameter"] m.bsp.diagnostics.PrintDebug("InternalSyncMasterPreload " + internalSyncParameter$) actedOn$ = "0" if type(m.internalSynchronizeEventsMaster) = "roAssociativeArray" then if type(m.internalSynchronizeEventsMaster[internalSyncParameter$]) = "roAssociativeArray" then m.bsp.diagnostics.PrintDebug("post play message with parameter " + internalSyncParameter$) internalSyncPlay = { } internalSyncPlay["EventType"] = "INTERNAL_SYNC_PLAY" internalSyncPlay["EventParameter"] = internalSyncParameter$ m.stateMachine.msgPort.PostMessage(internalSyncPlay) actedOn$ = "1" end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "internalSyncMasterPreload", internalSyncParameter$, actedOn$) return "HANDLED" else if event["EventType"] = "INTERNAL_SYNC_PLAY" then internalSyncParameter$ = event["EventParameter"] m.bsp.diagnostics.PrintDebug("InternalSyncPlayEvent " + internalSyncParameter$) if type(m.internalSynchronizeEventsMaster) = "roAssociativeArray" then if type(m.internalSynchronizeEventsMaster[internalSyncParameter$]) = "roAssociativeArray" then m.bsp.diagnostics.PrintDebug("master play event received - prepare to return") m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "internalSyncMasterPlay", internalSyncParameter$, "1") return m.ExecuteTransition(m.internalSynchronizeEventsMaster[internalSyncParameter$], stateData, "") end if end if if type(m.internalSynchronizeEvents) = "roAssociativeArray" then if type(m.internalSynchronizeEvents[internalSyncParameter$]) = "roAssociativeArray" then m.bsp.diagnostics.PrintDebug("slave play event received - prepare to return") m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "internalSyncSlavePlay", internalSyncParameter$, "1") return m.ExecuteTransition(m.internalSynchronizeEvents[internalSyncParameter$], stateData, "") end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "internalSyncPlay", internalSyncParameter$, "0") else if event["EventType"] = "PREPARE_FOR_RESTART" then m.bsp.diagnostics.PrintDebug(m.id$ + " - PREPARE_FOR_RESTART") if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer = invalid end if if IsAudioPlayer(m.stateMachine.audioPlayer) then m.stateMachine.audioPlayer = invalid end if if type(m.stateMachine.imagePlayer) = "roImageWidget" then m.stateMachine.imagePlayer = invalid end if return "HANDLED" end if end if else if (type(event) = "roVideoEvent" and type(m.stateMachine.videoPlayer) = "roVideoPlayer" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity()) or (type(event) = "roAudioEvent" and IsAudioPlayer(m.stateMachine.audioPlayer) and event.GetSourceIdentity() = m.stateMachine.audioPlayer.GetIdentity()) then if event.GetInt() = 8 then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "0") else if event.GetInt() = 12 then ' m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "videoTimeCode", "", "0") m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "timeCode", "", "0") end if else if type(event) = "roBmapMessageEvent" then resp = m.processBMapMessageEvent(event, stateData) if resp <> "" then return resp endif end if stateData.nextState = m.superState return "SUPER" end function Function getBmapOperator(bmapFunction as object, bmapOperatorIndex as integer) as object bmapOperators = bmapFunction.Operators for each bmapOperator in bmapOperators bmapOperatorValue = GetBmapOperatorValue(bmapOperator) if bmapOperatorValue = bmapOperatorIndex then return bmapOperator endif next end function Function getBmapFunction(functionBlock as object, bmapFunctionIndex as integer) as object bmapFunctions = functionBlock.functions for each bmapFunction in bmapFunctions if bmapFunction.Value = bmapFunctionIndex then return bmapFunction endif next end function Function getFunctionBlock(bmapCommunicationSpec as object, functionBlockIndex as integer) as object functionBlocks = bmapCommunicationSpec.functionBlocks for each functionBlock in functionBlocks if functionBlock.value = functionBlockIndex then return functionBlock endif next return invalid end function Function MatchBMapHexInputEvent(port$ as string, bmapMessage$ as string, stateData as object) as string eventMatchFound = false specifiedConnector$ = m.bsp.GetSpecifiedConnector(port$) if type(m.bmapHexInputEvents[specifiedConnector$]) = "roAssociativeArray" then transition = m.bmapHexInputEvents[specifiedConnector$][bmapMessage$] if type(transition) = "roAssociativeArray" then eventMatchFound = true else ' look for regular expression match if spec includes wildcard for each bmapHexInputSpec in m.bmapHexInputEvents[specifiedConnector$] if instr(1, bmapHexInputSpec, "(.*)") > 0 then r = CreateObject("roRegEx", bmapHexInputSpec, "i") if type(r) = "roRegex" then matches = r.match(bmapMessage$) if matches.Count() > 0 then transition = m.bmapHexInputEvents[specifiedConnector$][bmapHexInputSpec] if matches.Count() > 1 and transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m, matches[1]) end if eventMatchFound = true endif endif endif next endif endif if eventMatchFound then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "bmap", bmapMessage$, "1") if transition.assignInputToUserVariable then transition.AssignEventInputToUserVariable(m.bsp, bmapMessage$) end if return m.ExecuteTransition(transition, stateData, bmapMessage$) endif return "" end function Function GetBMapFieldLength(fieldType As String) As Integer if fieldType = "uint8" or fieldType = "int8" then fieldLength = 2 else if fieldType = "uint16" or fieldType = "int16" then fieldLength = 4 else if fieldType = "uint32" or fieldType = "int32" then fieldLength = 8 else if fieldType = "uint64" or fieldType = "int64" then fieldLength = 16 else stop endif return fieldLength End Function Function GetFieldValueAsStr(fieldValue) as String if type(fieldValue)= "Invalid" then return "" endif if (IsString(fieldValue)) then return fieldValue endif return StripLeadingSpaces(stri(fieldValue)) end function Function MatchBMapEvent(event as object, port$ as string, bmapMessage$ as string, stateData as object) as string if type(m.bmapEvents) = "roArray" and m.bmapEvents.Count() > 0 then tmpResponse = event.GetMessage() ' make deep copy of event.GetMessage() as the Shift operator is destructive hs = tmpResponse.ToHexString() newByteArray = CreateObject("roByteArray") newByteArray.FromHexString(hs) response = newByteArray bmapFunctionBlock = response.Shift() bmapFunctionIndex = response.Shift() bmapOperatorIndex = response.Shift() bmapPayloadLength = response.Shift() bmapPayload = response.ToHexString() m.bsp.diagnostics.PrintDebug("bmapEvent") m.bsp.diagnostics.PrintDebug(stri(bmapFunctionBlock)) m.bsp.diagnostics.PrintDebug(stri(bmapFunctionIndex)) m.bsp.diagnostics.PrintDebug(stri(bmapOperatorIndex)) m.bsp.diagnostics.PrintDebug(stri(bmapPayloadLength)) specifiedConnector$ = m.bsp.GetSpecifiedConnector(port$) boseProduct = m.bsp.sign.boseProductsByConnector[specifiedConnector$] bmapCommunicationSpec = boseProduct.bmapCommunicationSpec functionBlock = getFunctionBlock(bmapCommunicationSpec, bmapFunctionBlock) if type(functionBlock) = "roAssociativeArray" then bmapFunction = getBmapFunction(functionBlock, bmapFunctionIndex) if type(bmapFunction) = "roAssociativeArray" then bmapOperator = getBmapOperator(bmapFunction, bmapOperatorIndex) if type(bmapOperator) = "roAssociativeArray" then bmapFields = bmapOperator.fields for each specifiedBmapEvent in m.bmapEvents if lcase(specifiedBmapEvent.port) = lcase(specifiedConnector$) then if lcase(specifiedBmapEvent.functionBlockName) = lcase(functionBlock.Name) then if lcase(specifiedBmapEvent.functionName) = lcase(bmapFunction.Name) then if lcase(specifiedBmapEvent.operatorName) = lcase(bmapOperator.Operator) then ' only support matching to a single field in an event bmapEventMatchFound = false matchedPayload = "" if bmapFields = invalid and specifiedBmapEvent.fieldName = "" then bmapEventMatchFound = true else if bmapFields <> invalid and specifiedBmapEvent.fieldName <> "" then payloadOffsetIndex = 0 payloadBA = CreateObject("roByteArray") payloadBA.FromHexString(bmapPayload) payloadAsNumber = 0 for i% = 0 to payloadBA.Count() - 1 payloadAsNumber = (payloadAsNumber * 256) + payloadBA[i%] next for each bmapField in bmapFields bmapFieldName = bmapField.Name if lcase(bmapFieldName) = lcase(specifiedBmapEvent.fieldName) then if type(bmapField.BitFields) = "roArray" and bmapField.BitFields.Count() > 0 then shiftCount = 0 for each bmapBitField in bmapField.BitFields if bmapBitField.Name = specifiedBmapEvent.bitFieldName then maskValue = ShiftLeft(2, bmapBitField.NumBits) - 1 maskedPayload = payloadAsNumber and maskValue if instr(1, specifiedBmapEvent.fieldValue, "(.*)") then ' wildcard specified ' for bmapBitFields, only support wildcards against entire field. ' always match if the event includes the specified bitfield transition = specifiedBmapEvent.transition if transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m, stri(maskedPayload)) end if bmapEventMatchFound = true matchedPayload = stri(payloadAsNumber) else fieldValueStr = specifiedBmapEvent.fieldValue fieldValueBA = CreateObject("roByteArray") if (len(fieldValueStr) and 1) = 1 then fieldValueStr = "0" + fieldValueStr endif fieldValueBA.FromHexString(fieldValueStr) ' TEDTODOBMAP - field value Num > 1 byte? fieldValueNum = fieldValueBA[0] if maskedPayload = fieldValueNum then bmapEventMatchFound = true matchedPayload = specifiedBmapEvent.fieldValue endif endif exit for endif payloadAsNumber = ShiftRight(payloadAsNumber, bmapBitField.NumBits) next ' look for regular expression match if spec includes wildcard else if instr(1, specifiedBmapEvent.fieldValue, "(.*)") > 0 then r = CreateObject("roRegEx", specifiedBmapEvent.fieldValue, "i") if type(r) = "roRegex" then if isBoolean(bmapField.variableLength) and bmapField.variableLength then fieldPayload = bmapPayload.mid(payloadOffsetIndex) else fieldLength = GetBMapFieldLength(bmapField.Type) fieldPayload = bmapPayload.mid(fieldLength) endif ' if units are ascii, attempt wildcard match on ascii, not hex if bmapField.Units = "ASCII" then ' convert payload field from hex to ascii ba = CreateObject("roByteArray") ba.FromHexString(fieldPayload) fieldPayload = ba.ToAsciiString() endif matches = r.match(fieldPayload) if matches.Count() > 0 then transition = specifiedBmapEvent.transition if matches.Count() > 1 and transition.assignWildcardToUserVariable then transition.AssignWildcardInputToUserVariable(m, matches[1]) end if bmapEventMatchFound = true matchedPayload = fieldPayload exit for endif endif else if (bmapField.Units = "ASCII") then ' convert specified value from ascii to a hex string for comparison with payload ba = CreateObject("roByteArray") ba.FromAsciiString(specifiedBmapEvent.fieldValue) fieldValueAsHex = ba.ToHexString() if isBoolean(bmapField.variableLength) and bmapField.variableLength then fieldPayload = bmapPayload.mid(payloadOffsetIndex) else fieldLength = GetBMapFieldLength(bmapField.Type) fieldPayload = bmapPayload.mid(fieldLength) endif if fieldPayload = fieldValueAsHex then stop ' TODO - unable to test due to lack of appropriate test data bmapEventMatchFound = true matchedPayload = specifiedBmapEvent.fieldValue exit for endif else if bmapField.Type = "uint8" or bmapField.Type = "int8" then fieldPayload = bmapPayload.mid(payloadOffsetIndex, 2) if int(val(fieldPayload)) = int(val(specifiedBmapEvent.fieldValue)) then bmapEventMatchFound = true matchedPayload = bmapPayload exit for endif else if bmapField.Type = "uint16" or bmapField.Type = "int16" then stop ' TODO - unable to test due to lack of appropriate test data fieldPayload = bmapPayload.mid(payloadOffsetIndex, 4) if int(val(fieldPayload)) = int(val(specifiedBmapEvent.fieldValue)) then bmapEventMatchFound = true matchedPayload = bmapPayload exit for endif else if bmapField.Type = "uint32" or bmapField.Type = "int32" then stop ' TODO - unable to test due to lack of appropriate test data fieldPayload = bmapPayload.mid(payloadOffsetIndex, 8) if int(val(fieldPayload)) = int(val(specifiedBmapEvent.fieldValue)) then bmapEventMatchFound = true matchedPayload = bmapPayload exit for endif else stop ' TODO - unable to test due to lack of appropriate test data endif endif ' get offset to next field in payload payloadMultiplier = 1 if type(bmapField.count) = "Integer" then payloadMultiplier = bmapField.count endif if bmapField.Type = "uint8" or bmapField.Type = "int8" then fieldSize = 2 else if bmapField.Type = "uint16" or bmapField.Type = "int16" then fieldSize = 4 else if bmapField.Type = "uint32" or bmapField.Type = "int32" then fieldSize = 8 endif payloadOffsetIndex = payloadOffsetIndex + (fieldSize * payloadMultiplier) next endif if bmapEventMatchFound then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "bmap", bmapMessage$, "1") transition = specifiedBmapEvent.transition if transition.assignInputToUserVariable then transition.AssignEventInputToUserVariable(m.bsp, matchedPayload) end if return m.ExecuteTransition(transition, stateData, bmapMessage$) endif endif endif endif endif next endif endif endif endif return "" end function Function processBMapMessageEvent(event as object, stateData as object) as string port$ = event.GetUserData() bmapMessage$ = event.GetMessage().ToHexString() m.bsp.diagnostics.PrintDebug(bmapMessage$) ' check for hex input events first result = m.MatchBMapHexInputEvent(port$, bmapMessage$, stateData) if result <> "" then return result endif ' no match found with a hex input event - look for match with standard bmap input event result = m.MatchBMapEvent(event, port$, bmapMessage$, stateData) if result <> "" then return result endif return "" End function Sub dumpJsonBody(eddieDumpFile as object, jsonBody as object) for each propertyName in jsonBody eddieDumpFile.sendLine(propertyName) propertyValue = jsonBody[propertyName] if type(propertyValue) <> "roAssociativeArray" then eddieDumpFile.sendLine(propertyValue) else dumpJsonBody(eddieDumpFile, propertyValue) end if next end sub ' m is bsp Sub WaitForSyncResponse(parameter$ as string) udpReceiver = CreateObject("roDatagramReceiver", m.udpReceivePort) ' Set user data to distinguish between presentation udp messages and bootstrap udp messages udpReceiver.SetUserData("receiver") msgPort = CreateObject("roMessagePort") udpReceiver.SetPort(msgPort) m.udpSender.Send("ply-" + parameter$) while true msg = wait(50, msgPort) if type(msg) = "roDatagramEvent" or msg = invalid then udpReceiver = invalid return end if end while end sub Function EventMatchesWildcard(eventSpec as string, eventValue as string) as object ' look for regular expression match if spec includes wildcard if instr(1, eventSpec, "(.*)") > 0 then r = CreateObject("roRegEx", eventSpec, "i") if type(r) = "roRegex" then return r.match(eventValue) end if end if return [] end function Function EventActiveToday(currentDateTime as object, daysOfWeek% as integer) as boolean bitwiseDaysOfWeek% = daysOfWeek% currentDayOfWeek = currentDateTime.GetDayOfWeek() bitDayOfWeek% = 2 ^ currentDayOfWeek if (bitwiseDaysOfWeek% and bitDayOfWeek%) <> 0 then return true end if return false end function Function TimeWithinWindow(currentTime% as integer, startTime% as integer, endTime% as integer) as boolean withinWindow = false if startTime% = endTime% then withinWindow = true else if startTime% < endTime% then if currentTime% >= startTime% and currentTime% < endTime% then withinWindow = true end if else if currentTime% < endTime% or currentTime% > startTime% then withinWindow = true end if return withinWindow end function Function IsTimeoutInFuture(timeoutDateTime as object) as boolean systemTime = CreateObject("roSystemTime") currentDateTime = systemTime.GetLocalDateTime() systemTime = invalid return currentDateTime.GetString() < timeoutDateTime.GetString() end function Sub LaunchTimeClockEventTimer(state as object, timeClockEvent as object) if type(timeClockEvent.timer) = "roTimer" then timeClockEvent.timer.Stop() timeClockEvent.timer = invalid end if timer = CreateObject("roTimer") if type(timeClockEvent.timeClockEventDateTime) = "roDateTime" then dateTime = timeClockEvent.timeClockEventDateTime ' only set timer if it is in the future if not IsTimeoutInFuture(dateTime) then return end if state.bsp.diagnostics.PrintDebug("Set timeout to " + dateTime.GetString()) timer.SetDateTime(dateTime) else if type(timeClockEvent.userVariableName$) = "roString" then if type(timeClockEvent.userVariable) = "roAssociativeArray" then dateTime$ = timeClockEvent.userVariable.GetCurrentValue() dateTime = FixDateTime(dateTime$) if type(dateTime) = "roDateTime" then ' only set timer if it is in the future if not IsTimeoutInFuture(dateTime) then print "Specified timer is in the past, don't set it: timer time is ";dateTime.GetString() return end if state.bsp.diagnostics.PrintDebug("Set timeout to " + dateTime.GetString()) timer.SetDateTime(dateTime) else state.bsp.diagnostics.PrintDebug("Timeout specification " + dateTime$ + " is invalid") state.bsp.logging.WriteDiagnosticLogEntry(state.bsp.diagnosticCodes.EVENT_INVALID_DATE_TIME_SPEC, dateTime$) end if end if else if type(timeClockEvent.timeClockDaily%) = "roInt" then hours% = timeClockEvent.timeClockDaily% / 60 minutes% = timeClockEvent.timeClockDaily% - (hours% * 60) timer.SetTime(hours%, minutes%, 0, 0) timer.SetDate( - 1, - 1, - 1) else systemTime = CreateObject("roSystemTime") currentDateTime = systemTime.GetLocalDateTime() ' units in seconds rather than minutes? currentTime% = currentDateTime.GetHour() * 60 + currentDateTime.GetMinute() startTime% = timeClockEvent.timeClockPeriodicStartTime% endTime% = timeClockEvent.timeClockPeriodicEndTime% intervalTime% = timeClockEvent.timeClockPeriodicInterval% withinWindow = TimeWithinWindow(currentTime%, startTime%, endTime%) if not withinWindow then ' set timer for next start time hours% = startTime% / 60 minutes% = startTime% - (hours% * 60) timer.SetTime(hours%, minutes%, 0, 0) timer.SetDate( - 1, - 1, - 1) else if currentTime% > startTime% then minutesSinceStartTime% = currentTime% - startTime% else minutesSinceStartTime% = currentTime% + (24 * 60 - startTime%) end if ' elapsed intervals since the start time? numberOfElapsedIntervals% = minutesSinceStartTime% / intervalTime% numberOfIntervalsUntilNextTimeout% = numberOfElapsedIntervals% + 1 ' determine time for next timeout nextTimeoutTime% = startTime% + (numberOfIntervalsUntilNextTimeout% * intervalTime%) ' check for wrap to next day if nextTimeoutTime% > (24 * 60) then nextTimeoutTime% = nextTimeoutTime% - (24 * 60) end if ' set timer for next start time hours% = nextTimeoutTime% / 60 minutes% = nextTimeoutTime% - (hours% * 60) timer.SetTime(hours%, minutes%, 0, 0) timer.SetDate( - 1, - 1, - 1) state.bsp.diagnostics.PrintDebug("Set timeout to " + stri(hours%) + " hours, " + stri(minutes%) + " minutes.") end if systemTime = invalid end if timer.SetPort(state.stateMachine.msgPort) timer.Start() timeClockEvent.timer = timer end sub Sub LaunchTimer() if type(m.mstimeoutEvent) = "roAssociativeArray" then timer = CreateObject("roTimer") timer.SetPort(m.stateMachine.msgPort) timer.SetElapsed(0, m.mstimeoutValue%) timer.Start() m.mstimeoutTimer = timer end if if type(m.timeClockEvents) = "roArray" then for each timeClockEvent in m.timeClockEvents LaunchTimeClockEventTimer(m, timeClockEvent) next end if end sub Sub PreloadItem() zone = m.stateMachine if m.type$ = "mediaList" or m.type$ = "mediaRSS" or m.type$ = "signChannel" or m.type$ = "liveText" then zone.preloadedStateName$ = "" return end if if m.type$ = "playFile" then fileTableEntry = m.filesTable.Lookup(m.payload$) fileName$ = fileTableEntry.fileName$ fileType$ = fileTableEntry.fileType$ if fileType$ = "image" then imageItem = { } imageItem.fileName$ = fileName$ imageItem.isEncrypted = false else if fileType$ = "video" then videoItem = { } videoItem.fileName$ = fileName$ videoItem.probeData = fileTableEntry.probeData videoItem.isEncrypted = false end if else if type(m.imageItem) = "roAssociativeArray" then imageItem = { } imageItem.fileName$ = m.imageItem.fileName$ else if type(m.videoItem) = "roAssociativeArray" then videoItem = { } videoItem.fileName$ = m.videoItem.fileName$ if type(m.videoItem.probeData) = "roString" then videoItem.probeData = m.videoItem.probeData else videoItem.probeData = invalid end if end if if type(imageItem) = "roAssociativeArray" then imageItemFilePath$ = GetPoolFilePath(m.bsp.assetPoolFiles, imageItem.fileName$) isEncrypted = m.bsp.encryptionByFile.DoesExist(imageItem.fileName$) 'isEncrypted = m.bsp.contentEncrypted if isEncrypted then aa = { } aa.AddReplace("Filename", imageItemFilePath$) aa.AddReplace("EncryptionAlgorithm", "AesCtrHmac") aa.AddReplace("EncryptionKey", imageItem.fileName$) zone.imagePlayer.PreloadFile(aa) else zone.imagePlayer.PreloadFile(imageItemFilePath$) end if zone.preloadedStateName$ = m.name$ m.bsp.diagnostics.PrintDebug("Preloaded file in PreloadItem: " + imageItem.fileName$ + ", " + imageItemFilePath$) else if type(videoItem) = "roAssociativeArray" then videoItemFilePath$ = GetPoolFilePath(m.bsp.assetPoolFiles, videoItem.fileName$) aa = { } aa.AddReplace("Filename", videoItemFilePath$) if type(videoItem.probeData) = "roString" then m.bsp.diagnostics.PrintDebug("PreloadItem: probeData = " + videoItem.probeData) aa.AddReplace("ProbeString", videoItem.probeData) end if isEncrypted = m.bsp.encryptionByFile.DoesExist(videoItem.fileName$) 'isEncrypted = m.bsp.contentEncrypted if isEncrypted then aa.AddReplace("EncryptionAlgorithm", "AesCtrHmac") aa.AddReplace("EncryptionKey", videoItem.fileName$) end if ok = zone.videoPlayer.PreloadFile(aa) zone.preloadedStateName$ = m.name$ m.bsp.diagnostics.PrintDebug("Preloaded file in PreloadItem: " + videoItem.fileName$) end if end sub 'endregion 'region Images State Machine ' ************************************************* ' ' Images State Machine ' ' ************************************************* Function newImagesZoneHSM(bsp as object, zoneDescription as object) as object zoneHSM = newHSM() zoneHSM.ConstructorHandler = ImageZoneConstructor zoneHSM.InitialPseudostateHandler = ImageZoneGetInitialState newZoneCommon(bsp, zoneDescription, zoneHSM) zoneHSM.imageMode% = zoneDescription.imageMode% zoneHSM.numImageItems% = 0 return zoneHSM end function Function IsPortraitBottomLeft(portraitSpec$) as boolean if lcase(portraitSpec$) = "portrait" or lcase(portraitSpec$) = "portraitbottomleft" then return true end if return false end function Function IsPortraitBottomRight(portraitSpec$) as boolean if lcase(portraitSpec$) = "portraitbottomright" then return true end if return false end function Sub ImageZoneConstructor() m.InitializeZoneCommon(m.bsp.msgPort) zoneHSM = m if zoneHSM.numImageItems% > 0 then imagePlayer = CreateObject("roImageWidget", zoneHSM.rectangle) if CanRotateByScreen(m.bsp.sign, {}) then ' no need to rotate per zone if already rotated by screen else if IsPortraitBottomLeft(m.bsp.sign.monitorOrientation) then imagePlayer.SetTransform("rot90") else if IsPortraitBottomRight(m.bsp.sign.monitorOrientation) then imagePlayer.SetTransform("rot270") end if zoneHSM.imagePlayer = imagePlayer ' initialize image player parameters imagePlayer.SetDefaultMode(zoneHSM.imageMode%) else zoneHSM.imagePlayer = invalid end if m.CreateObjects() m.activeState = m.playlist.firstState if type(m.playlist.firstState) = "roAssociativeArray" then m.previousStateName$ = m.playlist.firstState.id$ else m.previousStateName$ = "" end if end sub Function ImageZoneGetInitialState() as object return m.activeState end function Function STDisplayingImageEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.DisplayImage("image") return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else return m.MediaItemEventHandler(event, stateData) end if end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function 'endregion 'region Enhanced Audio State Machine ' ************************************************* ' ' Enhanced Audio State Machine ' ' ************************************************* Function newEnhancedAudioZoneHSM(bsp as object, zoneDescription as object) as object zoneHSM = newHSM() zoneHSM.ConstructorHandler = EnhancedAudioZoneConstructor zoneHSM.InitializeAudioZoneCommon = InitializeAudioZoneCommon zoneHSM.InitialPseudostateHandler = EnhancedAudioZoneGetInitialState newZoneCommon(bsp, zoneDescription, zoneHSM) newAudioZoneCommon(zoneDescription, zoneHSM) zoneHSM.fadeLength% = zoneDescription.fadeLength return zoneHSM end function Sub EnhancedAudioZoneConstructor() audioPlayer = CreateObject("roAudioPlayerMx") m.InitializeAudioZoneCommon(audioPlayer) end sub Function EnhancedAudioZoneGetInitialState() as object return m.activeState end function 'endregion 'region Audio State Machine ' ************************************************* ' ' Audio State Machine ' ' ************************************************* Function newAudioZoneHSM(bsp as object, zoneDescription as object) as object zoneHSM = newHSM() zoneHSM.InitializeAudioZoneCommon = InitializeAudioZoneCommon zoneHSM.ConstructorHandler = AudioZoneConstructor zoneHSM.InitialPseudostateHandler = AudioZoneGetInitialState newZoneCommon(bsp, zoneDescription, zoneHSM) newAudioZoneCommon(zoneDescription, zoneHSM) return zoneHSM end function ' TODO - is there any reason to go from json to zoneDescription to zoneHSM parameters now that XML has been eliminated? Sub newAudioZoneCommon(zoneDescription as object, zoneHSM as object) zoneHSM.audioMixMode$ = zoneDescription.audioMixMode$ zoneHSM.analogOutput$ = zoneDescription.analogOutput$ zoneHSM.hdmiOutput$ = zoneDescription.hdmiOutput$ zoneHSM.hdmi1Output$ = zoneDescription.hdmi1Output$ zoneHSM.hdmi2Output$ = zoneDescription.hdmi2Output$ zoneHSM.hdmi3Output$ = zoneDescription.hdmi3Output$ zoneHSM.hdmi4Output$ = zoneDescription.hdmi4Output$ zoneHSM.spdifOutput$ = zoneDescription.spdifOutput$ zoneHSM.usbOutputA$ = zoneDescription.usbOutputA$ zoneHSM.usbOutputB$ = zoneDescription.usbOutputB$ zoneHSM.usbOutputTypeA$ = zoneDescription.usbOutputTypeA$ zoneHSM.usbOutputTypeC$ = zoneDescription.usbOutputTypeC$ zoneHSM.usbOutput700_1$ = zoneDescription.usbOutput700_1$ zoneHSM.usbOutput700_2$ = zoneDescription.usbOutput700_2$ zoneHSM.usbOutput700_3$ = zoneDescription.usbOutput700_3$ zoneHSM.usbOutput700_4$ = zoneDescription.usbOutput700_4$ zoneHSM.usbOutput700_5$ = zoneDescription.usbOutput700_5$ zoneHSM.usbOutput700_6$ = zoneDescription.usbOutput700_6$ zoneHSM.usbOutput700_7$ = zoneDescription.usbOutput700_7$ zoneHSM.usbOutput_1$ = zoneDescription.usbOutput_1$ zoneHSM.usbOutput_2$ = zoneDescription.usbOutput_2$ zoneHSM.usbOutput_3$ = zoneDescription.usbOutput_3$ zoneHSM.usbOutput_4$ = zoneDescription.usbOutput_4$ zoneHSM.usbOutput_5$ = zoneDescription.usbOutput_5$ zoneHSM.usbOutput_6$ = zoneDescription.usbOutput_6$ zoneHSM.minimumVolume% = zoneDescription.minimumVolume% zoneHSM.maximumVolume% = zoneDescription.maximumVolume% zoneHSM.hasMultiScreenOutputs = zoneDescription.hasMultiScreenOutputs zoneHSM.initialAudioVolume% = zoneDescription.initialAudioVolume% end sub Sub InitializeAudioZoneCommon(audioPlayer as object) m.InitializeZoneCommon(m.bsp.msgPort) zoneHSM = m ' create players zoneHSM.audioVolume% = zoneHSM.initialAudioVolume% zoneHSM.audioChannelVolumes = CreateObject("roArray", 6, true) for i% = 0 to 5 zoneHSM.audioChannelVolumes[i%] = zoneHSM.audioVolume% next ' initialize audio player parameters audioPlayer.SetPort(zoneHSM.msgPort) zoneHSM.audioPlayer = audioPlayer m.SetAudioOutputAndMode(audioPlayer) audioPlayer.SetVolume(zoneHSM.audioVolume%) audioPlayer.SetLoopMode(false) zoneHSM.ConfigureAudioResources() zoneHSM.audioPlayerAudioSettings = { } m.bsp.SetAudioVolumeLimits(zoneHSM, zoneHSM.audioPlayerAudioSettings) m.activeState = m.playlist.firstState if type(m.playlist.firstState) = "roAssociativeArray" then m.previousStateName$ = m.playlist.firstState.id$ else m.previousStateName$ = "" end if m.CreateObjects() end sub Sub AudioZoneConstructor() audioPlayer = CreateObject("roAudioPlayer") m.InitializeAudioZoneCommon(audioPlayer) end sub Function AudioZoneGetInitialState() as object return m.activeState end function 'endregion 'region Video State Machine ' ************************************************* ' ' Video State Machine ' ' ************************************************* Function newVideoZoneHSM(bsp as object, zoneDescription as object) as object zoneHSM = newHSM() zoneHSM.InitializeVideoZoneObjects = InitializeVideoZoneObjects zoneHSM.ConstructorHandler = VideoZoneConstructor zoneHSM.InitialPseudostateHandler = VideoZoneGetInitialState newZoneCommon(bsp, zoneDescription, zoneHSM) ' zone.properties zoneHSM.viewMode% = zoneDescription.viewMode% newAudioZoneCommon(zoneDescription, zoneHSM) zoneHSM.initialVideoVolume% = zoneDescription.initialVideoVolume% zoneHSM.zOrderFront = zoneDescription.zOrderFront return zoneHSM end function Sub SetAudioOutputAndMode(player as object) pcm = CreateObject("roArray", 1, true) compressed = CreateObject("roArray", 1, true) multichannel = CreateObject("roArray", 1, true) analogAudioOutput = CreateObject("roAudioOutput", "Analog:1") if m.hasMultiScreenOutputs = true then hdmi1AudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI:1")) hdmi2AudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI:2")) hdmi3AudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI:3")) hdmi4AudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI:4")) else hdmiAudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector("HDMI")) end if spdifAudioOutput = CreateObject("roAudioOutput", "SPDIF") if lcase(m.analogOutput$) <> "none" and lcase(m.analogOutput$) <> "multichannel" then pcm.push(analogAudioOutput) end if if lcase(m.analogOutput$) = "multichannel" then multichannel.push(analogAudioOutput) end if if m.hasMultiScreenOutputs = true then AddAudioOutputByType(m.hdmi1Output$, hdmi1AudioOutput, compressed, pcm) AddAudioOutputByType(m.hdmi2Output$, hdmi2AudioOutput, compressed, pcm) AddAudioOutputByType(m.hdmi3Output$, hdmi3AudioOutput, compressed, pcm) AddAudioOutputByType(m.hdmi4Output$, hdmi4AudioOutput, compressed, pcm) else AddAudioOutputByType(m.hdmiOutput$, hdmiAudioOutput, compressed, pcm) end if AddAudioOutputByType(m.spdifOutput$, spdifAudioOutput, compressed, pcm) gaa = GetGlobalAA() for each runtimeUsbConnectorName in gaa.usbConnectorNameToUsbSpec usbConnectorName = m.bsp.GetSpecifiedConnector(runtimeUsbConnectorName) usbSpec = gaa.usbConnectorNameToUsbSpec.Lookup(runtimeUsbConnectorName) if usbSpec.audioOutputSpec <> "" then usbAudioOutput = CreateObject("roAudioOutput", GetAudioOutputConnector(usbSpec.audioOutputSpec)) if usbConnectorName = "usbTypeA" then spec$ = m.usbOutputTypeA$ else if usbConnectorName = "usbTypeC" then spec$ = m.usbOutputTypeC$ else if usbConnectorName = "usb700_1" then spec$ = m.usbOutput700_1$ else if usbConnectorName = "usb700_2" then spec$ = m.usbOutput700_2$ else if usbConnectorName = "usb700_3" then spec$ = m.usbOutput700_3$ else if usbConnectorName = "usb700_4" then spec$ = m.usbOutput700_4$ else if usbConnectorName = "usb700_5" then spec$ = m.usbOutput700_5$ else if usbConnectorName = "usb700_6" then spec$ = m.usbOutput700_6$ else if usbConnectorName = "usb700_7" then spec$ = m.usbOutput700_7$ else if usbConnectorName = "usb_1" then spec$ = m.usbOutput_1$ else if usbConnectorName = "usb_2" then spec$ = m.usbOutput_2$ else if usbConnectorName = "usb_3" then spec$ = m.usbOutput_3$ else if usbConnectorName = "usb_4" then spec$ = m.usbOutput_4$ else if usbConnectorName = "usb_5" then spec$ = m.usbOutput_5$ else if usbConnectorName = "usb_6" then spec$ = m.usbOutput_6$ else stop end if if type(usbAudioOutput) = "roAudioOutput" then if lcase(spec$) = "pcm" then pcm.push(usbAudioOutput) else if lcase(spec$) = "multichannel" then multichannel.push(usbAudioOutput) end if end if endif next if pcm.Count() = 0 then noPCMAudioOutput = CreateObject("roAudioOutput", "none") pcm.push(noPCMAudioOutput) end if if compressed.Count() = 0 then noCompressedAudioOutput = CreateObject("roAudioOutput", "none") compressed.push(noCompressedAudioOutput) end if if multichannel.Count() = 0 then noMultichannelAudioOutput = CreateObject("roAudioOutput", "none") multichannel.push(noMultichannelAudioOutput) end if player.SetPcmAudioOutputs(pcm) player.SetCompressedAudioOutputs(compressed) player.SetMultichannelAudioOutputs(multichannel) if lcase(m.audioMixMode$) = "passthrough" then player.SetAudioMode(0) else if lcase(m.audioMixMode$) = "left" then player.SetAudioMode(3) else if lcase(m.audioMixMode$) = "right" then player.SetAudioMode(4) else player.SetAudioMode(1) end if end sub Sub AddAudioOutputByType(outputType$ as string, output as object, compressed as object, pcm as object) if outputType$ = "" or type(output) <> "roAudioOutput" then return if lcase(outputType$) = "passthrough" then compressed.push(output) else if lcase(outputType$) <> "none" then pcm.push(output) end if end sub Sub InitializeVideoZoneObjects() m.InitializeZoneCommon(m.bsp.msgPort) zoneHSM = m ' create players ' reclaim memory (destroy any leaked video players) RunGarbageCollector() videoPlayer = CreateObject("roVideoPlayer") if type(videoPlayer) <> "roVideoPlayer" then print "videoPlayer creation failed" : stop if CanRotateByScreen(m.bsp.sign, {}) then ' no need to rotate per zone if already rotated by screen else if IsPortraitBottomLeft(m.bsp.sign.monitorOrientation) then videoPlayer.SetTransform("rot90") else if IsPortraitBottomRight(m.bsp.sign.monitorOrientation) then videoPlayer.SetTransform("rot270") end if videoPlayer.SetRectangle(zoneHSM.rectangle) videoInput = CreateObject("roVideoInput") zoneHSM.videoPlayer = videoPlayer zoneHSM.videoInput = videoInput zoneHSM.isVideoZone = true zoneHSM.videoVolume% = zoneHSM.initialVideoVolume% zoneHSM.audioVolume% = zoneHSM.initialAudioVolume% zoneHSM.videoChannelVolumes = CreateObject("roArray", 6, true) zoneHSM.audioChannelVolumes = CreateObject("roArray", 6, true) for i% = 0 to 5 zoneHSM.videoChannelVolumes[i%] = zoneHSM.videoVolume% zoneHSM.audioChannelVolumes[i%] = zoneHSM.audioVolume% next ' initialize video player parameters videoPlayer.SetPort(zoneHSM.msgPort) videoPlayer.SetViewMode(zoneHSM.viewMode%) videoPlayer.SetLoopMode(false) m.SetAudioOutputAndMode(videoPlayer) videoPlayer.SetVolume(zoneHSM.videoVolume%) zoneHSM.ConfigureAudioResources() if m.bsp.sysInfo.numberOfVideoPlanes = 1 then ' graphics should always be in front if there's only a single video plane videoPlayer.ToBack() else if zoneHSM.zOrderFront then videoPlayer.ToFront() else videoPlayer.ToBack() end if end if zoneHSM.videoPlayerAudioSettings = { } m.bsp.SetAudioVolumeLimits(zoneHSM, zoneHSM.videoPlayerAudioSettings) m.activeState = m.playlist.firstState if type(m.playlist.firstState) = "roAssociativeArray" then m.previousStateName$ = m.playlist.firstState.id$ else m.previousStateName$ = "" end if end sub Sub VideoZoneConstructor() activeState = m.InitializeVideoZoneObjects() m.CreateObjects() end sub Function VideoZoneGetInitialState() as object return m.activeState end function Function STStreamPlayingEventHandler(event as object, stateData as object) as object MEDIA_END = 8 stateData.nextState = invalid if type(m.stateMachine.audioPlayer) = "roAudioPlayer" or type(m.stateMachine.audioPlayer) = "roAudioPlayerMx" then audioPlayer = m.stateMachine.audioPlayer else audioPlayer = m.stateMachine.videoPlayer end if if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) url$ = m.url.GetCurrentParameterValue() if m.mediaType$ = "video" then aa = { } aa["url"] = url$ if m.bsp.sign.isVideoWall and m.bsp.sign.videoWallType$ = "stretched" then aa["MultiscreenWidth"] = m.bsp.sign.videoWallNumColumns% aa["MultiscreenHeight"] = m.bsp.sign.videoWallNumRows% aa["MultiscreenX"] = m.bsp.sign.videoWallColumnPosition% aa["MultiscreenY"] = m.bsp.sign.videoWallRowPosition% aa["Mode"] = m.stateMachine.viewMode% end if loopMode% = 1 if type(m.videoEndEvent) = "roAssociativeArray" then loopMode% = 0 m.stateMachine.videoPlayer.SetLoopMode(loopMode%) if m.stateMachine.mosaicDecoderName <> "" then aa.Decoder = m.stateMachine.mosaicDecoderName end if ok = m.stateMachine.videoPlayer.PlayFile(aa) if ok = 0 then m.bsp.diagnostics.PrintDebug("Error playing rtsp file in STStreamPlayingEventHandler: url = " + url$) videoStreamPlaybackFailure = { } videoStreamPlaybackFailure["EventType"] = "VideoStreamPlaybackFailure" m.stateMachine.msgPort.PostMessage(videoStreamPlaybackFailure) end if else aa = { } aa["url"] = url$ ok = audioPlayer.PlayFile(aa) if ok = 0 then m.bsp.diagnostics.PrintDebug("Error playing rtsp file in STStreamPlayingEventHandler: url = " + url$) audioStreamPlaybackFailure = { } audioStreamPlaybackFailure["EventType"] = "AudioStreamPlaybackFailure" m.stateMachine.msgPort.PostMessage(audioStreamPlaybackFailure) end if end if m.bsp.SetTouchRegions(m) m.stateMachine.ClearImagePlane() m.LaunchTimer() ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "stream") ' playback logging m.stateMachine.LogPlayStart("stream", url$) return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else if event["EventType"] = "VideoStreamPlaybackFailure" then if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) else if event["EventType"] = "AudioStreamPlaybackFailure" then if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if else return m.MediaItemEventHandler(event, stateData) end if end if else if type(event) = "roVideoEvent" and type(m.stateMachine.videoPlayer) = "roVideoPlayer" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Video Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") end if end if else if type(event) = "roAudioEvent" and type(m.stateMachine.audioPlayer) = "roAudioPlayer" and event.GetSourceIdentity() = audioPlayer.GetIdentity() then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Audio Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Function STMjpegPlayingEventHandler(event as object, stateData as object) as object MEDIA_END = 8 stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) if type(m.stateMachine.mjpegUrl) <> "roUrlTransfer" then m.stateMachine.mjpegUrl = CreateObject("roUrlTransfer") m.stateMachine.mjpegUrl.SetUserAgent(m.bsp.userAgent$) end if url$ = m.url.GetCurrentParameterValue() m.stateMachine.mjpegUrl.SetURL(url$) m.stateMachine.mjpegUrl.SetProxy("") if type(m.stateMachine.mjpegMimeStream) <> "roMimeStream" then aa = GetBinding("contentDownloadEnabled", 0) binding = aa.network_interface m.bsp.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for mjpegMimeStream is ", binding)) ok = m.stateMachine.mjpegUrl.BindToInterface(binding) if not ok then stop m.stateMachine.mjpegMimeStream = CreateObject("roMimeStream", m.stateMachine.mjpegUrl) end if if type(m.stateMachine.mjpegVideoPlayer) <> "roVideoPlayer" then m.stateMachine.mjpegVideoPlayer = CreateObject("roVideoPlayer") if CanRotateByScreen(m.bsp.sign, {}) then ' no need to rotate per zone if already rotated by screen else if IsPortraitBottomLeft(m.bsp.sign.monitorOrientation) then m.stateMachine.mjpegVideoPlayer.SetTransform("rot90") else if IsPortraitBottomRight(m.bsp.sign.monitorOrientation) then m.stateMachine.mjpegVideoPlayer.SetTransform("rot270") end if m.stateMachine.mjpegVideoPlayer.SetRectangle(m.stateMachine.rectangle) m.stateMachine.mjpegVideoPlayer.SetPort(m.bsp.msgPort) end if aa = { } aa["PictureStream"] = m.stateMachine.mjpegMimeStream aa["Rotate"] = m.rotation% if m.bsp.sign.isVideoWall and m.bsp.sign.videoWallType$ = "stretched" then aa["MultiscreenWidth"] = m.bsp.sign.videoWallNumColumns% aa["MultiscreenHeight"] = m.bsp.sign.videoWallNumRows% aa["MultiscreenX"] = m.bsp.sign.videoWallColumnPosition% aa["MultiscreenY"] = m.bsp.sign.videoWallRowPosition% aa["Mode"] = m.stateMachine.viewMode% end if if m.stateMachine.mosaicDecoderName <> "" then aa.Decoder = m.stateMachine.mosaicDecoderName end if ok = m.stateMachine.mjpegVideoPlayer.PlayFile(aa) m.bsp.SetTouchRegions(m) m.stateMachine.ClearImagePlane() m.LaunchTimer() ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "mjpeg") ' playback logging m.stateMachine.LogPlayStart("mjpeg", url$) return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.stateMachine.mjpegUrl = invalid m.stateMachine.mjpegMimeStream = invalid m.stateMachine.mjpegVideoPlayer = invalid m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) ' else if event["EventType"] = "VideoPlaybackFailureEvent" then ' if type(m.videoEndEvent) = "roAssociativeArray" then ' return m.ExecuteTransition(m.videoEndEvent, stateData, "") ' endif else return m.MediaItemEventHandler(event, stateData) end if end if else if type(event) = "roVideoEvent" and event.GetSourceIdentity() = m.stateMachine.mjpegVideoPlayer.GetIdentity() then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Video Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Function STVideoPlayingEventHandler(event as object, stateData as object) as object MEDIA_END = 8 VIDEO_TIME_CODE = 12 stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.LaunchVideo("video") return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else if event["EventType"] = "VideoPlaybackFailureEvent" then if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) else return m.MediaItemEventHandler(event, stateData) end if end if else if type(event) = "roVideoEvent" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Video Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") else if not(type(m.synchronizeEvents) = "roAssociativeArray" or type(m.internalSynchronizeEvents) = "roAssociativeArray") then ' looping video - since LaunchVideo is not called, perform logging here. file$ = m.videoItem.fileName$ m.stateMachine.LogPlayStart("video", file$) end if PostMediaEndEvent(m.bsp.msgPort) else if event.GetInt() = VIDEO_TIME_CODE then videoTimeCodeIndex$ = str(event.GetData()) m.bsp.diagnostics.PrintDebug("Video TimeCode Event " + videoTimeCodeIndex$) if type(m.videoTimeCodeEvents) = "roAssociativeArray" then videoTimeCodeEvent = m.videoTimeCodeEvents[videoTimeCodeIndex$] if type(videoTimeCodeEvent) = "roAssociativeArray" then m.bsp.ExecuteTransitionCommands(m.stateMachine, videoTimeCodeEvent) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "videoTimeCode", "", "1") return "HANDLED" end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "videoTimeCode", "", "0") end if end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Sub PostMediaEndEvent(msgPort as object) mediaEndEvent = { } mediaEndEvent["EventType"] = "MEDIA_END" msgPort.PostMessage(mediaEndEvent) end sub Sub SetAudioTimeCodeEvents() if type(m.stateMachine.audioPlayer) = "roAudioPlayer" then player = m.stateMachine.audioPlayer else player = m.stateMachine.videoPlayer end if player.ClearEvents() if type(m.audioTimeCodeEvents) = "roAssociativeArray" then for each eventNum in m.audioTimeCodeEvents m.AddAudioTimeCodeEvent(m.audioTimeCodeEvents[eventNum].timeInMS%, int(val(eventNum))) next end if end sub Sub AddAudioTimeCodeEvent(timeInMS% as integer, eventNum% as integer) if type(m.stateMachine.audioPlayer) = "roAudioPlayer" then m.stateMachine.audioPlayer.AddEvent(eventNum%, timeInMS%) else m.stateMachine.videoPlayer.AddEvent(eventNum%, timeInMS%) end if end sub Sub SetVideoTimeCodeEvents() m.stateMachine.videoPlayer.ClearEvents() if type(m.videoTimeCodeEvents) = "roAssociativeArray" then for each eventNum in m.videoTimeCodeEvents m.AddVideoTimeCodeEvent(m.videoTimeCodeEvents[eventNum].timeInMS%, int(val(eventNum))) next end if end sub Sub AddVideoTimeCodeEvent(timeInMS% as integer, eventNum% as integer) if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.AddEvent(eventNum%, timeInMS%) end if end sub 'endregion 'region Video or Images State Machine ' ************************************************* ' ' VideoOrImages State Machine ' ' ************************************************* Function newVideoOrImagesZoneHSM(bsp as object, zoneDescription as object) as object zoneHSM = newVideoZoneHSM(bsp, zoneDescription) zoneHSM.ConstructorHandler = VideoOrImagesZoneConstructor zoneHSM.InitialPseudostateHandler = VideoOrImagesZoneGetInitialState zoneHSM.imageMode% = zoneDescription.imageMode% zoneHSM.numImageItems% = 0 return zoneHSM end function Sub VideoOrImagesZoneConstructor() m.InitializeVideoZoneObjects() zoneHSM = m ' create players if zoneHSM.numImageItems% > 0 then imagePlayer = CreateObject("roImageWidget", zoneHSM.rectangle) if CanRotateByScreen(m.bsp.sign, {}) then ' no need to rotate per zone if already rotated by screen else if IsPortraitBottomLeft(m.bsp.sign.monitorOrientation) then imagePlayer.SetTransform("rot90") else if IsPortraitBottomRight(m.bsp.sign.monitorOrientation) then imagePlayer.SetTransform("rot270") end if zoneHSM.imagePlayer = imagePlayer ' initialize image player parameters imagePlayer.SetDefaultMode(zoneHSM.imageMode%) else zoneHSM.imagePlayer = invalid end if zoneHSM.audioVolume% = zoneHSM.initialAudioVolume% zoneHSM.audioPlayerAudioSettings = { } m.bsp.SetAudioVolumeLimits(zoneHSM, zoneHSM.audioPlayerAudioSettings) m.CreateObjects() end sub Function VideoOrImagesZoneGetInitialState() as object return m.activeState end function Sub PopulatePlayFileFromLiveDataFeed() if type(m.liveDataFeed.assetPoolFiles) = "roAssetPoolFiles" then numFiles% = m.liveDataFeed.fileUrls.Count() for i% = 0 to numFiles% - 1 key$ = m.liveDataFeed.fileKeys[i%] url$ = m.liveDataFeed.fileUrls[i%] filePath$ = m.liveDataFeed.assetPoolFiles.GetPoolFilePath(url$) fileTableEntry = { } fileTableEntry.fileName$ = url$ fileTableEntry.filePath$ = filePath$ if type(m.liveDataFeed.fileTypes) = "roArray" and m.liveDataFeed.fileTypes.Count() > i% then fileTableEntry.fileType$ = m.liveDataFeed.fileTypes[i%] end if fileTableEntry.automaticallyLoop = true fileTableEntry.isEncrypted = false fileTableEntry.videoDisplayMode% = 0 m.filesTable.AddReplace(key$, fileTableEntry) next end if end sub Function STPlayFileEventHandler(event as object, stateData as object) as object MEDIA_END = 8 stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.bsp.diagnostics.PrintDebug(m.id$ + ": payload is " + m.payload$) if m.useUserVariable then userVariable = m.userVariable m.payload$ = userVariable.GetCurrentValue() end if if m.filesTable.IsEmpty() or (not m.useDefaultMedia and not m.filesTable.DoesExist(m.payload$)) then if m.filesTable.IsEmpty() then m.bsp.diagnostics.PrintDebug(m.id$ + ": files not loaded yet") else m.bsp.diagnostics.PrintDebug(m.id$ + ": no file associated with payload") end if m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) m.LaunchTimer() m.bsp.SetTouchRegions(m) else fileTableEntry = m.filesTable.Lookup(m.payload$) if type(fileTableEntry) = "roAssociativeArray" then fileName$ = fileTableEntry.fileName$ fileType$ = fileTableEntry.fileType$ else fileName$ = m.defaultMediaFileName$ fileType$ = m.defaultMediaFileType$ end if m.imageItem = invalid m.videoItem = invalid m.audioItem = invalid if fileType$ = "image" then m.imageItem = { } m.imageItem.fileName$ = fileName$ m.imageItem.isEncrypted = m.bsp.encryptionByFile.DoesExist(fileName$) if type(fileTableEntry) = "roAssociativeArray" then if type(fileTableEntry.filePath$) = "roString" then m.imageItem.filePath$ = fileTableEntry.filePath$ end if m.imageItem.userVariable = fileTableEntry.userVariable end if m.imageItem.slideTransition% = m.slideTransition% m.DisplayImage("playFile") else if fileType$ = "video" then m.videoItem = { } m.videoItem.fileName$ = fileName$ m.videoItem.isEncrypted = m.bsp.encryptionByFile.DoesExist(fileName$) if type(fileTableEntry) = "roAssociativeArray" then if type(fileTableEntry.filePath$) = "roString" then m.videoItem.filePath$ = fileTableEntry.filePath$ end if m.videoItem.probeData = fileTableEntry.probeData m.videoItem.videoDisplayMode% = fileTableEntry.videoDisplayMode% m.videoItem.userVariable = fileTableEntry.userVariable m.videoItem.automaticallyLoop = fileTableEntry.automaticallyLoop if type(m.videoEndEvent) = "roAssociativeArray" then transition = m.videoEndEvent if transition.targetMediaState$ = "" and (transition.remainOnCurrentStateActions = "stop" or transition.remainOnCurrentStateActions = "stopclear") then m.videoItem.automaticallyLoop = false endif endif else m.videoItem.videoDisplayMode% = 0 end if m.LaunchVideo("playFile") else if fileType$ = "audio" then m.audioItem = { } m.audioItem.fileName$ = fileName$ m.audioItem.isEncrypted = m.bsp.encryptionByFile.DoesExist(fileName$) if type(fileTableEntry) = "roAssociativeArray" then if type(fileTableEntry.filePath$) = "roString" then m.audioItem.filePath$ = fileTableEntry.filePath$ end if m.audioItem.probeData = fileTableEntry.probeData m.audioItem.userVariable = fileTableEntry.userVariable end if m.LaunchAudio("playFile") end if end if return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) return "HANDLED" end if end if else if type(m.videoItem) = "roAssociativeArray" and type(event) = "roVideoEvent" and type(m.stateMachine.videoPlayer) = "roVideoPlayer" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Video Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) end if else if type(m.audioItem) = "roAssociativeArray" and IsAudioEvent(m.stateMachine, event) then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Audio Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) end if end if return m.MediaItemEventHandler(event, stateData) end function ' this code explicitly does not catch roAudioEventMx events - those are handled elsewhere. Function IsAudioEvent(stateMachine as object, event as object) as boolean return (type(stateMachine.audioPlayer) = "roAudioPlayer" and type(event) = "roAudioEvent" and event.GetSourceIdentity() = stateMachine.audioPlayer.GetIdentity()) or (type(stateMachine.videoPlayer) = "roVideoPlayer" and type(event) = "roVideoEvent" and event.GetSourceIdentity() = stateMachine.videoPlayer.GetIdentity()) end function Function IsAudioPlayer(audioPlayer as object) as boolean return type(audioPlayer) = "roAudioPlayer" or type(audioPlayer) = "roAudioPlayerMx" end function ' BACONTODO - possible to combine these two subs? Sub ConfigureIntraStateEventHandlersButton(navigationList as object) for each navigation in navigationList if IsBpNavigationEvent(navigation) then bpEvent = { } bpEvent.buttonPanelIndex% = int(val(GetButtonPanelIndexFromBpIndex(navigation.eventData.bpIndex))) bpEvent.buttonNumber$ = stri(navigation.eventData.buttonNumber) bpEvent.configuration$ = "press" m.bsp.ConfigureBPButton(bpEvent.buttonPanelIndex%, bpEvent.buttonNumber$, bpEvent) end if if navigation.eventName = "gpioUserEvent" then gpioEvent = { } gpioEvent.buttonNumber$ = StripLeadingSpaces(stri(navigation.eventData.buttonNumber)) gpioEvent.configuration$ = "press" m.bsp.ConfigureGPIOButton(gpioEvent.buttonNumber$, gpioEvent) end if next end sub Function IsBpNavigationEvent(navigationEvent as object) as boolean if navigationEvent.eventName = "bp900AUserEvent" or navigationEvent.eventName = "bp900BUserEvent" or navigationEvent.eventName = "bp900CUserEvent" or navigationEvent.eventName = "bp900DUserEvent" then return true end if if navigationEvent.eventName = "bp200AUserEvent" or navigationEvent.eventName = "bp200BUserEvent" or navigationEvent.eventName = "bp200CUserEvent" or navigationEvent.eventName = "bp200DUserEvent" then return true end if return false end function Function GetButtonPanelIndexFromBpIndex(bpIndex$ as string) as string bpUserEventButtonPanelIndex$ = "0" if bpIndex$ = "a" then bpUserEventButtonPanelIndex$ = "0" else if bpIndex$ = "b" then bpUserEventButtonPanelIndex$ = "1" else if bpIndex$ = "c" then bpUserEventButtonPanelIndex$ = "2" else if bpIndex$ = "d" then bpUserEventButtonPanelIndex$ = "3" end if return bpUserEventButtonPanelIndex$ end function Function GetMatchingNavigationEvent(navigationEventList as object, event as object) as object MEDIA_END = 8 for each navigationEvent in navigationEventList if type(event) = "roAssociativeArray" and IsString(event["EventType"]) then if IsBpNavigationEvent(navigationEvent) and event["EventType"] = "BPControlDown" then bpIndex$ = event.ButtonPanelIndex bpNum$ = event["ButtonNumber"] m.bsp.diagnostics.PrintDebug("BP Press, button number " + bpNum$ + ", button index " + bpIndex$) navigationButtonNumber$ = StripLeadingSpaces(stri(navigationEvent.eventData.buttonNumber)) bpUserEventButtonPanelIndex$ = GetButtonPanelIndexFromBpIndex(navigationEvent.eventData.bpIndex) if navigationButtonNumber$ = bpNum$ and bpUserEventButtonPanelIndex$ = bpIndex$ then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "bpDown", bpIndex$ + " " + bpNum$, "1") return true end if else if event["EventType"] = "GPIOControlDown" and navigationEvent.eventName = "gpioUserEvent" then if (event["ButtonNumber"] = StripLeadingSpaces(stri(navigationEvent.eventData.buttonNumber))) then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "gpioDown", event["ButtonNumber"], "1") return true end if else if navigationEvent.eventName = "zoneMessage" and event["EventType"] = "SEND_ZONE_MESSAGE" zoneMessage$ = event["EventParameter"] if navigationEvent.eventData.data = zoneMessage$ then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "zoneMessage", zoneMessage$, "1") return true end if end if else if navigationEvent.eventName = "timeout" and type(event) = "roTimerEvent" and type(m.advanceOnImageTimeoutTimer) = "roTimer" and event.GetSourceIdentity() = m.advanceOnImageTimeoutTimer.GetIdentity() then return navigationEvent else if navigationEvent.eventName = "timeout" and type(event) = "roTimerEvent" and type(m.retreatOnImageTimeoutTimer) = "roTimer" and event.GetSourceIdentity() = m.retreatOnImageTimeoutTimer.GetIdentity() then return navigationEvent else if navigationEvent.eventName = "mediaEnd" and type(event) = "roVideoEvent" and event.GetInt() = MEDIA_END and type(m.stateMachine.videoPlayer) = "roVideoPlayer" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() then return navigationEvent else if navigationEvent.eventName = "mediaEnd" and type(event) = "roAudioEvent" and event.GetInt() = MEDIA_END and IsAudioPlayer(m.stateMachine.audioPlayer) and event.GetSourceIdentity() = m.stateMachine.audioPlayer.GetIdentity() then return navigationEvent else if navigationEvent.eventName = "udp" and type(event) = "roDatagramEvent" then udpEvent$ = event.GetString() m.bsp.diagnostics.PrintDebug("UDP Event " + udpEvent$) udpEventSpec = navigationEvent.eventData.data if (udpEvent$ = udpEventSpec or udpEventSpec = "(.*)") then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "udp", udpEvent$, "1") return true else if instr(1, udpEventSpec, "(.*)") > 0 then r = CreateObject("roRegEx", udpEventSpec, "i") if type(r) = "roRegex" then matches = r.match(udpEvent$) if matches.Count() > 0 then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "udp", udpEvent$, "1") return true endif endif endif endif else if navigationEvent.eventName = "serial" and type(event) = "roStreamLineEvent" then port = event.GetUserData() serialEvent$ = event.GetString() m.bsp.diagnostics.PrintDebug("Serial Line Event " + serialEvent$) if port = navigationEvent.eventData.port then if (serialEvent$ = navigationEvent.eventData.data or navigationEvent.eventData.data = "<*>") then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "serial", serialEvent$, "1") return true else if instr(1, navigationEvent.eventData.data, "(.*)") > 0 then r = CreateObject("roRegEx", navigationEvent.eventData.data, "i") if type(r) = "roRegex" then matches = r.match(serialEvent$) if matches.Count() > 0 then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "serial", serialEvent$, "1") return true end if end if end if end if end if else if navigationEvent.eventName = "keyboard" and type(event) = "roKeyboardPress" then keyboardChar$ = chr(event.GetInt()) m.bsp.diagnostics.PrintDebug("Keyboard Press" + keyboardChar$) ' if keyboard input is non printable character, convert it to the special code keyboardCode$ = m.bsp.GetNonPrintableKeyboardCode(event.GetInt()) if keyboardCode$ <> "" then keyboardChar$ = keyboardCode$ end if if navigationEvent.eventData.data = keyboardChar$ or navigationEvent.eventData.data = "" then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "keyboard", keyboardChar$, "1") return true end if else if navigationEvent.eventName = "synchronize" and type(event) = "roSyncManagerEvent" then synchronizeEvent$ = event.GetId() m.bsp.diagnostics.PrintDebug("Synchronize Event " + synchronizeEvent$) if navigationEvent.eventData.data = synchronizeEvent$ then m.stateMachine.syncInfo = CreateObject("roAssociativeArray") m.stateMachine.syncInfo.SyncDomain = event.GetDomain() m.stateMachine.syncInfo.SyncId = event.GetId() m.stateMachine.syncInfo.SyncIsoTimestamp = event.GetIsoTimestamp() m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "enhancedSynchronize", synchronizeEvent$, "1") return true endif end if next return invalid end function Function HandleIntraStateEvent(event as object, navigationEventList as object) as boolean MEDIA_END = 8 navigationEvent = m.GetMatchingNavigationEvent(navigationEventList, event) if navigationEvent = invalid then return false end if return true end function Sub LaunchMediaListPlaybackItem(playImmediate as boolean, executeNextCommands as boolean, executePrevCommands as boolean) ' Make sure we have a valid list itemIndex = m.playbackIndices[m.playbackIndex%] if itemIndex = invalid then if m.playbackIndices.count() = 0 then ' Attempting to play an empty list - log the failure m.bsp.diagnostics.PrintDebug("LaunchMediaListPlaybackItem failed - attempted to play an empty list") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_EMPTY_MEDIA_PLAYLIST, "") stop else m.bsp.diagnostics.PrintDebug("LaunchMediaListPlaybackItem failed - invalid item index " + stri(m.playbackIndex%) + " " + stri(m.playbackIndices.count())) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_EMPTY_MEDIA_PLAYLIST, "invalid item index") m.playbackIndex% = 0 end if endif ' get current media item and launch playback item = m.items[itemIndex] if m.sendZoneMessage and not(m.statemachine.type$ = "EnhancedAudio") then fileNameWithoutExtension$ = item.filename$ ' if the file name has an extension, remove it before sending ext = GetFileExtension(item.filename$) if type(ext) = "roString" then index = instr(1, item.filename$, ext) if index > 2 then fileNameWithoutExtension$ = mid(item.filename$, 1, index - 2) end if end if ' send ZoneMessage using the file name as the message zoneMessageCmd = { } zoneMessageCmd["EventType"] = "SEND_ZONE_MESSAGE" zoneMessageCmd["EventParameter"] = fileNameWithoutExtension$ m.bsp.msgPort.PostMessage(zoneMessageCmd) end if if executeNextCommands then if type(m.transitionNextItemCmds) = "roArray" then for each cmd in m.transitionNextItemCmds m.bsp.ExecuteCmd(m.stateMachine, cmd.name$, cmd.parameters) next end if end if if executePrevCommands then if type(m.transitionPreviousItemCmds) = "roArray" then for each cmd in m.transitionPreviousItemCmds m.bsp.ExecuteCmd(m.stateMachine, cmd.name$, cmd.parameters) next end if end if if item.type = "image" then m.imageItem = item m.imageItem.slideTransition% = m.slideTransition% m.imageItem.transitionDuration% = m.transitionDuration% if not m.firstItemDisplayed then m.PreDrawImage() end if m.DrawImage(true) if not m.firstItemDisplayed then m.PostDrawImage("imageList") else m.ClearVideo() end if ' if advancing on image timeout, set the timer if type(m.advanceOnImageTimeoutTimer) = "roTimer" then m.advanceOnImageTimeoutTimer.Stop() end if ' setup timeout event if the user specified one in the transitionToNextEventList timeoutEvent = m.GetTimeoutEvent(m.transitionToNextEventList) if type(timeoutEvent) = "roAssociativeArray" then if type(m.advanceOnImageTimeoutTimer) <> "roTimer" then m.advanceOnImageTimeoutTimer = CreateObject("roTimer") m.advanceOnImageTimeoutTimer.SetPort(m.stateMachine.msgPort) end if m.advanceOnImageTimeoutTimer.SetElapsed(timeoutevent.eventData.interval, 0) m.advanceOnImageTimeoutTimer.Start() end if ' setup timeout event if the user specified one in the transitionToPreviousEventList timeoutEvent = m.GetTimeoutEvent(m.transitionToPreviousEventList) if type(timeoutEvent) = "roAssociativeArray" then if type(m.retreatOnImageTimeoutTimer) <> "roTimer" then m.retreatOnImageTimeoutTimer = CreateObject("roTimer") m.retreatOnImageTimeoutTimer.SetPort(m.stateMachine.msgPort) end if m.retreatOnImageTimeoutTimer.SetElapsed(timeoutevent.eventData.interval, 0) m.retreatOnImageTimeoutTimer.Start() end if else if item.type = "video" then m.videoItem = item if not m.firstItemDisplayed then m.PrePlayVideo() end if m.PlayVideo(not m.firstItemDisplayed, true) if not m.firstItemDisplayed then m.PostPlayVideo("videoList") else m.stateMachine.ClearImagePlane() end if else if item.type = "audio" then m.audioItem = item if not m.firstItemDisplayed then m.PrePlayAudio() end if if m.stateMachine.type$ = "EnhancedAudio" then m.PlayMixerAudio(not m.firstItemDisplayed, m.playbackIndex%, playImmediate) else m.PlayAudio(not m.firstItemDisplayed, true) end if if not m.firstItemDisplayed then m.PostPlayAudio("audioList") end if end if m.firstItemDisplayed = true end sub Function GetTimeoutEvent(userEventList) as object for each userEvent in userEventList if userEvent.eventName = "timeout" then return userEvent end if next return invalid end function Sub AdvanceMediaListPlayback(playImmediate as boolean, executeNextCommands as boolean) m.LaunchMediaListPlaybackItem(playImmediate, executeNextCommands, false) m.playbackIndex% = m.playbackIndex% + 1 if m.playbackIndex% >= m.numItems% then m.playbackIndex% = 0 end if end sub Sub RetreatMediaListPlayback(playImmediate as boolean, executePrevCommands as boolean) ' index currently points to 'next' track - need to retreat by 2 to get to previous track for i% = 0 to 1 m.playbackIndex% = m.playbackIndex% - 1 if m.playbackIndex% < 0 then m.playbackIndex% = m.numItems% - 1 end if next m.LaunchMediaListPlaybackItem(playImmediate, false, executePrevCommands) m.playbackIndex% = m.playbackIndex% + 1 if m.playbackIndex% >= m.numItems% then m.playbackIndex% = 0 end if end sub Sub StartInactivityTimer() if m.inactivityTimeout then if type(m.inactivityTimer) = "roTimer" then userData = {} userData.id = "mediaList" userData.state = m m.inactivityTimer.SetUserData(userData) m.inactivityTimer.SetElapsed(m.inactivityTime, 0) m.inactivityTimer.Start() endif endif end sub Sub ConfigureBPButtons() for buttonPanelIndex% = 0 to 3 bpEvents = m.bpEvents[buttonPanelIndex%] for each buttonNumber in bpEvents bpEvent = bpEvents[buttonNumber] m.bsp.ConfigureBPButton(buttonPanelIndex%, buttonNumber, bpEvent) next next end sub Sub ConfigureGPIOButtons() gpioEvents = m.gpioEvents for each buttonNumber in gpioEvents gpioEvent = gpioEvents[buttonNumber] m.bsp.ConfigureGPIOButton(buttonNumber, gpioEvent) next end sub Function ItemIsEncrypted(item as object) as boolean if type(item.IsEncrypted) = "roBoolean" and item.isEncrypted then return true else return false end if end function Sub PrePlayVideo() m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" end sub Sub PlayVideo(executeEntryCmds as boolean, disableLoopMode as boolean) ' set video mode before executing commands - required order for working around LG (maybe others) bugs getting back to 2-D mode videoMode = CreateObject("roVideoMode") if type(videoMode) = "roVideoMode" then videoMode.Set3dMode(m.videoItem.videoDisplayMode%) videoMode = invalid end if loopMode$ = "AlwaysLoop" if disableLoopMode or (type(m.videoItem.automaticallyLoop) = "roBoolean" and (not m.videoItem.automaticallyLoop)) or type(m.videoEndEvent) = "roAssociativeArray" or type(m.synchronizeEvents) = "roAssociativeArray" or type(m.internalSynchronizeEvents) = "roAssociativeArray" then loopMode$ = "NoLoop" ' Support seamless looping during synchronization - overwrite loopMode as necessary if (type(m.videoItem.automaticallyLoop) = "roBoolean" and m.videoItem.automaticallyLoop) and (type(m.synchronizeEvents) = "roAssociativeArray" or type(m.internalSynchronizeEvents) = "roAssociativeArray") then loopMode$ = "SeamlessLoopOrNotAtAll" m.stateMachine.videoPlayer.SetLoopMode(loopMode$) file$ = m.videoItem.fileName$ if type(m.videoItem.filePath$) = "roString" then filePath$ = m.videoItem.filePath$ else filePath$ = GetPoolFilePath(m.bsp.assetPoolFiles, file$) end if ' determine whether or not a preload has been performed preloaded = false if type(m.stateMachine.preloadState) = "roAssociativeArray" then if m.stateMachine.preloadedStateName$ = m.name$ then preloaded = true end if end if syncInProgress = false if type(m.stateMachine.syncInfo) = "roAssociativeArray" then syncInProgress = true end if m.stateMachine.videoPlayer.EnableSafeRegionTrimming(false) if not preloaded and not syncInProgress then m.stateMachine.videoPlayer.Stop() end if m.SetVideoTimeCodeEvents() if executeEntryCmds then m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) end if if preloaded then ok = m.stateMachine.videoPlayer.Play() if ok = 0 then m.bsp.diagnostics.PrintDebug("Error playing preloaded file in PlayVideo: " + file$ + ", " + filePath$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_PLAYBACK_FAILURE, file$) videoPlaybackFailure = { } videoPlaybackFailure["EventType"] = "VideoPlaybackFailureEvent" m.stateMachine.msgPort.PostMessage(videoPlaybackFailure) end if m.stateMachine.preloadState = invalid m.stateMachine.preloadedStateName$ = "" m.bsp.diagnostics.PrintDebug("LaunchVideo: play preloaded file " + file$ + ", loopMode = " + loopMode$) else aa = { } aa.AddReplace("Filename", filePath$) if type(m.videoItem.probeData) = "roString" then m.bsp.diagnostics.PrintDebug("LaunchVideo: probeData = " + m.videoItem.probeData) aa.AddReplace("ProbeString", m.videoItem.probeData) end if if syncInProgress then aa.AddReplace("SyncDomain", m.stateMachine.syncInfo.SyncDomain) aa.AddReplace("SyncId", m.stateMachine.syncInfo.SyncId) aa.AddReplace("SyncIsoTimestamp", m.stateMachine.syncInfo.SyncIsoTimestamp) if m.bsp.sign.isVideoWall and m.bsp.sign.videoWallType$ = "stretched" then aa["MultiscreenWidth"] = m.bsp.sign.videoWallNumColumns% aa["MultiscreenHeight"] = m.bsp.sign.videoWallNumRows% aa["MultiscreenX"] = m.bsp.sign.videoWallColumnPosition% aa["MultiscreenY"] = m.bsp.sign.videoWallRowPosition% aa["Mode"] = m.stateMachine.viewMode% end if end if if ItemIsEncrypted(m.videoItem) then aa.AddReplace("EncryptionAlgorithm", "AesCtrHmac") aa.AddReplace("EncryptionKey", file$) end if if m.stateMachine.mosaicDecoderName <> "" then aa.Decoder = m.stateMachine.mosaicDecoderName end if ok = m.stateMachine.videoPlayer.PlayFile(aa) if ok = 0 then m.bsp.diagnostics.PrintDebug("Error playing file in LaunchVideo: " + file$ + ", " + filePath$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_PLAYBACK_FAILURE, file$) videoPlaybackFailure = { } videoPlaybackFailure["EventType"] = "VideoPlaybackFailureEvent" m.stateMachine.msgPort.PostMessage(videoPlaybackFailure) end if if syncInProgress then m.bsp.diagnostics.PrintDebug("LaunchVideo: play synchronized file " + file$) m.stateMachine.syncInfo = invalid end if end if if type(m.videoItem.userVariable) = "roAssociativeArray" then m.videoItem.userVariable.Increment() end if ' playback logging m.stateMachine.LogPlayStart("video", file$) end sub Sub PostPlayVideo(stateType$ as string) m.bsp.SetTouchRegions(m) m.stateMachine.ClearImagePlane() m.LaunchTimer() ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, stateType$) end sub Sub LaunchVideo(stateType$ as string) m.PrePlayVideo() m.PlayVideo(true, false) m.PostPlayVideo(stateType$) end sub Sub PreDrawImage() m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) end sub Sub DrawImage(setTransition as boolean) file$ = m.imageItem.fileName$ if type(m.imageItem.filePath$) = "roString" then filePath$ = m.imageItem.filePath$ else filePath$ = GetPoolFilePath(m.bsp.assetPoolFiles, file$) end if if setTransition then m.stateMachine.imagePlayer.SetDefaultTransition(m.imageItem.slideTransition%) end if if type(m.imageItem.transitionDuration%) = "roInt" then m.stateMachine.imagePlayer.SetTransitionDuration(m.imageItem.transitionDuration%) end if if not true then stop else if type(m.stateMachine.syncInfo) = "roAssociativeArray" then aa = { } aa.AddReplace("Filename", filePath$) aa.AddReplace("SyncDomain", m.stateMachine.syncInfo.SyncDomain) aa.AddReplace("SyncId", m.stateMachine.syncInfo.SyncId) aa.AddReplace("SyncIsoTimestamp", m.stateMachine.syncInfo.SyncIsoTimestamp) aa.AddReplace("Transition", m.imageItem.slideTransition%) ok = m.stateMachine.imagePlayer.DisplayFile(aa) if ok = 0 then m.bsp.diagnostics.PrintDebug("Error displaying synchronized file: " + file$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_PLAYBACK_FAILURE, file$) else m.bsp.diagnostics.PrintDebug("DisplayImage: display synchronized file " + file$) end if m.stateMachine.syncInfo = invalid else ' determine whether or not a preload has been performed preloaded = false if type(m.stateMachine.preloadState) = "roAssociativeArray" then if m.stateMachine.preloadedStateName$ = m.name$ preloaded = true m.bsp.diagnostics.PrintDebug("Use preloaded file " + file$ + " in DisplayImage: ") m.bsp.diagnostics.PrintDebug("DisplayPreload in DisplayImage: " + file$) ok = m.stateMachine.imagePlayer.DisplayPreload() if ok = 0 then m.bsp.diagnostics.PrintDebug("Error in DisplayPreload in DisplayImage: " + file$ + ", " + filePath$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_PLAYBACK_FAILURE, file$) end if end if end if if not preloaded then aa = { } aa.filename = filePath$ if ItemIsEncrypted(m.imageItem) then aa.AddReplace("EncryptionAlgorithm", "AesCtrHmac") aa.AddReplace("EncryptionKey", file$) end if ok = m.stateMachine.imagePlayer.DisplayFile(aa) if not ok then m.bsp.diagnostics.PrintDebug("Error displaying file in DisplayImage: " + file$ + ", " + filePath$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_PLAYBACK_FAILURE, file$) else m.bsp.diagnostics.PrintDebug("Displayed file in DisplayImage: " + file$) end if end if end if end if m.stateMachine.ShowImageWidget() m.stateMachine.preloadState = 0 m.stateMachine.preloadedStateName$ = "" if type(m.imageItem.userVariable) = "roAssociativeArray" then m.imageItem.userVariable.Increment() end if ' playback logging m.stateMachine.LogPlayStart("image", file$) end sub Sub PostDrawImage(stateType$ as string) m.ClearVideo() m.LaunchTimer() m.bsp.SetTouchRegions(m) ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, stateType$) end sub Sub ClearVideo() if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if end sub Sub DisplayImage(stateType$ as string) m.PreDrawImage() m.DrawImage(true) m.PostDrawImage(stateType$) end sub Sub PrePlayAudio() m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if end sub Sub PlayAudio(executeEntryCmds as boolean, disableLoopMode as boolean) loopMode% = 1 if disableLoopMode or type(m.audioEndEvent) = "roAssociativeArray" then loopMode% = 0 if type(m.stateMachine.audioPlayer) = "roAudioPlayer" then player = m.stateMachine.audioPlayer else player = m.stateMachine.videoPlayer end if player.SetLoopMode(loopMode%) player.Stop() m.SetAudioTimeCodeEvents() if executeEntryCmds then m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) end if if type(m.audioItem.fileName$) = "roString" and len(m.audioItem.fileName$) > 0 then file$ = m.audioItem.fileName$ else file$ = m.audioItem.filePath$ end if if type(m.audioItem.filePath$) = "roString" then filePath$ = m.audioItem.filePath$ else filePath$ = GetPoolFilePath(m.bsp.assetPoolFiles, m.audioItem.fileName$) end if aa = { } aa.AddReplace("Filename", filePath$) if type(m.audioItem.probeData) = "roString" then m.bsp.diagnostics.PrintDebug("LaunchAudio: probeData = " + m.audioItem.probeData) aa.AddReplace("ProbeString", m.audioItem.probeData) end if ok = player.PlayFile(aa) if ok = 0 then m.bsp.diagnostics.PrintDebug("Error playing audio file: " + file$ + ", " + filePath$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_PLAYBACK_FAILURE, file$) audioPlaybackFailure = { } audioPlaybackFailure["EventType"] = "AudioPlaybackFailureEvent" m.stateMachine.msgPort.PostMessage(audioPlaybackFailure) end if m.stateMachine.ClearImagePlane() if type(m.audioItem.userVariable) = "roAssociativeArray" then m.audioItem.userVariable.Increment() end if ' playback logging m.stateMachine.LogPlayStart("audio", file$) end sub Sub PostPlayAudio(stateType$ as string) m.bsp.SetTouchRegions(m) m.LaunchTimer() ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, stateType$) end sub Sub LaunchAudio(stateType$ as string) m.PrePlayAudio() m.PlayAudio(true, false) m.PostPlayAudio(stateType$) end sub Function STSuperStateEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) m.LaunchTimer() m.bsp.SetTouchRegions(m) ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "superState") ' playback logging m.stateMachine.LogPlayStart("superState", "") return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else if event["EventType"] = "MEDIA_END" then if type(m.mediaEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.mediaEndEvent, stateData, "") end if else return m.MediaItemEventHandler(event, stateData) end if end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Function STEventHandlerEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) if m.stopPlayback then if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if m.stateMachine.ClearImagePlane() if IsAudioPlayer(m.stateMachine.audioPlayer) then m.stateMachine.audioPlayer.Stop() end if end if m.LaunchTimer() m.bsp.SetTouchRegions(m) ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "eventHandler") ' playback logging m.stateMachine.LogPlayStart("eventHandler", "") return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else return m.MediaItemEventHandler(event, stateData) end if end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Function STLiveVideoPlayingEventHandler(event as object, stateData as object) as object MEDIA_END = 8 stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) m.stateMachine.videoPlayer.Stop() m.stateMachine.ClearImagePlane() ' HDMI In m.stateMachine.videoPlayer.EnableSafeRegionTrimming(m.overscan) aa = { } aa.AddReplace("Capture", m.stateMachine.videoInput) if type(m.stateMachine.syncInfo) = "roAssociativeArray" then aa.AddReplace("SyncDomain", m.stateMachine.syncInfo.SyncDomain) aa.AddReplace("SyncId", m.stateMachine.syncInfo.SyncId) aa.AddReplace("SyncIsoTimestamp", m.stateMachine.syncInfo.SyncIsoTimestamp) if m.bsp.sign.isVideoWall and m.bsp.sign.videoWallType$ = "stretched" then aa["MultiscreenWidth"] = m.bsp.sign.videoWallNumColumns% aa["MultiscreenHeight"] = m.bsp.sign.videoWallNumRows% aa["MultiscreenX"] = m.bsp.sign.videoWallColumnPosition% aa["MultiscreenY"] = m.bsp.sign.videoWallRowPosition% aa["Mode"] = m.stateMachine.viewMode% end if end if if m.stateMachine.mosaicDecoderName <> "" then aa.Decoder = m.stateMachine.mosaicDecoderName end if ok = m.stateMachine.videoPlayer.PlayFile(aa) m.LaunchTimer() m.bsp.SetTouchRegions(m) ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "liveVideo") ' playback logging m.stateMachine.LogPlayStart("liveVideo", "") return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else return m.MediaItemEventHandler(event, stateData) end if end if else if type(event) = "roVideoEvent" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Video Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort)' end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Sub SetUserAgentForHtmlWidget(bsp as object, htmlWidget as object, aa as object) if type(aa) = "roAssociativeArray" then isLocalHtmlWidget = false if htmlWidget = invalid then r = CreateObject("roRectangle", 0, 0, 0, 0) h = CreateObject("roHtmlWidget", r) isLocalHtmlWidget = true else h = htmlWidget end if if type(h) = "roHtmlWidget" then ua$ = h.GetUserAgent() ' Prepend custom user agent string on to standard Html Widget string p% = instr(1, ua$, ")") if p% > 0 then ua$ = bsp.userAgent$ + mid(ua$, p% + 1) aa.user_agent = ua$ end if if isLocalHtmlWidget then h = invalid end if end if end if End Sub Function GetInterstitialHtmlToLaunchHtml() as string interstitialHtml$ = "" interstitialHtml$ = interstitialHtml$ + "" + chr(10) interstitialHtml$ = interstitialHtml$ + "" + chr(10) interstitialHtml$ = interstitialHtml$ + "" + chr(10) interstitialHtml$ = interstitialHtml$ + "" + chr(10) interstitialHtml$ = interstitialHtml$ + "" + chr(10) return interstitialHtml$ end function Function GetInterstitialHtmlUrl(prefix$, fileName$) as string ' if the last character in prefix$ is a '/', strip it to create a new string ' substitute instances of 'siteName' len% = len(prefix$) if mid(prefix$, len(prefix$), 1) = "/" then len% = len% - 1 end if prefixSubstitution$ = mid(prefix$, 1, len%) interstitialHtml$ = GetInterstitialHtmlToLaunchHtml() ' replace with actual siteName siteNameIndex% = instr(1, interstitialHtml$, "") while siteNameIndex% > 0 interstitialHtmlLength% = len(interstitialHtml$) interstitialHtml$ = mid(interstitialHtml$, 1, siteNameIndex% - 1) + prefixSubstitution$ + mid(interstitialHtml$, siteNameIndex% + len("")) siteNameIndex% = instr(1, interstitialHtml$, "") end while ' replace with actual filename fileNameIndex% = instr(1, interstitialHtml$, "") while fileNameIndex% > 0 interstitialHtmlLength% = len(interstitialHtml$) interstitialHtml$ = mid(interstitialHtml$, 1, fileNameIndex% - 1) + fileName$ + mid(interstitialHtml$, fileNameIndex% + len("")) fileNameIndex% = instr(1, interstitialHtml$, "") end while interstitialFilePath$ = "tmp:/interstitial.html" interstitialFile = CreateObject("roCreateFile", interstitialFilePath$) interstitialFile.SendLine(interstitialHtml$) interstitialFile.Flush() interstitialFile = invalid url$ = "file:///tmp:/interstitial.html" return url$ end function Sub LoadHtmlWidgetStaticInitialization() syncSpec = GetActiveSyncSpec() activeSyncSpecSettings = GetActiveSyncSpecSettings() assetCollection = syncSpec.GetAssets("download") presentationName$ = m.bsp.sign.name$ stateName$ = m.name$ aa = { } aa.nodejs_enabled = m.isNodeServer if m.isNodeServer then m.view = CreateObject("roAssetCollectionView", m.bsp.assetPool, assetCollection) end if if m.bsp.sign.htmlEnableJavascriptConsole then aa.inspector_server = { port: 2999 } end if if CanRotateByScreen(m.bsp.sign, {}) then ' no need to rotate per zone if already rotated by screen else if IsPortraitBottomLeft(m.bsp.sign.monitorOrientation) then aa.transform = "rot90" else if IsPortraitBottomRight(m.bsp.sign.monitorOrientation) then aa.transform = "rot270" end if aa.port = m.bsp.msgPort security = { } security.websecurity = m.enableCrossDomainPolicyChecks security.camera_enabled = m.enableCamera security.insecure_https_enabled = m.ignoreHttpsCertificateErrors aa.security_params = security aa.brightsign_js_objects_enabled = m.enableBrightSignJavascriptObjects aa.mouse_enabled = m.enableMouseEvents if m.hwzOn then aa.hwz_default = "on" end if aa.javascript_enabled = true aa.focus_enabled = true if m.useUserStylesheet then fileName$ = GetPoolFilePath(m.bsp.assetPoolFiles, m.userStylesheet) aa.user_stylesheet = fileName$ end if aa.fonts = [] for each customFont in m.customFonts customFontPath$ = GetPoolFilePath(m.bsp.assetPoolFiles, customFont) aa.fonts.push(customFontPath$) next if m.isNodeServer or m.contentIsLocal then asset = { } asset.pool = m.bsp.assetPool asset.collection = assetCollection asset.uri_prefix = "/" + m.prefix$ + "/" asset.pool_prefix = m.prefix$ aa.assets = [] aa.assets.push(asset) end if if m.isNodeServer then m.url$ = GetInterstitialHtmlUrl(m.prefix$, m.filePath$) else if m.contentIsLocal then m.url$ = "file:///" + m.prefix$ + "/" + m.filePath$ else m.url$ = m.url.GetCurrentParameterValue() end if m.url$ = m.url$ + m.queryString.GetCurrentParameterValue() aa.url = m.url$ SetUserAgentForHtmlWidget(m.bsp, m.stateMachine.loadingHtmlWidget, aa) limitStorageSpace = activeSyncSpecSettings.limitStorageSpace if limitStorageSpace then storageSpaceLimits = GetStorageSpaceLimits(activeSyncSpecSettings) aa.storage_path = "browser_storage" aa.storage_quota = GetRequestedBrowserStorageSpace(storageSpaceLimits) endif if isBoolean(m.enableFileURLSharedStorage) then aa.force_shared_storage = m.enableFileURLSharedStorage else aa.force_shared_storage = true endif if isBoolean(m.enableHtmlURLSharedStorage) then aa.force_unshared_storage = not m.enableHtmlURLSharedStorage else aa.force_unshared_storage = false endif m.stateMachine.loadingHtmlWidgetParams = aa m.stateMachine.loadingHtmlWidget = CreateObject("roHtmlWidget", m.stateMachine.rectangle, aa) end sub ' TEDTODO - fix indentation Function STHTML5PlayingEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) m.LoadHtmlWidgetStaticInitialization() if not m.stateMachine.isVisible then m.stateMachine.loadingHtmlWidget.Hide() end if m.LaunchTimer() m.bsp.SetTouchRegions(m) ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "html5") ' playback logging m.stateMachine.LogPlayStart("html5", m.name$) return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else return m.MediaItemEventHandler(event, stateData) end if end if else if type(event) = "roHtmlWidgetEvent" then eventData = event.GetData() if type(eventData) = "roAssociativeArray" and type(eventData.reason) = "roString" then m.bsp.diagnostics.PrintDebug("reason = " + eventData.reason) if eventData.reason = "load-error" then m.bsp.diagnostics.PrintDebug("message = " + eventData.message) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_HTML5_LOAD_ERROR, eventData.message) if not m.contentIsLocal then m.htmlReloadTimer = CreateObject("roTimer") m.htmlReloadTimer.SetPort(m.bsp.msgPort) m.htmlReloadTimer.SetElapsed(30, 0) m.htmlReloadTimer.Start() end if else if eventData.reason = "load-finished" then if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if ' m.stateMachine.displayedHtmlWidget = m.stateMachine.loadingHtmlWidget ' m.stateMachine.ShowHtmlWidget() ' Do a swap instead of just an assignment m.stateMachine.onDisplayHtmlWidget = m.stateMachine.displayedHtmlWidget m.stateMachine.displayedHtmlWidget = m.stateMachine.loadingHtmlWidget m.stateMachine.ShowHtmlWidget() m.stateMachine.onDisplayHtmlWidget = invalid end if end if else if type(event) = "roTimerEvent" then if type(m.htmlReloadTimer) = "roTimer" and event.GetSourceIdentity() = m.htmlReloadTimer.GetIdentity() then m.bsp.diagnostics.PrintDebug("Reload Html5 widget") userdata = m.stateMachine.loadingHtmlWidget.GetUserData() m.stateMachine.loadingHtmlWidget = CreateObject("roHtmlWidget", m.stateMachine.rectangle, m.stateMachine.loadingHtmlWidgetParams) m.stateMachine.loadingHtmlWidget.SetUserData(userdata) return "HANDLED" else return m.MediaItemEventHandler(event, stateData) end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Function IsPlayingClip() as boolean return m.playingVideoClip or m.playingAudioClip or m.displayingImage end function Sub ClearPlayingClip() m.playingVideoClip = false m.playingAudioClip = false m.displayingImage = false end sub Sub ConfigureNavigationButton(navigation as object) if type(navigation) = "roAssociativeArray" then if type(navigation.bpEvent) = "roAssociativeArray" then bpEvent = navigation.bpEvent bpEvent.configuration$ = "press" m.bsp.ConfigureBPButton(bpEvent.buttonPanelIndex%, bpEvent.buttonNumber$, bpEvent) else if type(navigation.gpioEvent) = "roAssociativeArray" then gpioEvent = navigation.gpioEvent gpioEvent.configuration$ = "press" m.bsp.ConfigureGPIOButton(gpioEvent.buttonNumber$, gpioEvent) end if end if end sub Sub ScaleBackgroundImageToFit(backgroundImage as object) xScale = m.backgroundImageWidth% / m.stateMachine.width% yScale = m.backgroundImageHeight% / m.stateMachine.height% if xScale > yScale then x% = 0 y% = (m.stateMachine.height% - (m.backgroundImageHeight% / xScale)) / 2 width% = m.backgroundImageWidth% / xScale height% = m.backgroundImageHeight% / xScale else x% = (m.stateMachine.width% - (m.backgroundImageWidth% / yScale)) / 2 y% = 0 width% = m.backgroundImageWidth% / yScale height% = m.backgroundImageHeight% / yScale end if backgroundImage["targetRect"] = { x: x%, y: y%, w: width%, h: height% } end sub Sub SetBackgroundImageSizeLocation(backgroundImage as object) if m.stateMachine.imageMode% = 0 ' center image if m.backgroundImageWidth% > m.stateMachine.width% or m.backgroundImageHeight% > m.stateMachine.height% then m.ScaleBackgroundImageToFit(backgroundImage) else x% = (m.stateMachine.width% - m.backgroundImageWidth%) / 2 y% = (m.stateMachine.height% - m.backgroundImageHeight%) / 2 backgroundImage["targetRect"] = { x: x%, y: y%, w: m.backgroundImageWidth%, h: m.backgroundImageHeight% } end if else if m.stateMachine.imageMode% = 1 ' scale to fit m.ScaleBackgroundImageToFit(backgroundImage) else if m.stateMachine.imageMode% = 2 ' scale to fill and crop m.ScaleBackgroundImageToFit(backgroundImage) else if m.stateMachine.imageMode% ' scale to fill backgroundImage["targetRect"] = { x: 0, y: 0, w: m.stateMachine.width%, h: m.stateMachine.height% } end if end sub Function TemplateUsesAnyUserVariable() as boolean for each templateItem in m.templateItems if templateItem.type$ = "userVariableTemplateItem" then return true end if next end function Function TemplateUsesUserVariable(userVariable as object) as boolean for each templateItem in m.templateItems if templateItem.type$ = "userVariableTemplateItem" then if type(templateItem.userVariable) = "roAssociativeArray" then if templateItem.userVariable.name$ = userVariable.name$ then return true end if end if end if next end function Function TemplateUsesSystemVariable() as boolean for each templateItem in m.templateItems ' case 1: if system variable is directly created in the template if templateItem.type$ = "systemVariableTemplateItem" then return true ' case 2: if system variable is added as user variable and referenced in the template else if templateItem.type$ = "userVariableTemplateItem" then if getVarFromObj(templateItem, "userVariable.systemVariable$", "String", "") <> "" then return true end if next return false end function Sub BuildBaseTemplateItem(templateItem as object, content as object) content["targetRect"] = { x: templateItem.x%, y: templateItem.y%, w: templateItem.width%, h: templateItem.height% } end sub Sub BuildTextTemplateItem(templateItem as object, content as object) BuildBaseTemplateItem(templateItem, content) textAttrs = { } textAttrs.color = templateItem.foregroundTextColor$ textAttrs.fontSize = templateItem.fontSize% if templateItem.font$ <> "System" then textAttrs.fontFile = GetPoolFilePath(m.bsp.assetPoolFiles, templateItem.font$) end if textAttrs.vAlign = "Top" textAttrs.hAlign = templateItem.alignment$ textAttrs.rotation = templateItem.rotation$ content.textAttrs = textAttrs end sub Sub ClearTemplateItems() m.stateMachine.canvasWidget.EnableAutoRedraw(0) numLayers% = m.stateMachine.templateObjectsByLayer.Count() for i% = 0 to numLayers% - 1 m.stateMachine.canvasWidget.ClearLayer(i% + 1) next m.stateMachine.canvasWidget.EnableAutoRedraw(1) end sub Sub RedisplayTemplateItems() m.BuildTemplateItems() m.stateMachine.canvasWidget.EnableAutoRedraw(0) numLayers% = m.stateMachine.templateObjectsByLayer.Count() for i% = 0 to numLayers% - 1 if type(m.stateMachine.templateObjectsByLayer[i%]) = "roArray" then templateObjects = m.stateMachine.templateObjectsByLayer[i%] ' must support multiple objects per layer??!! templateObject = templateObjects[0] if type(templateObject) = "roAssociativeArray" and templateObject.DoesExist("name") then name$ = templateObject["name"] if type(m.bsp.encryptionByFile) = "roAssociativeArray" and m.bsp.encryptionByFile.DoesExist(name$) then templateObject.EncryptionAlgorithm = "AesCtrHmac" templateObject.EncryptionKey = name$ end if end if if m.bsp.contentEncrypted and type(templateObject) = "roAssociativeArray" and templateObject.DoesExist("fileNameForEncryption") then name$ = templateObject["fileNameForEncryption"] if name$ <> "" and type(m.currentFeed) = "roAssociativeArray" and type(m.currentFeed.liveDataFeed) = "roAssociativeArray" then if (m.currentFeed.liveDataFeed.isDynamicPlaylist or m.currentFeed.liveDataFeed.isLiveMediaFeed) then templateObject.EncryptionAlgorithm = "AesCtrHmac" templateObject.EncryptionKey = name$ end if end if end if m.stateMachine.canvasWidget.SetLayer(templateObject, i% + 1) else m.stateMachine.canvasWidget.ClearLayer(i% + 1) end if next for i% = numLayers% to numLayers% + 2 m.stateMachine.canvasWidget.ClearLayer(i% + 1) next m.stateMachine.canvasWidget.EnableAutoRedraw(1) end sub Sub BuildTemplateItems() m.stateMachine.templateObjectsByLayer = CreateObject("roArray", 1, true) for each templateItem in m.templateItems text = invalid image = invalid backgroundLayer% = (templateItem.layer% - 1) * 2 + 1 contentLayer% = backgroundLayer% + 1 if templateItem.type$ = "constantTextTemplateItem" then text = { } text["text"] = templateItem.textString$ m.BuildTextTemplateItem(templateItem, text) else if templateItem.type$ = "systemVariableTemplateItem" then videoConnector$ = getVarFromObj(templateItem, "videoConnector$", "roString", "") suffix$ = "$" if videoConnector$ <> "" then suffix$ = "_" + videoConnector$ + "$" text = { } if templateItem.systemVariableType$ = "SerialNumber" then text["text"] = m.bsp.sysInfo.deviceUniqueID$ else if templateItem.systemVariableType$ = "IPAddressWired" then text["text"] = m.bsp.sysInfo.ipAddressWired$ else if templateItem.systemVariableType$ = "IPAddressWireless" then text["text"] = m.bsp.sysInfo.ipAddressWireless$ else if templateItem.systemVariableType$ = "FirmwareVersion" then text["text"] = m.bsp.sysInfo.deviceFWVersion$ else if templateItem.systemVariableType$ = "ScriptVersion" then text["text"] = m.bsp.sysInfo.autorunVersion$ else if templateItem.systemVariableType$ = "RFChannelCount" then text["text"] = StripLeadingSpaces(stri(m.bsp.scannedChannels.Count())) else if templateItem.systemVariableType$ = "RFChannelName" then text["text"] = StripLeadingSpaces(m.bsp.rfChannelName) else if templateItem.systemVariableType$ = "RFVirtualChannel" then text["text"] = StripLeadingSpaces(m.bsp.rfVirtualChannel) else if templateItem.systemVariableType$ = "EdidMonitorSerialNumber" then text["text"] = m.bsp.sysInfo["edidMonitorSerialNumber"+suffix$] else if templateItem.systemVariableType$ = "EdidYearOfManufacture" then text["text"] = m.bsp.sysInfo["edidYearOfManufacture"+suffix$] else if templateItem.systemVariableType$ = "EdidMonitorName" then text["text"] = m.bsp.sysInfo["edidMonitorName"+suffix$] else if templateItem.systemVariableType$ = "EdidManufacturer" then text["text"] = m.bsp.sysInfo["edidManufacturer"+suffix$] else if templateItem.systemVariableType$ = "EdidUnspecifiedText" then text["text"] = m.bsp.sysInfo["edidUnspecifiedText"+suffix$] else if templateItem.systemVariableType$ = "EdidSerialNumber" then text["text"] = m.bsp.sysInfo["edidSerialNumber"+suffix$] else if templateItem.systemVariableType$ = "EdidManufacturerProductCode" then text["text"] = m.bsp.sysInfo["edidManufacturerProductCode"+suffix$] else if templateItem.systemVariableType$ = "EdidWeekOfManufacture" then text["text"] = m.bsp.sysInfo["edidWeekOfManufacture"+suffix$] else if templateItem.systemVariableType$ = "ActivePresentation" then if IsString(m.bsp.activePresentation$) then text["text"] = m.bsp.activePresentation$ else text["text"] = "" end if else if templateItem.systemVariableType$ = "BrightAuthorVersion" then text["text"] = m.bsp.sysInfo.baconVersion$ else if templateItem.systemVariableType$ = "SessionGuid" then text["text"] = m.bsp.sysInfo.sessionGuid$ else if templateItem.systemVariableType$ = "PlayerModelNumber" then text["text"] = m.bsp.sysInfo.deviceModel$ end if m.BuildTextTemplateItem(templateItem, text) else if templateItem.type$ = "mediaCounterTemplateItem" or templateItem.type$ = "userVariableTemplateItem" then if type(templateItem.userVariable) = "roAssociativeArray" then text = { } text["text"] = templateItem.userVariable.GetCurrentValue() m.BuildTextTemplateItem(templateItem, text) end if else if templateItem.type$ = "indexedLiveTextDataItem" or templateItem.type$ = "titledLiveTextDataItem" then liveDataFeed = templateItem.liveDataFeed if m.liveDataFeeds.DoesExist(liveDataFeed.id$) then if templateItem.type$ = "indexedLiveTextDataItem" then indexStr$ = templateItem.index.GetCurrentParameterValue() index% = int(val(indexStr$)) if index% > 0 then index% = index% - 1 end if if type(liveDataFeed.articles) = "roArray" then if index% <= (liveDataFeed.articles.count() - 1) then textValue$ = liveDataFeed.articles[index%] else textValue$ = "" end if text = { } text["text"] = textValue$ m.BuildTextTemplateItem(templateItem, text) end if else title$ = templateItem.title.GetCurrentParameterValue() if type(liveDataFeed.articlesByTitle) = "roAssociativeArray" then if liveDataFeed.articlesByTitle.DoesExist(title$) then textValue$ = liveDataFeed.articlesByTitle.Lookup(title$) else textValue$ = "" end if text = { } text["text"] = textValue$ m.BuildTextTemplateItem(templateItem, text) end if end if end if else if templateItem.type$ = "imageTemplateItem" then image = { } image["name"] = templateItem.fileName$ image["filename"] = GetPoolFilePath(m.bsp.assetPoolFiles, templateItem.fileName$) image["CompositionMode"] = "source_over" ' TemplateToDo - do the images care about the stretch/crop mode? ' m.SetBackgroundImageSizeLocation(backgroundImage) BuildBaseTemplateItem(templateItem, image) else if templateItem.type$ = "mrssTextTemplateItem" then if type(templateItem.textString$) <> "Invalid" then text = { } text["text"] = templateItem.textString$ m.BuildTextTemplateItem(templateItem, text) end if else if templateItem.type$ = "mrssImageTemplateItem" or templateItem.type$ = "mrssMediaTemplateItem" then if type(templateItem.fileName$) <> "Invalid" and templateItem.fileName$ <> "" then image = { } if type(templateItem.fileNameForEncryption) = "roString" then image["fileNameForEncryption"] = templateItem.fileNameForEncryption else image["fileNameForEncryption"] = "" end if image["filename"] = templateItem.fileName$ image["CompositionMode"] = "source-over" ' TemplateToDo - do the images care about the stretch/crop mode? ' m.SetBackgroundImageSizeLocation(backgroundImage) BuildBaseTemplateItem(templateItem, image) end if end if m.BuildTemplateItem(text, image, templateItem) next ' now add any simple rss items if type(m.simpleRSSTemplateItems) = "roAssociativeArray" then for each simpleRSSId in m.simpleRSSTemplateItems simpleRSS = m.simpleRSSTemplateItems.Lookup(simpleRSSId) if type(simpleRSS) = "roAssociativeArray" then liveDataFeed = simpleRSS.rssLiveDataFeeds[simpleRSS.currentLiveDataFeedIndex%] if m.liveDataFeeds.DoesExist(liveDataFeed.id$) then if type(liveDataFeed.articles) = "roArray" then if simpleRSS.currentIndex% >= liveDataFeed.articles.count() then simpleRSS.currentIndex% = 0 end if index% = simpleRSS.currentIndex% ' remove the next conditional - it's not needed if index% <= (liveDataFeed.articles.count() - 1) then for each templateItem in simpleRSS.items if lcase(templateItem.elementName$) = "title" then textValue$ = liveDataFeed.articleTitles[index%] else textValue$ = liveDataFeed.articles[index%] end if text = { } text["text"] = textValue$ m.BuildTextTemplateItem(templateItem, text) m.BuildTemplateItem(text, image, templateItem) next ' first time display - start timer to display next item if type(simpleRSS.rssItemTimer) <> "roTimer" then simpleRSS.rssItemTimer = CreateObject("roTimer") simpleRSS.rssItemTimer.SetPort(m.stateMachine.msgPort) simpleRSS.rssItemTimer.SetElapsed(simpleRSS.displayTime%, 0) simpleRSS.rssItemTimer.Start() end if end if end if end if end if next end if end sub Sub BuildTemplateItem(text as object, image as object, templateItem as object) if type(text) = "roAssociativeArray" or type(image) = "roAssociativeArray" then backgroundLayer% = (templateItem.layer% - 1) * 2 + 1 contentLayer% = backgroundLayer% + 1 if type(m.stateMachine.templateObjectsByLayer[contentLayer%]) <> "roArray" then m.stateMachine.templateObjectsByLayer[contentLayer%] = CreateObject("roArray", 1, true) end if if type(text) = "roAssociativeArray" then m.stateMachine.templateObjectsByLayer[contentLayer%].push(text) if templateItem.backgroundColorSpecified then backgroundColor = { } backgroundColor["color"] = templateItem.backgroundTextColor$ backgroundColor["targetRect"] = { x: templateItem.x%, y: templateItem.y%, w: templateItem.width%, h: templateItem.height% } if type(m.stateMachine.templateObjectsByLayer[backgroundLayer%]) <> "roArray" then m.stateMachine.templateObjectsByLayer[backgroundLayer%] = CreateObject("roArray", 1, true) end if m.stateMachine.templateObjectsByLayer[backgroundLayer%].push(backgroundColor) end if else m.stateMachine.templateObjectsByLayer[contentLayer%].push(image) end if end if end sub Sub SetupTemplateMRSS() if type(m.mrssTitleTemplateItem) = "roAssociativeArray" then m.mrssTitleTemplateItem.textString$ = invalid end if if type(m.mrssDescriptionTemplateItem) = "roAssociativeArray" then m.mrssDescriptionTemplateItem.textString$ = invalid end if if type(m.mrssImageTemplateItem) = "roAssociativeArray" or type(m.mrssMediaTemplateItem) = "roAssociativeArray" then m.mrssMediaTemplateItem.fileName$ = invalid end if if type(m.mrssCustomFieldTemplateItems) = "roAssociativeArray" then for each customFieldName in m.mrssCustomFieldTemplateItems customFieldTemplateItem = m.mrssCustomFieldTemplateItems[customFieldName] customFieldTemplateItem.textString$ = invalid next end if end sub Function STTemplatePlayingEventHandler(event as object, stateData as object) as object MEDIA_END = 8 stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") if m.mrssActive then m.SetupTemplateMRSS() m.currentFeed = invalid m.pendingFeed = invalid m.mrssItemTimer = CreateObject("roTimer") m.mrssItemTimer.SetPort(m.stateMachine.msgPort) if type(m.mrssLiveDataFeeds) = "roArray" and m.mrssLiveDataFeeds.Count() > 0 then m.mrssLiveDataFeed = invalid m.currentFeed = invalid m.LaunchWaitForContentTimer() end if end if ' reset indices on entry to the state if type(m.simpleRSSTemplateItems) = "roAssociativeArray" then for each simpleRSSId in m.simpleRSSTemplateItems simpleRSS = m.simpleRSSTemplateItems.Lookup(simpleRSSId) if type(simpleRSS) = "roAssociativeArray" then simpleRSS.currentIndex% = 0 simpleRSS.currentLiveDataFeedIndex% = 0 simpleRSS.rssItemTimer = invalid end if next end if m.liveDataFeeds = { } for each liveDataFeedId in m.bsp.liveDataFeeds liveDataFeed = m.bsp.liveDataFeeds.Lookup(liveDataFeedId) m.liveDataFeeds.AddReplace(liveDataFeedId, liveDataFeed) next m.ConfigureBPButtons() m.ConfigureGPIOButtons() m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if if type(m.stateMachine.canvasWidget) <> "roCanvasWidget" then r = CreateScaledRectangle(m.stateMachine.x%, m.stateMachine.y%, m.stateMachine.width%, m.stateMachine.height%) m.stateMachine.canvasWidget = CreateObject("roCanvasWidget", r) end if m.stateMachine.canvasWidget.EnableAutoRedraw(0) maxLayer% = 1 if type(m.stateMachine.templateObjectsByLayer) = "roArray" then maxLayer% = m.stateMachine.templateObjectsByLayer.Count() end if for i% = 1 to maxLayer% m.stateMachine.canvasWidget.ClearLayer(i%) next ' display background image if it exists file$ = m.backgroundImage$ if file$ <> "" then backgroundImage = { } backgroundImage["name"] = file$ backgroundImage["filename"] = GetPoolFilePath(m.bsp.assetPoolFiles, file$) backgroundImage["CompositionMode"] = "source" if m.backgroundImageWidth% <= 0 or m.backgroundImageHeight% <= 0 then backgroundImage["targetRect"] = { x: 0, y: 0, w: m.stateMachine.width%, h: m.stateMachine.height% } else m.SetBackgroundImageSizeLocation(backgroundImage) end if if backgroundImage.DoesExist("name") then name$ = backgroundImage["name"] if type(m.bsp.encryptionByFile) = "roAssociativeArray" and m.bsp.encryptionByFile.DoesExist(name$) then 'if m.bsp.contentEncrypted then backgroundImage.EncryptionAlgorithm = "AesCtrHmac" backgroundImage.EncryptionKey = name$ end if end if m.stateMachine.canvasWidget.SetLayer(backgroundImage, 0) else m.stateMachine.canvasWidget.ClearLayer(0) end if ' build arrays of template items & background colors m.BuildTemplateItems() numLayers% = m.stateMachine.templateObjectsByLayer.Count() for i% = 0 to numLayers% - 1 if type(m.stateMachine.templateObjectsByLayer[i%]) = "roArray" then templateObjects = m.stateMachine.templateObjectsByLayer[i%] ' must support multiple objects per layer??!! templateObject = templateObjects[0] if type(templateObject) = "roAssociativeArray" and templateObject.DoesExist("name") then name$ = templateObject["name"] if type(m.bsp.encryptionByFile) = "roAssociativeArray" and m.bsp.encryptionByFile.DoesExist(name$) then templateObject.EncryptionAlgorithm = "AesCtrHmac" templateObject.EncryptionKey = name$ end if end if m.stateMachine.canvasWidget.SetLayer(templateObject, i% + 1) end if next m.stateMachine.canvasWidget.EnableAutoRedraw(1) m.stateMachine.ShowCanvasWidget() m.LaunchTimer() m.bsp.SetTouchRegions(m) ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "liveText") ' playback logging m.stateMachine.LogPlayStart("liveText", "") return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") ' resize the video player in case it was used by an MRSS feed within this state if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() m.stateMachine.videoPlayer.SetRectangle(m.stateMachine.rectangle) end if m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else if event["EventType"] = "USER_VARIABLES_UPDATED" then if m.TemplateUsesAnyUserVariable() then m.RedisplayTemplateItems() end if return "HANDLED" else if event["EventType"] = "SYSTEM_VARIABLE_UPDATED" then if m.TemplateUsesSystemVariable() then m.RedisplayTemplateItems() end if return "HANDLED" else if event["EventType"] = "USER_VARIABLE_CHANGE" then userVariable = event["UserVariable"] if m.TemplateUsesUserVariable(userVariable) then m.RedisplayTemplateItems() end if return "HANDLED" else if event["EventType"] = "USER_VARIABLES_RESET" then m.RedisplayTemplateItems() return "HANDLED" else if event["EventType"] = "LIVE_DATA_FEED_UPDATE" then liveDataFeed = event["EventData"] m.liveDataFeeds.AddReplace(liveDataFeed.id$, liveDataFeed) m.RedisplayTemplateItems() return "HANDLED" else return m.MediaItemEventHandler(event, stateData) end if end if else if type(event) = "roVideoEvent" and type(m.stateMachine.videoPlayer) = "roVideoPlayer" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() and event.GetInt() = MEDIA_END then m.GetMRSSTemplateItem() return "HANDLED" else if type(event) = "roTimerEvent" then if type(m.waitForContentTimer) = "roTimer" and event.GetSourceIdentity() = m.waitForContentTimer.GetIdentity() then if m.FindMRSSContent() then m.GetMRSSTemplateItem() end if return "HANDLED" end if if type(m.mrssItemTimer) = "roTimer" and event.GetSourceIdentity() = m.mrssItemTimer.GetIdentity() then m.GetMRSSTemplateItem() return "HANDLED" end if if type(m.simpleRSSTemplateItems) = "roAssociativeArray" then for each simpleRSSId in m.simpleRSSTemplateItems simpleRSS = m.simpleRSSTemplateItems.Lookup(simpleRSSId) if type(simpleRSS) = "roAssociativeArray" then if type(simpleRSS.rssItemTimer) = "roTimer" then if event.GetSourceIdentity() = simpleRSS.rssItemTimer.GetIdentity() then itemExists = false liveDataFeed = simpleRSS.rssLiveDataFeeds[simpleRSS.currentLiveDataFeedIndex%] if m.liveDataFeeds.DoesExist(liveDataFeed.id$) then if type(liveDataFeed.articles) = "roArray" then simpleRSS.currentIndex% = simpleRSS.currentIndex% + 1 if simpleRSS.currentIndex% >= liveDataFeed.articles.count() then simpleRSS.currentIndex% = 0 simpleRSS.currentLiveDataFeedIndex% = simpleRSS.currentLiveDataFeedIndex% + 1 if simpleRSS.currentLiveDataFeedIndex% >= simpleRSS.rssLiveDataFeeds.Count() then simpleRSS.currentLiveDataFeedIndex% = 0 end if liveDataFeed = simpleRSS.rssLiveDataFeeds[simpleRSS.currentLiveDataFeedIndex%] if m.liveDataFeeds.DoesExist(liveDataFeed.id$) then if type(liveDataFeed.articles) = "roArray" then itemExists = true end if end if else itemExists = true end if end if end if if itemExists then m.RedisplayTemplateItems() ' restart timer simpleRSS.rssItemTimer.SetElapsed(simpleRSS.displayTime%, 0) simpleRSS.rssItemTimer.Start() end if return "HANDLED" end if end if end if next end if return m.MediaItemEventHandler(event, stateData) else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Function FindMRSSContent() as boolean ' get the mrss live data feed to start searching with if m.mrssLiveDataFeed = invalid then mrssLiveDataFeedIndex% = 0 else mrssLiveDataFeedIndex% = m.mrssLiveDataFeedIndex% end if startingIndex% = mrssLiveDataFeedIndex% while true mrssLiveDataFeed = m.mrssLiveDataFeeds[mrssLiveDataFeedIndex%] if type(mrssLiveDataFeed.assetPoolFiles) = "roAssetPoolFiles" then currentFeed = mrssLiveDataFeed.feed if currentFeed.AllContentExists(mrssLiveDataFeed.assetPoolFiles) then m.mrssLiveDataFeed = mrssLiveDataFeed m.currentFeed = m.mrssLiveDataFeed.feed m.mrssLiveDataFeedIndex% = mrssLiveDataFeedIndex% m.displayIndex = 0 return true end if end if mrssLiveDataFeedIndex% = mrssLiveDataFeedIndex% + 1 if mrssLiveDataFeedIndex% >= m.mrssLiveDataFeeds.Count() then mrssLiveDataFeedIndex% = 0 end if if mrssLiveDataFeedIndex% = startingIndex% then ' search has wrapped around - nothing was found - continue waiting m.LaunchWaitForContentTimer() return false end if end while end function Function GetNextMRSSTemplateItem() as object if m.currentFeed.items.Count() = 0 then return invalid end if foundItem = false while not foundItem if m.displayIndex >= m.currentFeed.items.Count() then m.mrssLiveDataFeedIndex% = m.mrssLiveDataFeedIndex% + 1 if m.mrssLiveDataFeedIndex% >= m.mrssLiveDataFeeds.Count() then m.mrssLiveDataFeedIndex% = 0 end if contentFound = m.FindMRSSContent() if not contentFound then return invalid end if end if displayItem = m.currentFeed.items[m.displayIndex] if displayItem = invalid then return invalid end if filePath$ = GetPoolFilePath(m.mrssLiveDataFeed.assetPoolFiles, displayItem.url) if filePath$ <> "" then foundItem = true displayItem.filePath$ = filePath$ end if m.displayIndex = m.displayIndex + 1 end while return displayItem end function Sub GetMRSSTemplateItem() item = m.GetNextMRSSTemplateItem() if type(item) <> "roAssociativeArray" then ' no valid content - clear old content if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if if type(m.stateMachine.imagePlayer) = "roImageWidget" then m.stateMachine.imagePlayer.StopDisplay() end if m.ClearTemplateItems() m.LaunchWaitForContentTimer() return end if ' check to see if the item is an image or a video. ' if an image, set a timer ' if a video, need to wait for media end event if isImage(item) then m.mrssItemTimer.SetElapsed(item.duration, 0) m.mrssItemTimer.Start() end if if type(m.mrssTitleTemplateItem) = "roAssociativeArray" then m.mrssTitleTemplateItem.textString$ = item.title end if if type(m.mrssDescriptionTemplateItem) = "roAssociativeArray" then m.mrssDescriptionTemplateItem.textString$ = item.description end if ' need to distinguish here between image items and video items m.mrssVideoTemplateItem = { } if type(m.mrssMediaTemplateItem) = "roAssociativeArray" then if isImage(item) then m.mrssMediaTemplateItem.fileName$ = item.filePath$ m.mrssMediaTemplateItem.fileNameForEncryption = item.title m.mrssVideoTemplateItem.fileName$ = "" else m.mrssMediaTemplateItem.fileName$ = "" m.mrssVideoTemplateItem.fileName$ = item.filePath$ m.mrssVideoTemplateItem.fileNameForEncryption = item.title end if end if if type(m.mrssCustomFieldTemplateItems) = "roAssociativeArray" then for each customFieldName in m.mrssCustomFieldTemplateItems customFieldTemplateItem = m.mrssCustomFieldTemplateItems[customFieldName] ' see if the corresponding custom field exists in this item customFieldValue = item.mrssCustomFields.lookup(customFieldName) if type(customFieldValue) = "roString" then customFieldTemplateItem.textString$ = customFieldValue end if next end if ' different/additional path needed to display video if type(m.mrssVideoTemplateItem) = "roAssociativeArray" and m.mrssVideoTemplateItem.fileName$ <> invalid and m.mrssVideoTemplateItem.fileName$ <> "" and type(m.stateMachine.videoPlayer) = "roVideoPlayer" then ' ensure that the image gets cleared item = m.mrssMediaTemplateItem r = CreateScaledRectangle(item.x% + m.stateMachine.rectangle.getX(), item.y% + m.stateMachine.rectangle.getY(), item.width%, item.height%) m.stateMachine.videoPlayer.SetRectangle(r) m.videoItem = { } m.videoItem.videoDisplayMode% = 0 m.videoItem.automaticallyLoop = false m.videoItem.fileName$ = m.mrssVideoTemplateItem.fileNameForEncryption m.videoItem.filePath$ = m.mrssVideoTemplateItem.fileName$ m.stateMachine.preloadState = invalid m.stateMachine.syncInfo = invalid m.videoItem.userVariable = invalid m.videoTimeCodeEvents = invalid m.videoItem.isEncrypted = false if m.bsp.contentEncrypted and type(m.currentFeed) = "roAssociativeArray" and type(m.currentFeed.liveDataFeed) = "roAssociativeArray" then if m.currentFeed.liveDataFeed.isDynamicPlaylist or m.currentFeed.liveDataFeed.isLiveMediaFeed then m.videoItem.isEncrypted = true end if end if m.PlayVideo(false, true) else if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if end if m.RedisplayTemplateItems() end sub Function STPlayingMediaRSSEventHandler(event as object, stateData as object) as object MEDIA_START = 3 MEDIA_END = 8 stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") ' execute entry commands; perform other setup functions m.firstItemDisplayed = false m.PreDrawImage() ' set default transition if type(m.stateMachine.imagePlayer) = "roImageWidget" then m.stateMachine.imagePlayer.SetDefaultTransition(m.slideTransition%) end if m.currentFeed = invalid m.pendingFeed = invalid ' see if the designated feed has already been downloaded (doesn't imply content exists) if type(m.liveDataFeed) <> "roAssociativeArray" stop if type(m.liveDataFeed.assetPoolFiles) = "roAssetPoolFiles" then ' create local versions of key objects m.assetCollection = m.liveDataFeed.assetCollection m.assetPoolFiles = m.liveDataFeed.assetPoolFiles m.currentFeed = m.liveDataFeed.feed ' protect the feed that is getting displayed m.ProtectMRSSFeed("display-" + m.liveDataFeed.id$, m.assetCollection) m.displayIndex = 0 ' distinguish between a feed that has no content and a feed in which no content has been downloaded if m.currentFeed.items.Count() = 0 or not m.currentFeed.AllContentExists(m.assetPoolFiles) then ' no content in feed - send a message to self to trigger exit from state (like video playback failure) mrssNotFullyLoadedPlaybackEvent = { } mrssNotFullyLoadedPlaybackEvent["EventType"] = "MRSSNotFullyLoadedPlaybackEvent" mrssNotFullyLoadedPlaybackEvent["EventParameter"] = m.liveDataFeed.id$ m.stateMachine.msgPort.PostMessage(mrssNotFullyLoadedPlaybackEvent) else m.AdvanceToNextMRSSItem() end if else ' this situation will occur when the feed itself has not downloaded yet - send a message to self to trigger exit from state (like video playback failure) mrssNotFullyLoadedPlaybackEvent = { } mrssNotFullyLoadedPlaybackEvent["EventType"] = "MRSSNotFullyLoadedPlaybackEvent" mrssNotFullyLoadedPlaybackEvent["EventParameter"] = m.liveDataFeed.id$ m.stateMachine.msgPort.PostMessage(mrssNotFullyLoadedPlaybackEvent) return "HANDLED" end if m.LaunchTimer() m.bsp.SetTouchRegions(m) m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "mrss") return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) return "HANDLED" else if event["EventType"] = "VideoPlaybackFailureEvent" then if type(m.currentFeed) <> "roAssociativeArray" then return "HANDLED" end if if m.AtEndOfFeed() and type(m.signChannelEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.signChannelEndEvent, stateData, "") else m.AdvanceToNextMRSSItem() return "HANDLED" end if else if event["EventType"] = "MRSSNotFullyLoadedPlaybackEvent" then liveDataFeedId$ = event["EventParameter"] if liveDataFeedId$ = m.liveDataFeed.id$ then if type(m.signChannelEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.signChannelEndEvent, stateData, "") else if type(m.currentFeed) = "roAssociativeArray" and m.currentFeed.ContentExists(m.assetPoolFiles) then m.AdvanceToNextMRSSItem() '' redundant check '' else if type(m.currentFeed) = "roAssociativeArray" and type(m.currentFeed.items) = "roArray" and m.currentFeed.items.Count() = 0 then '' m.LaunchWaitForContentTimer() else m.LaunchWaitForContentTimer() end if end if return "HANDLED" else if event["EventType"] = "MRSS_SPEC_UPDATED" then updatedLiveDataFeed = event["LiveDataFeed"] if updatedLiveDataFeed.id$ = m.liveDataFeed.id$ then m.liveDataFeed = updatedLiveDataFeed ' this seems completely unnecessary if type(m.currentFeed) <> "roAssociativeArray" or (not m.currentFeed.ContentExists(m.assetPoolFiles)) then ' this is the first time that data is available m.pendingFeed = invalid m.currentFeed = m.liveDataFeed.feed m.assetCollection = m.liveDataFeed.assetCollection m.assetPoolFiles = m.liveDataFeed.assetPoolFiles ' protect the feed that is getting displayed m.ProtectMRSSFeed("display-" + m.liveDataFeed.id$, m.assetCollection) ' feed may have been downloaded but it might not have content yet (empty mrss feed) ' or feed has been downloaded but not all of its content has been downloaded yet - in this case, move on to the next item if possible if m.currentFeed.items.Count() = 0 or not m.currentFeed.AllContentExists(m.assetPoolFiles) if type(m.currentFeed) = "roAssociativeArray" and m.currentFeed.ContentExists(m.assetPoolFiles) then m.AdvanceToNextMRSSItem() else if type(m.signChannelEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.signChannelEndEvent, stateData, "") else m.LaunchWaitForContentTimer() return "HANDLED" end if end if ' all content exists - display an item m.displayIndex = 0 m.AdvanceToNextMRSSItem() else ' feed was updated. play through existing feed until it reaches the end; then switch to new feed. ' note - this does not imply that the feed actually changed. m.pendingFeed = m.liveDataFeed.feed m.pendingAssetCollection = m.liveDataFeed.assetCollection m.pendingAssetPoolFiles = m.liveDataFeed.assetPoolFiles end if end if return "HANDLED" else return m.MediaItemEventHandler(event, stateData) end if end if else if type(event) = "roHtmlWidgetEvent" and type(m.stateMachine.loadingHtmlWidget) = "roHtmlWidget" then eventData = event.GetData() if type(eventData) = "roAssociativeArray" and type(eventData.reason) = "roString" then userData = event.GetUserData() if userData <> invalid and userData.stateId$ <> invalid and userData.stateId$ = m.id$ then m.bsp.diagnostics.PrintDebug("reason = " + eventData.reason) if eventData.reason = "load-error" then m.bsp.diagnostics.PrintDebug("message = " + eventData.message) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_HTML5_LOAD_ERROR, eventData.message) m.AdvanceToNextMRSSItem() else if eventData.reason = "load-finished" then if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if ' m.stateMachine.displayedHtmlWidget = m.stateMachine.loadingHtmlWidget ' m.stateMachine.ShowHtmlWidget() ' Do a swap instead of just an assignment m.stateMachine.onDisplayHtmlWidget = m.stateMachine.displayedHtmlWidget m.stateMachine.displayedHtmlWidget = m.stateMachine.loadingHtmlWidget m.stateMachine.ShowHtmlWidget() m.stateMachine.onDisplayHtmlWidget = invalid end if end if end if else if type(event) = "roTimerEvent" then if type(m.imageTimeoutTimer) = "roTimer" and event.GetSourceIdentity() = m.imageTimeoutTimer.GetIdentity() then if m.AtEndOfFeed() and type(m.signChannelEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.signChannelEndEvent, stateData, "") else m.AdvanceToNextMRSSItem() return "HANDLED" end if else if type(m.waitForContentTimer) = "roTimer" and event.GetSourceIdentity() = m.waitForContentTimer.GetIdentity() then if type(m.currentFeed) <> "roAssociativeArray" or not m.currentFeed.AllContentExists(m.assetPoolFiles) then if type(m.currentFeed) = "roAssociativeArray" and m.currentFeed.ContentExists(m.assetPoolFiles) then if m.displayIndex = invalid then m.displayIndex = 0 end if m.AdvanceToNextMRSSItem() else m.LaunchWaitForContentTimer() end if else if type(m.currentFeed) = "roAssociativeArray" and type(m.currentFeed.items) = "roArray" and m.currentFeed.items.Count() = 0 then m.LaunchWaitForContentTimer() else m.displayIndex = 0 m.AdvanceToNextMRSSItem() end if return "HANDLED" end if return m.MediaItemEventHandler(event, stateData) else if type(event) = "roVideoEvent" and type(m.stateMachine.videoPlayer) = "roVideoPlayer" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() and event.GetInt() = MEDIA_END then if m.AtEndOfFeed() and type(m.signChannelEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.signChannelEndEvent, stateData, "") else m.AdvanceToNextMRSSItem() return "HANDLED" end if else if m.stateMachine.type$ = "EnhancedAudio" and type(event) = "roAudioEventMx" then if event.GetInt() = MEDIA_START then if not (m.AtEndOfFeed() and type(m.signChannelEndEvent) = "roAssociativeArray") then m.AdvanceToNextMRSSItem() return "HANDLED" end if else if event.GetInt() = MEDIA_END then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.AtEndOfFeed() and type(m.signChannelEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.signChannelEndEvent, stateData, "") else m.AdvanceToNextMRSSItem() return "HANDLED" end if end if else if IsAudioEvent(m.stateMachine, event) and event.GetInt() = MEDIA_END then if m.AtEndOfFeed() and type(m.signChannelEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.signChannelEndEvent, stateData, "") else m.AdvanceToNextMRSSItem() return "HANDLED" end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Sub LaunchWaitForContentTimer() if type(m.waitForContentTimer) = "roTimer" then m.waitForContentTimer.Stop() else m.waitForContentTimer = CreateObject("roTimer") m.waitForContentTimer.SetPort(m.bsp.msgPort) end if m.waitForContentTimer.SetElapsed(1, 0) m.waitForContentTimer.Start() end sub ' need to also consider the case where it's not at the end but there's no more content. Function AtEndOfFeed() as boolean return m.displayIndex >= m.currentFeed.items.Count() end function Sub AdvanceToNextMRSSItem() displayedItem = false while not displayedItem if m.displayIndex >= m.currentFeed.items.Count() then m.displayIndex = 0 ' switch to new feed if available if type(m.pendingFeed) = "roAssociativeArray" then m.currentFeed = m.pendingFeed m.assetCollection = m.pendingAssetCollection m.assetPoolFiles = m.pendingAssetPoolFiles m.pendingFeed = invalid ' protect the feed that we're switching to m.ProtectMRSSFeed("display-" + m.liveDataFeed.id$, m.assetCollection) if m.currentFeed.items.Count() = 0 or not m.currentFeed.AllContentExists(m.assetPoolFiles) then if type(m.currentFeed) = "roAssociativeArray" and m.currentFeed.ContentExists(m.assetPoolFiles) then if m.displayIndex = invalid then m.displayIndex = 0 end if m.AdvanceToNextMRSSItem() else m.LaunchWaitForContentTimer() end if return end if end if end if displayItem = m.currentFeed.items[m.displayIndex] if isHtml(displayItem) then if displayItem.type = "application/widget" then filePath$ = GetPoolFilePath(m.assetPoolFiles, displayItem.url) if filePath$ <> "" then widgetPath$ = m.GetHtmlWidgetFilePath(displayItem, filePath$) if widgetPath$ <> invalid then m.DisplayMRSSItem(displayItem, widgetPath$) displayedItem = true end if end if else if displayItem.type = "text/html" then m.DisplayMRSSItem(displayItem, displayItem.url) displayedItem = true end if else filePath$ = GetPoolFilePath(m.assetPoolFiles, displayItem.url) if filePath$ <> "" then m.ProtectMRSSItem(displayItem) ' with the current code, this may be unnecessary since the entire feed is protected. m.DisplayMRSSItem(displayItem, filePath$) ' check return value before doing this?? displayedItem = true end if end if m.displayIndex = m.displayIndex + 1 end while end sub Sub ProtectMRSSFeed(name$ as string, assets as object) if not m.bsp.feedPool.ProtectAssets(name$, assets) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.logging.FlushLogFile() m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + m.bsp.feedPool.GetFailureReason()) stop end if end sub Sub ProtectMRSSItem(displayItem as object) m.playingItemAssetCollection = CreateObject("roAssetCollection") asset = { } asset.link = displayItem.url asset.name = displayItem.url if IsNonEmptyString(displayItem.guid) then asset.change_hint = displayItem.guid else if IsString(displayItem.url) then asset.change_hint = displayItem.url end if m.playingItemAssetCollection.AddAsset(asset) ' m.ProtectMRSSFeed( "playing-" + m.currentFeed.title, m.playingItemAssetCollection ) m.ProtectMRSSFeed("playing-" + m.liveDataFeed.id$, m.playingItemAssetCollection) end sub Sub DisplayMRSSItem(displayItem as object, filePath$ as string) if ItemIsEncrypted(displayItem) and (m.liveDataFeed.isDynamicPlaylist or m.liveDataFeed.isLiveMediaFeed) isEncrypted = true else isEncrypted = false end if if isImage(displayItem) then m.imageItem = { } if IsString(displayItem.title) then m.imageItem.fileName$ = displayItem.title else m.imageItem.fileName$ = "noTitle" end if m.imageItem.filePath$ = filePath$ m.imageItem.userVariable = invalid m.imageItem.imageTimeout% = displayItem.duration m.imageItem.isEncrypted = isEncrypted ' m.DisplayImage("mrss") m.DrawImage(false) if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then m.stateMachine.videoPlayer.StopClear() end if if type(m.imageTimeoutTimer) = "roTimer" then m.imageTimeoutTimer.Stop() end if if type(m.imageTimeoutTimer) <> "roTimer" then m.imageTimeoutTimer = CreateObject("roTimer") m.imageTimeoutTimer.SetPort(m.stateMachine.msgPort) end if m.imageTimeoutTimer.SetElapsed(m.imageItem.imageTimeout%, 0) m.imageTimeoutTimer.Start() ' Set transition after image display to be default transition if type(m.stateMachine.imagePlayer) = "roImageWidget" then m.stateMachine.imagePlayer.SetDefaultTransition(m.slideTransition%) end if else if isAudio(displayItem) fileName$ = displayItem.title if type(m.stateMachine.audioPlayer) = "roAudioPlayerMx" then track = { } track["Filename"] = filePath$ track["QueueNext"] = 1 fadeLength% = m.stateMachine.fadelength% * 1000 track["FadeInLength"] = fadeLength% track["FadeOutLength"] = fadeLength% if not m.firstItemDisplayed then track["FadeCurrentPlayNext"] = 0 m.firstItemDisplayed = true end if ok = m.stateMachine.audioPlayer.PlayFile(track) m.stateMachine.LogPlayStart("audioMx", fileName$) else player = invalid if type(m.stateMachine.audioPlayer) = "roAudioPlayer" then player = m.stateMachine.audioPlayer else if type(m.stateMachine.videoPlayer) = "roVideoPlayer" then player = m.stateMachine.videoPlayer end if if player <> invalid then player.SetLoopMode(0) aa = { } aa.AddReplace("Filename", filePath$) if type(displayItem.probeData) = "roString" and displayItem.probeData <> "" then aa.AddReplace("ProbeString", displayItem.probeData) end if ok = player.PlayFile(aa) if ok = 0 then m.bsp.diagnostics.PrintDebug("Error playing audio file: " + fileName$ + ", " + filePath$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_PLAYBACK_FAILURE, fileName$) end if ' playback logging m.stateMachine.LogPlayStart("audio", fileName$) end if end if else if isHtml(displayItem) aa = { } if m.bsp.sign.htmlEnableJavascriptConsole then aa.inspector_server = { port: 2999 } end if if CanRotateByScreen(m.bsp.sign, {}) then ' no need to rotate per zone if already rotated by screen else if IsPortraitBottomLeft(m.bsp.sign.monitorOrientation) then aa.transform = "rot90" else if IsPortraitBottomRight(m.bsp.sign.monitorOrientation) then aa.transform = "rot270" end if aa.port = m.bsp.msgPort aa.url = filePath$ 'print "Loading MRSS HTML path: ";filePath$ ' ?? call LaunchTimer here to handle load timeout ?? SetUserAgentForHtmlWidget(m.bsp, m.stateMachine.loadingHtmlWidget, aa) m.stateMachine.loadingHtmlWidget = CreateObject("roHtmlWidget", m.stateMachine.rectangle, aa) userData = { } userData.stateId$ = m.id$ m.stateMachine.loadingHtmlWidget.SetUserData(userData) if type(m.imageTimeoutTimer) = "roTimer" then m.imageTimeoutTimer.Stop() end if if type(m.imageTimeoutTimer) <> "roTimer" then m.imageTimeoutTimer = CreateObject("roTimer") m.imageTimeoutTimer.SetPort(m.stateMachine.msgPort) end if m.imageTimeoutTimer.SetElapsed(displayItem.duration, 0) m.imageTimeoutTimer.Start() else m.videoItem = { } m.videoItem.fileName$ = displayItem.title m.videoItem.filePath$ = filePath$ m.videoItem.userVariable = invalid m.videoItem.probeData = invalid m.videoItem.automaticallyLoop = true m.videoItem.videoDisplayMode% = 0 m.videoItem.isEncrypted = isEncrypted ' m.LaunchVideo("mrss") m.PlayVideo(false, true) m.stateMachine.ClearImagePlane() ' Set transition to put image up immediately after video is finished if type(m.stateMachine.imagePlayer) = "roImageWidget" then m.stateMachine.imagePlayer.SetDefaultTransition(0) end if end if if not m.firstItemDisplayed then m.firstItemDisplayed = true end if end sub Function LastPathComponent(path$ as string) as string p% = instr(1, path$, "/") t% = p% while t% > 0 t% = instr(p% + 1, path$, "/") if t% > 0 then p% = t% end if end while if p% > 0 then return mid(path$, p% + 1) end if return path$ end function Function GetHtmlWidgetFilePath(displayItem as object, filePath$ as string) as string ' Get file name from url as widget name widgetName$ = LastPathComponent(displayItem.url) widgetDir$ = "/htmlWidgets/" + widgetName$ + "/" CreateDirectory(widgetDir$) ' TODO - check to see if files have already been unzipped and skip unzip if so ?? unzipper = CreateObject("roBrightPackage", filePath$) unzipper.Unpack(widgetDir$) return "file:" + widgetDir$ + "index.html" end function Sub PlayMixerAudio(executeEntryCmds as boolean, playbackIndex% as integer, playImmediate as boolean) loopMode% = 1 if type(m.audioEndEvent) = "roAssociativeArray" then loopMode% = 0 m.stateMachine.audioPlayer.SetLoopMode(loopMode%) if executeEntryCmds then m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) end if file$ = m.audioItem.fileName$ if type(m.audioItem.filePath$) = "roString" then filePath$ = m.audioItem.filePath$ else filePath$ = GetPoolFilePath(m.bsp.assetPoolFiles, m.audioItem.fileName$) end if track = { } track["Filename"] = filePath$ track["QueueNext"] = 1 track["UserString"] = playbackIndex% fadeLength% = m.stateMachine.fadelength% * 1000 track["FadeInLength"] = fadeLength% track["FadeOutLength"] = fadeLength% if playImmediate then track["FadeCurrentPlayNext"] = 0 end if ok = m.stateMachine.audioPlayer.PlayFile(track) m.stateMachine.ClearImagePlane() if type(m.audioItem.userVariable) = "roAssociativeArray" then m.audioItem.userVariable.Increment() end if ' playback logging m.stateMachine.LogPlayStart("audioMx", file$) end sub Sub LaunchMixerAudio(playbackIndex% as integer, playImmediate as boolean) m.PrePlayAudio() m.PlayMixerAudio(true, playbackIndex%, playImmediate) m.PostPlayAudio("audioMx") end sub Function STAudioPlayingEventHandler(event as object, stateData as object) as object MEDIA_END = 8 AUDIO_TIME_CODE = 12 stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") if m.stateMachine.type$ = "EnhancedAudio" then m.LaunchMixerAudio( - 1, true) else m.LaunchAudio("audio") end if return "HANDLED" else if event["EventType"] = "AudioPlaybackFailureEvent" then if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else return m.MediaItemEventHandler(event, stateData) end if end if else if m.stateMachine.type$ = "EnhancedAudio" and type(event) = "roAudioEventMx" then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Audio Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) end if else if IsAudioEvent(m.stateMachine, event) then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Audio Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) else if event.GetInt() = AUDIO_TIME_CODE then audioTimeCodeIndex$ = str(event.GetData()) m.bsp.diagnostics.PrintDebug("Audio TimeCode Event " + audioTimeCodeIndex$) if type(m.audioTimeCodeEvents) = "roAssociativeArray" then audioTimeCodeEvent = m.audioTimeCodeEvents[audioTimeCodeIndex$] if type(audioTimeCodeEvent) = "roAssociativeArray" then m.bsp.ExecuteTransitionCommands(m.stateMachine, audioTimeCodeEvent) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "audioTimeCode", "", "1") return "HANDLED" end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "audioTimeCode", "", "0") end if end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function 'endregion 'region Control State Machine ' ************************************************* ' ' Control State Machine ' ' ************************************************* Function newControlZoneHSM(bsp as object, zoneXML as object) as object zoneHSM = newHSM() zoneHSM.ConstructorHandler = ControlZoneConstructor zoneHSM.InitialPseudostateHandler = ControlZoneGetInitialState newZoneCommon(bsp, zoneXML, zoneHSM) return zoneHSM end function Sub ControlZoneConstructor() m.InitializeZoneCommon(m.bsp.msgPort) zoneHSM = m zoneHSM.isVideoZone = false m.activeState = m.playlist.firstState if type(m.playlist.firstState) = "roAssociativeArray" then m.previousStateName$ = m.playlist.firstState.id$ else m.previousStateName$ = "" end if m.CreateObjects() end sub Function ControlZoneGetInitialState() as object return m.activeState end function 'region Background Image State Machine ' ************************************************* ' ' Background Image State Machine ' ' ************************************************* Function newBackgroundImageZoneHSM(bsp as object, zoneXML as object) as object zoneHSM = newHSM() zoneHSM.ConstructorHandler = BackgroundImageZoneConstructor zoneHSM.InitialPseudostateHandler = BackgroundImageZoneGetInitialState newZoneCommon(bsp, zoneXML, zoneHSM) return zoneHSM end function Sub BackgroundImageZoneConstructor() m.InitializeZoneCommon(m.bsp.msgPort) zoneHSM = m ' create players videoPlayer = CreateObject("roVideoPlayer") if type(videoPlayer) <> "roVideoPlayer" then print "videoPlayer creation failed" : stop if CanRotateByScreen(m.bsp.sign, {}) then ' no need to rotate per zone if already rotated by screen else if IsPortraitBottomLeft(m.bsp.sign.monitorOrientation) then videoPlayer.SetTransform("rot90") else if m.bsp.sign.monitorOrientation = "portraitbottomonright" then videoPlayer.SetTransform("rot270") end if videoPlayer.SetRectangle(zoneHSM.rectangle) videoPlayer.ToBack() videoPlayer.SetPort(zoneHSM.msgPort) zoneHSM.videoPlayer = videoPlayer zoneHSM.isVideoZone = true m.activeState = m.playlist.firstState if type(m.playlist.firstState) = "roAssociativeArray" then m.previousStateName$ = m.playlist.firstState.id$ else m.previousStateName$ = "" end if m.CreateObjects() end sub Function BackgroundImageZoneGetInitialState() as object return m.activeState end function Function STDisplayingBackgroundImageEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.usbInputBuffer$ = "" m.usbInputLogBuffer$ = "" m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.cmds) file$ = m.backgroundImageItem.fileName$ filePath$ = GetPoolFilePath(m.bsp.assetPoolFiles, file$) aa = { } m.bsp.SetEncryptionAttributes(aa, file$, filePath$) ok = m.stateMachine.videoPlayer.PlayStaticImage(aa) if ok = 0 then m.bsp.diagnostics.PrintDebug("Error displaying file in LaunchBackgroundImage: " + file$ + ", " + filePath$) else m.bsp.diagnostics.PrintDebug("LaunchBackgroundImage: display file " + file$) end if if type(m.backgroundImageItem.userVariable) = "roAssociativeArray" then m.backgroundImageItem.userVariable.Increment() end if ' state logging m.bsp.logging.WriteStateLogEntry(m.stateMachine, m.id$, "backgroundImage") return "HANDLED" else if event["EventType"] = "PREPARE_FOR_RESTART" then m.stateMachine.videoPlayer = invalid return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) else return m.MediaItemEventHandler(event, stateData) end if end if else return m.MediaItemEventHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function 'endregion 'region Clock State Machine ' ************************************************* ' ' Clock State Machine ' ' ************************************************* Function newClockZoneHSM(bsp as object, zoneData as object) as object zoneHSM = newHSM() zoneHSM.ConstructorHandler = ClockZoneConstructor zoneHSM.InitialPseudostateHandler = ClockZoneGetInitialState newZoneCommon(bsp, zoneData, zoneHSM) zoneHSM.clockData = zoneData zoneHSM.stClock = zoneHSM.newHState(bsp, "Clock") zoneHSM.stClock.HStateEventHandler = STClockEventHandler zoneHSM.stClock.superState = zoneHSM.stTop return zoneHSM end function Sub ClockZoneConstructor() m.InitializeZoneCommon(m.bsp.msgPort) zoneHSM = m activeSyncSpec = GetActiveSyncSpec() assetCollection = activeSyncSpec.GetAssets("download") aa = { } aa.assets = [] asset = { } asset.pool = m.bsp.assetPool asset.collection = assetCollection asset.uri_prefix = "/Default_PresentationBsDateTimeWidget//" asset.pool_prefix = "Default_PresentationBsDateTimeWidget/" aa.assets.push(asset) if CanRotateByScreen(m.bsp.sign, {}) then ' no need to rotate per zone if already rotated by screen else if IsPortraitBottomLeft(m.bsp.sign.monitorOrientation) then aa.transform = "rot90" zoneHSM.clockData.AddReplace("isViewportPortrait", true) else if IsPortraitBottomRight(m.bsp.sign.monitorOrientation) then aa.transform = "rot270" zoneHSM.clockData.AddReplace("isViewportPortrait", true) end if aa.url = "file:///Default_PresentationBsDateTimeWidget//bsDateTimeWidget.html" aa.port = m.bsp.msgPort aa.javascript_enabled = true aa.focus_enabled = false aa.brightsign_js_objects_enabled = true aa.nodejs_enabled = true zoneHSM.widget = CreateObject("roHtmlWidget", zoneHSM.rectangle, aa) userData = { } userData.htmlWidgetId = "clockZoneWidget" zoneHSM.widget.SetUserData(userData) end sub Function ClockZoneGetInitialState() as object return m.stClock end function Function STClockEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.stateMachine.widget.Show() return "HANDLED" else if event["EventType"] = "PREPARE_FOR_RESTART" then m.stateMachine.widget = invalid return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") end if end if else if type(event) = "roHtmlWidgetEvent" then userData = event.getUserData() if type(userData) = "roAssociativeArray" and IsString(userData.htmlWidgetId) and userData.htmlWidgetId = "clockZoneWidget" then eventData = event.GetData() if type(eventData) = "roAssociativeArray" and type(eventData.reason) = "roString" then m.bsp.diagnostics.PrintDebug("reason = " + eventData.reason) if eventData.reason = "load-error" then m.bsp.diagnostics.PrintDebug("message = " + eventData.message) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_HTML5_LOAD_ERROR, eventData.message) else if eventData.reason = "load-finished" then widgetDataString = FormatJson(m.stateMachine.clockData) m.stateMachine.widget.PostJSMessage({ widget: widgetDataString }) end if end if end if end if stateData.nextState = m.superState return "SUPER" end function 'endregion 'region Ticker State Machine ' ************************************************* ' ' Ticker State Machine ' ' ************************************************* Function newTickerZoneHSM(bsp as object, sign as object, zoneDescription as object) as object zoneHSM = newHSM() zoneHSM.ConstructorHandler = TickerZoneConstructor zoneHSM.InitialPseudostateHandler = TickerZoneGetInitialState newZoneCommon(bsp, zoneDescription, zoneHSM) zoneHSM.numberOfLines% = zoneDescription.numberOfLines% zoneHSM.delay% = zoneDescription.delay% zoneHSM.rotation% = zoneDescription.rotation% zoneHSM.alignment% = zoneDescription.alignment% zoneHSM.scrollingMethod% = zoneDescription.scrollingMethod% zoneHSM.scrollSpeed% = zoneDescription.scrollSpeed% zoneHSM.foregroundTextColor% = zoneDescription.foregroundTextColor% zoneHSM.backgroundTextColor% = zoneDescription.backgroundTextColor% zoneHSM.font$ = zoneDescription.font$ zoneHSM.backgroundBitmapFile$ = zoneDescription.backgroundBitmapFile$ zoneHSM.stretch = zoneDescription.stretch zoneHSM.safeTextRegionX% = zoneDescription.safeTextRegionX% zoneHSM.safeTextRegionY% = zoneDescription.safeTextRegionY% zoneHSM.safeTextRegionWidth% = zoneDescription.safeTextRegionWidth% zoneHSM.safeTextRegionHeight% = zoneDescription.safeTextRegionHeight% zoneHSM.stRSSDataFeedInitialLoad = zoneHSM.newHState(bsp, "RSSDataFeedInitialLoad") zoneHSM.stRSSDataFeedInitialLoad.HStateEventHandler = STRSSDataFeedInitialLoadEventHandler zoneHSM.stRSSDataFeedInitialLoad.superState = zoneHSM.stTop zoneHSM.stRSSDataFeedPlaying = zoneHSM.newHState(bsp, "RSSDataFeedPlaying") zoneHSM.stRSSDataFeedPlaying.PopulateRSSDataFeedWidget = PopulateRSSDataFeedWidget zoneHSM.stRSSDataFeedPlaying.HStateEventHandler = STRSSDataFeedPlayingEventHandler zoneHSM.stRSSDataFeedPlaying.superState = zoneHSM.stTop return zoneHSM end function Sub TickerZoneConstructor() m.InitializeZoneCommon(m.bsp.msgPort) zoneHSM = m a = { } a["PauseTime"] = zoneHSM.delay% a["Rotation"] = zoneHSM.rotation% a["Alignment"] = zoneHSM.alignment% textWidget = CreateObject("roTextWidget", zoneHSM.rectangle, zoneHSM.numberOfLines%, zoneHSM.scrollingMethod%, a) zoneHSM.widget = textWidget if zoneHSM.scrollingMethod% = 3 then zoneHSM.widget.SetAnimationSpeed(zoneHSM.scrollSpeed%) end if if type(zoneHSM.foregroundTextColor%) = "roInt" then zoneHSM.widget.SetForegroundColor(zoneHSM.foregroundTextColor%) end if if type(zoneHSM.backgroundTextColor%) = "roInt" then zoneHSM.widget.SetBackgroundColor(zoneHSM.backgroundTextColor%) end if if zoneHSM.font$ <> "" and zoneHSM.font$ <> "System" then fontPath$ = GetPoolFilePath(m.bsp.assetPoolFiles, zoneHSM.font$) zoneHSM.widget.SetFont(fontPath$) end if if IsString(zoneHSM.backgroundBitmapFile$) then backgroundBitmapFilePath$ = GetPoolFilePath(m.bsp.assetPoolFiles, zoneHSM.backgroundBitmapFile$) aa = { } m.bsp.SetEncryptionAttributes(aa, zoneHSM.backgroundBitmapFile$, backgroundBitmapFilePath$) zoneHSM.widget.SetBackgroundBitmap(aa, zoneHSM.stretch) end if if type(zoneHSM.safeTextRegionX%) = "roInt" then r = CreateScaledRectangle(zoneHSM.safeTextRegionX%, zoneHSM.safeTextRegionY%, zoneHSM.safeTextRegionWidth%, zoneHSM.safeTextRegionHeight%) zoneHSM.widget.SetSafeTextRegion(r) r = invalid end if m.includesRSSFeeds = false for each rssDataFeedItem in m.rssDataFeedItems if rssDataFeedItem.isRSSFeed then m.includesRSSFeeds = true end if next end sub Sub SetEncryptionAttributes(aa as object, fileName$ as string, filePath$ as string) aa.fileName = filePath$ if type(m.encryptionByFile) = "roAssociativeArray" then if m.encryptionByFile.DoesExist(fileName$) then ' if m.bsp.contentEncrypted aa.AddReplace("EncryptionAlgorithm", "AesCtrHmac") aa.AddReplace("EncryptionKey", fileName$) end if end if end sub Function TickerZoneGetInitialState() as object if m.includesRSSFeeds then return m.stRSSDataFeedInitialLoad else return m.stRSSDataFeedPlaying end if end function Function GetRSSTempFilename() fileName$ = "tmp:/rss" + StripLeadingSpaces(stri(m.rssFileIndex%)) + ".xml" m.rssFileIndex% = m.rssFileIndex% + 1 return fileName$ end function Function STRSSDataFeedInitialLoadEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") for each rssDataFeedItem in m.stateMachine.rssDataFeedItems rssDataFeedItem.loadAttemptComplete = not rssDataFeedItem.isRSSFeed next return "HANDLED" else if event["EventType"] = "LIVE_DATA_FEED_UPDATE" or event["EventType"] = "LIVE_DATA_FEED_UPDATE_FAILURE" then liveDataFeed = event["EventData"] allLoadsComplete = true for each rssDataFeedItem in m.stateMachine.rssDataFeedItems if rssDataFeedItem.isRSSFeed then if liveDataFeed.id$ = rssDataFeedItem.liveDataFeed.id$ then rssDataFeedItem.loadAttemptComplete = true else if not rssDataFeedItem.loadAttemptComplete then allLoadsComplete = false end if end if next if allLoadsComplete then stateData.nextState = m.stateMachine.STRSSDataFeedPlaying return "TRANSITION" else return "HANDLED" end if else if event["EventType"] = "PREPARE_FOR_RESTART" then m.bsp.diagnostics.PrintDebug(m.id$ + " - PREPARE_FOR_RESTART") m.stateMachine.widget = invalid return "HANDLED" end if end if end if stateData.nextState = m.superState return "SUPER" end function Sub PopulateRSSDataFeedWidget() ' clear existing strings rssStringCount = m.stateMachine.widget.GetStringCount() m.stateMachine.widget.PopStrings(rssStringCount) ' populate widget with new strings for each rssDataFeedItem in m.stateMachine.rssDataFeedItems if type(rssDataFeedItem.textStrings) = "roArray" then for each textString in rssDataFeedItem.textStrings m.stateMachine.widget.PushString(textString) next else for each article in rssDataFeedItem.liveDataFeed.articles m.stateMachine.widget.PushString(article) next end if next if m.stateMachine.isVisible then m.stateMachine.widget.Show() end sub Function STRSSDataFeedPlayingEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.PopulateRSSDataFeedWidget() return "HANDLED" else if event["EventType"] = "LIVE_DATA_FEED_UPDATE" then liveDataFeed = event["EventData"] ' check that the live data feed is for one of the rss feeds rssDataFeedItemLoaded = false for each rssDataFeedItem in m.stateMachine.rssDataFeedItems if rssDataFeedItem.isRSSFeed then if liveDataFeed.id$ = rssDataFeedItem.liveDataFeed.id$ then rssDataFeedItemLoaded = true exit for end if end if next if rssDataFeedItemLoaded then m.PopulateRSSDataFeedWidget() end if else if event["EventType"] = "USER_VARIABLES_UPDATED" then rssDataFeedsUpdated = false userVariables = m.bsp.currentUserVariables for each rssDataFeedItem in m.stateMachine.rssDataFeedItems if rssDataFeedItem.isUserVariable then for each userVariableKey in userVariables userVariable = userVariables.Lookup(userVariableKey) if userVariable.name$ = rssDataFeedItem.userVariableName then tickerItemValue = userVariable.GetCurrentValue() rssDataFeedItem.textStrings = [] rssDataFeedItem.textStrings.push(tickerItemValue) rssDataFeedsUpdated = true end if next end if next if rssDataFeedsUpdated then m.PopulateRSSDataFeedWidget() end if return "HANDLED" else if event["EventType"] = "PREPARE_FOR_RESTART" then m.bsp.diagnostics.PrintDebug(m.id$ + " - PREPARE_FOR_RESTART") m.stateMachine.widget = invalid return "HANDLED" end if end if end if stateData.nextState = m.superState return "SUPER" end function 'endregion 'region Player State Machine ' ************************************************* ' ' Player State Machine ' ' ************************************************* Function newPlayerStateMachine(bsp as object) as object PlayerStateMachine = newHSM() PlayerStateMachine.InitialPseudostateHandler = InitializePlayerStateMachine PlayerStateMachine.bsp = bsp PlayerStateMachine.msgPort = bsp.msgPort PlayerStateMachine.logging = bsp.logging PlayerStateMachine.SetSystemInfo = SetSystemInfo PlayerStateMachine.CheckForUSBUpdate = CheckForUSBUpdate PlayerStateMachine.DisplayUSBUpdateStatus = DisplayUSBUpdateStatus PlayerStateMachine.dataFeedRetryInterval% = 30 PlayerStateMachine.POOL_EVENT_FILE_DOWNLOADED = 1 PlayerStateMachine.POOL_EVENT_FILE_FAILED = -1 PlayerStateMachine.POOL_EVENT_ALL_DOWNLOADED = 2 PlayerStateMachine.POOL_EVENT_ALL_FAILED = -2 PlayerStateMachine.SYNC_ERROR_CANCELLED = -10001 PlayerStateMachine.SYNC_ERROR_CHECKSUM_MISMATCH = -10002 PlayerStateMachine.SYNC_ERROR_EXCEPTION = -10003 PlayerStateMachine.SYNC_ERROR_DISK_ERROR = -10004 PlayerStateMachine.SYNC_ERROR_POOL_UNSATISFIED = -10005 PlayerStateMachine.EVENT_REALIZE_SUCCESS = 101 PlayerStateMachine.stTop = PlayerStateMachine.newHState(bsp, "Top") PlayerStateMachine.stTop.HStateEventHandler = STTopEventHandler PlayerStateMachine.stPlayer = PlayerStateMachine.newHState(bsp, "Player") PlayerStateMachine.stPlayer.HStateEventHandler = STPlayerEventHandler PlayerStateMachine.stPlayer.ProcessSupervisorCheckForContentMessage = ProcessSupervisorCheckForContentMessage PlayerStateMachine.stPlayer.superState = PlayerStateMachine.stTop PlayerStateMachine.stPlaying = PlayerStateMachine.newHState(bsp, "Playing") PlayerStateMachine.stPlaying.HStateEventHandler = STPlayingEventHandler PlayerStateMachine.stPlaying.PlayingEventUrlHandler = PlayingEventUrlHandler PlayerStateMachine.stPlaying.superState = PlayerStateMachine.stPlayer PlayerStateMachine.stPlaying.UpdateTimeClockEvents = UpdateTimeClockEvents PlayerStateMachine.stWaiting = PlayerStateMachine.newHState(bsp, "Waiting") PlayerStateMachine.stWaiting.HStateEventHandler = STWaitingEventHandler PlayerStateMachine.stWaiting.superState = PlayerStateMachine.stPlayer PlayerStateMachine.stUpdatingFromUSB = PlayerStateMachine.newHState(bsp, "UpdatingFromUSB") PlayerStateMachine.stUpdatingFromUSB.HStateEventHandler = STUpdatingFromUSBEventHandler PlayerStateMachine.stUpdatingFromUSB.superState = PlayerStateMachine.stPlayer PlayerStateMachine.stUpdatingFromUSB.BuildFileUpdateList = BuildFileUpdateList PlayerStateMachine.stUpdatingFromUSB.StartUpdateSyncListDownload = StartUpdateSyncListDownload PlayerStateMachine.stUpdatingFromUSB.HandleUSBAssetFetcherEvent = HandleUSBAssetFetcherEvent PlayerStateMachine.stWaitForStorageDetached = PlayerStateMachine.newHState(bsp, "WaitForStorageDetached") PlayerStateMachine.stWaitForStorageDetached.HStateEventHandler = STWaitForStorageDetachedEventHandler PlayerStateMachine.stWaitForStorageDetached.superState = PlayerStateMachine.stTop PlayerStateMachine.topState = PlayerStateMachine.stTop return PlayerStateMachine end function Function InitializePlayerStateMachine() as object m.bsp.Restart("") ' check for the presence of a USB drive with an update for n% = 1 to 9 usb$ = "USB" + StripLeadingSpaces(stri(n%)) + ":" du = CreateObject("roStorageInfo", usb$) if type(du) = "roStorageInfo" then m.bsp.diagnostics.PrintDebug("### Disc mounted at " + usb$) if m.CheckForUSBUpdate(usb$) then m.storagePath$ = usb$ return m.stUpdatingFromUSB end if end if next activeScheduledPresentation = m.bsp.schedule.activeScheduledEvent if type(activeScheduledPresentation) = "roAssociativeArray" then return m.stPlaying else return m.stWaiting end if end function Sub InitiateRemoteSnapshotTimer() m.remoteSnapshotTimer = CreateObject("roTimer") m.remoteSnapshotTimer.SetPort(m.msgPort) m.remoteSnapshotTimer.SetElapsed(GetActiveSettings().deviceScreenShotsInterval, 0) m.remoteSnapshotTimer.Start() end sub Sub RemoveRemoteSnapshotTimer() if type(m.remoteSnapshotTimer) = "roTimer" then m.remoteSnapshotTimer.Stop() m.remoteSnapshotTimer = invalid end if end sub Function STPlayerEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") if GetActiveSettings().deviceScreenShotsEnabled then m.bsp.InitiateRemoteSnapshotTimer() end if return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") else if event["EventType"] = "PREPARE_FOR_RESTART" then m.bsp.diagnostics.PrintDebug("STPlayerEventHandler - PREPARE_FOR_RESTART") m.bsp.touchScreen = invalid m.bsp.btManager.ResetPresentationBeacons() return "HANDLED" else if event["EventType"] = "SWITCH_PRESENTATION" then presentationName$ = event["Presentation"] m.bsp.diagnostics.PrintDebug("STPlayerEventHandler - Switch to presentation " + presentationName$) m.bsp.Restart(presentationName$) stateData.nextState = m.bsp.playerHSM.stPlaying return "TRANSITION" else if event["EventType"] = "CONTENT_UPDATED" then ' new content was downloaded from the network m.bsp.diagnostics.PrintDebug("STPlayerEventHandler - CONTENT_UPDATED") syncSpecSpec = GetSyncSpec() if type(syncSpecSpec) <> "roAssociativeArray" then stop endif currentSyncSpec = syncSpecSpec.syncSpec currentSyncSpecFileName = syncSpecSpec.fileName if currentSyncSpec.ReadFromFile(currentSyncSpecFileName) then m.bsp.assetCollection = currentSyncSpec.GetAssets("download") m.bsp.assetPoolFiles = CreateObject("roAssetPoolFiles", m.bsp.assetPool, m.bsp.assetCollection) UpdateSyncSpecAndSettings(currentSyncSpec, currentSyncSpecFileName, syncSpecSpec.syncSpecType) end if m.bsp.Restart("") activeScheduledPresentation = m.bsp.schedule.activeScheduledEvent if type(activeScheduledPresentation) = "roAssociativeArray" then stateData.nextState = m.stateMachine.stPlaying else stateData.nextState = m.stateMachine.stWaiting end if return "TRANSITION" end if end if else if type(event) = "roControlDisconnected" then userData$ = "Port unspecified" if type(event.getUserData()) = "roString" then userData$ = event.getUserData() end if m.bsp.diagnostics.PrintDebug("### Control port disconnected: " + userData$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_CONTROL_PORT_DISCONNECTED, userData$) m.bsp.logging.FlushLogFile() RebootSystem() return "HANDLED" else if type(event) = "roDiskErrorEvent" then aa = event.GetDiskError() diskErrorReport$ = "Time: " + aa["Time"] + " Error: " + aa["source"] + " " + aa["error"] + " " + aa["device"] + " " + aa["param"] m.bsp.diagnostics.PrintDebug("STPlayerEventHandler: Disk error event received: " + diskErrorReport$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_DISK_ERROR, diskErrorReport$) diskErrorMsg = { } diskErrorMsg["EventType"] = "DISK_ERROR" diskErrorMsg["DiskError"] = aa m.bsp.msgPort.PostMessage(diskErrorMsg) else if type(event) = "roTimerEvent" then if type(m.bsp.remoteSnapshotTimer) = "roTimer" and stri(event.GetSourceIdentity()) = stri(m.bsp.remoteSnapshotTimer.GetIdentity()) then presentationName$ = "" if m.bsp.activePresentation$ <> invalid then presentationName$ = m.bsp.activePresentation$ end if TakeSnapshot(m.bsp.systemTime, presentationName$) m.bsp.remoteSnapshotTimer.SetElapsed(GetActiveSettings().deviceScreenShotsInterval, 0) m.bsp.remoteSnapshotTimer.Start() end if if type(m.stateMachine.timer) = "roTimer" and stri(event.GetSourceIdentity()) = stri(m.stateMachine.timer.GetIdentity()) then m.bsp.diagnostics.PrintDebug("STPlayerEventHandler timer event") if not m.bsp.PostponeRestart() then ' send internal message to prepare for restart prepareForRestartEvent = { } prepareForRestartEvent["EventType"] = "PREPARE_FOR_RESTART" m.bsp.msgPort.PostMessage(prepareForRestartEvent) ' send internal message indicating that new content is available contentUpdatedEvent = { } contentUpdatedEvent["EventType"] = "CONTENT_UPDATED" m.bsp.msgPort.PostMessage(contentUpdatedEvent) end if return "HANDLED" end if if type(m.bsp.logging.cutoverTimer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(m.bsp.logging.cutoverTimer.GetIdentity()) then m.bsp.diagnostics.PrintDebug("STPlayerEventHandler cutover logs timer event") m.bsp.logging.HandleTimerEvent() m.bsp.LogActivePresentation() return "HANDLED" end if end if if type(m.bsp.serialPortsToRetry) = "roAssociativeArray" then for each serialPortToRetryName in m.bsp.serialPortsToRetry serialPortToRetry = m.bsp.serialPortsToRetry[serialPortToRetryName] timer = serialPortToRetry.timer if stri(event.GetSourceIdentity()) = stri(timer.GetIdentity()) then m.bsp.diagnostics.PrintDebug("RetryCreateSerial timeout") ok = m.bsp.RetryCreateSerial(serialPortToRetry.port$, serialPortToRetry.outputOnly) if not ok then m.bsp.diagnostics.PrintDebug("RetryCreateSerial failure, restart timer") timer.SetElapsed(15, 0) timer.Start() end if end if next end if if type(m.bsp.sign) = "roAssociativeArray" and type(m.bsp.bmapPortsToRetry) = "roAssociativeArray" then for each bmapPortToRetryName in m.bsp.bmapPortsToRetry bmapPortToRetry = m.bsp.bmapPortsToRetry[bmapPortToRetryName] timer = bmapPortToRetry.timer if stri(event.GetSourceIdentity()) = stri(timer.GetIdentity()) then m.bsp.diagnostics.PrintDebug("RetryCreateBMap timeout") ok = m.bsp.RetryCreateBMap(bmapPortToRetry.port$) if not ok then m.bsp.diagnostics.PrintDebug("RetryCreateBMap failure, restart timer") timer.SetElapsed(15, 0) timer.Start() end if end if next end if else if type(event) = "roHdmiEdidChanged" then eventString$ = "" if findMemberFunction(event, "GetString") <> invalid then eventString$ = event.GetString() if eventString$ = "" then edid = m.bsp.videoMode.GetEdidIdentity(true) else edid = m.bsp.videoMode.GetEdidIdentity(eventString$) end if UpdateEdidValues(edid, m.bsp.sysInfo, eventString$) edid = invalid m.bsp.UpdateEdidUserVariables(true, eventString$) systemVariableChanged = { } systemVariableChanged["EventType"] = "SYSTEM_VARIABLE_UPDATED" m.bsp.msgPort.PostMessage(systemVariableChanged) else if type(event) = "roAssetFetcherEvent" then userData$ = event.GetUserData() for each liveDataFeedId in m.bsp.liveDataFeeds if userData$ = liveDataFeedId then liveDataFeed = m.bsp.liveDataFeeds.Lookup(userData$) liveDataFeed.HandleLiveDataFeedContentDownloadAssetFetcherEvent(event) return "HANDLED" end if next else if type(event) = "roAssetFetcherProgressEvent" then m.bsp.diagnostics.PrintDebug("### File download progress " + event.GetFileName() + " unknown") userData$ = event.GetUserData() for each liveDataFeedId in m.bsp.liveDataFeeds if userData$ = liveDataFeedId then liveDataFeed = m.bsp.liveDataFeeds.Lookup(userData$) liveDataFeed.HandleLiveDataFeedContentDownloadAssetFetcherProgressEvent(event) return "HANDLED" end if next else if type(event) = "roNetworkAttached" or type(event) = "roNetworkDetached" then networkInterface% = event.GetInt() if type(event) = "roNetworkAttached" then nc = CreateObject("roNetworkConfiguration", networkInterface%) if type(nc) = "roNetworkConfiguration" then currentConfig = nc.GetCurrentConfig() if type(currentConfig) = "roAssociativeArray" then if currentConfig.ip4_address <> "" then if networkInterface% = 0 then m.bsp.sysInfo.ipAddressWired$ = currentConfig.ip4_address else if networkInterface% = 1 then m.bsp.sysInfo.ipAddressWireless$ = currentConfig.ip4_address end if end if end if end if nc = invalid else if networkInterface% = 0 then m.bsp.sysInfo.ipAddressWired$ = "Invalid" else if networkInterface% = 1 then m.bsp.sysInfo.ipAddressWireless$ = "Invalid" end if end if m.bsp.UpdateIPAddressUserVariables(true) systemVariableChanged = { } systemVariableChanged["EventType"] = "SYSTEM_VARIABLE_UPDATED" m.bsp.msgPort.PostMessage(systemVariableChanged) else if type(event) = "roControlEvent" then eventIdentity = stri(event.GetSourceIdentity()) blcIndex% = -1 if type(m.bsp.blcDiagnostics[0]) = "roControlPort" and stri(m.bsp.blcDiagnostics[0].GetIdentity()) = eventIdentity then blcIndex% = 0 else if type(m.bsp.blcDiagnostics[1]) = "roControlPort" and stri(m.bsp.blcDiagnostics[1].GetIdentity()) = eventIdentity then blcIndex% = 1 else if type(m.bsp.blcDiagnostics[2]) = "roControlPort" and stri(m.bsp.blcDiagnostics[2].GetIdentity()) = eventIdentity then blcIndex% = 2 end if if blcIndex% <> -1 then blcIdentifier$ = "BLC" + stri(blcIndex%) + ":" end if ' event types coming back from the blc400 REPORT_UNDER_EVENT% = &h20 REPORT_OVER_EVENT% = &h21 REPORT_MISSING% = &h22 REPORT_NORMAL% = &h23 ' event ADC channels MAIN_ADC% = 0 LED_ADC_COMP1% = 1 LED_ADC_COMP2% = 2 LED_ADC_COMP3% = 3 LED_ADC_COMP4% = 4 LED_ADC_OCOMP1% = 5 LED_ADC_OCOMP2% = 6 LED_ADC_OCOMP3% = 7 LED_ADC_OCOMP4% = 8 ch% = event.GetEventByte(1) adc% = event.GetEventWord(2) if (event.GetEventByte(0) = REPORT_UNDER_EVENT%) then event$ = "REPORT_UNDER_EVENT: " else if (event.GetEventByte(0) = REPORT_OVER_EVENT%) then event$ = "REPORT_OVER_EVENT: " else if (event.GetEventByte(0) = REPORT_MISSING%) then event$ = "REPORT_MISSING: " else if (event.GetEventByte(0) = REPORT_NORMAL%) then event$ = "REPORT_NORMAL: " else event$ = "" end if nextChannel% = -1 if event$ = "" then msg$ = blcIdentifier$ + "Unexpected control event(0): " + Stri(event.GetEventByte(0)) else if (ch% = MAIN_ADC%) then msg$ = blcIdentifier$ + event$ + "Main Power: " + Stri(adc%) nextChannel% = &h01 else if (ch% = LED_ADC_COMP1%) or (ch% = LED_ADC_OCOMP1%) then msg$ = blcIdentifier$ + event$ + "Channel A: " + Stri(adc%) nextChannel% = &h02 else if (ch% = LED_ADC_COMP2%) or (ch% = LED_ADC_OCOMP2%) then msg$ = blcIdentifier$ + event$ + "Channel B: " + Stri(adc%) nextChannel% = &h04 else if (ch% = LED_ADC_COMP3%) or (ch% = LED_ADC_OCOMP3%) then msg$ = blcIdentifier$ + event$ + "Channel C: " + Stri(adc%) nextChannel% = &h08 else if (ch% = LED_ADC_COMP4%) or (ch% = LED_ADC_OCOMP4%) then msg$ = blcIdentifier$ + event$ + "Channel D: " + Stri(adc%) else msg$ = blcIdentifier$ + "Unknown Power Error: " + Stri(ch%) + ": " + Stri(adc%) end if m.bsp.diagnostics.PrintDebug(msg$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BLC400_STATUS, msg$) if nextChannel% <> -1 and blcIndex% <> -1 then m.bsp.CheckBLCStatus(m.bsp.blcs[blcIndex%], nextChannel%) end if else if type(event) = "roStorageAttached" then storagePath$ = event.GetString() ' check for existence of upgrade file if m.stateMachine.CheckForUSBUpdate(storagePath$) then m.stateMachine.storagePath$ = storagePath$ stateData.nextState = m.stateMachine.stUpdatingFromUSB return "TRANSITION" else actionsXMLFilePath$ = event.GetString() + "actions.xml" actionsSpec$ = ReadAsciiFile(actionsXMLFilePath$) if actionsSpec$ <> "" then actionsXML = CreateObject("roXMLElement") actionsXML.Parse(actionsSpec$) if type(actionsXML.action) = "roXMLList" then if actionsXML.action.Count() > 0 then attributes = actionsXML.GetAttributes() displayStatus$ = attributes.Lookup("displayStatus") ' initialize as false, and will update to true when needed displayStatus = false if lcase(displayStatus$) = "true" then videoMode = CreateObject("roVideoMode") if type(videoMode) = "roVideoMode" then resX = videoMode.GetResX() resY = videoMode.GetResY() videoMode = invalid r = CreateObject("roRectangle", 0, 0, resX, resY) twParams = { } twParams.LineCount = 1 twParams.TextMode = 2 twParams.Rotation = 0 twParams.Alignment = 1 tw = CreateObject("roTextWidget", r, 1, 2, twParams) tw.PushString("") tw.Show() r = CreateObject("roRectangle", 0, resY / 2 - resY / 64, resX, resY / 32) tw = CreateObject("roTextWidget", r, 1, 2, twParams) displayStatus = true end if end if deletedLogFiles = false errorEncountered = false for each action in actionsXML.action action$ = action.GetText() if action$ = "copyLogs" then if displayStatus then tw.Clear() tw.PushString("Copying log files.") tw.Show() end if ok = m.bsp.logging.CopyAllLogFiles(storagePath$) if ok then m.bsp.diagnostics.PrintDebug("CopyAllLogFiles completed successfully") else errorEncountered = true m.bsp.diagnostics.PrintDebug("CopyAllLogFiles failed") if displayStatus then tw.Clear() tw.PushString("Error encountered while copying log files.") tw.Show() sleep(5000) end if exit for end if else if action$ = "deleteLogs" then if displayStatus then tw.Clear() tw.PushString("Deleting log files.") tw.Show() end if m.bsp.logging.DeleteAllLogFiles() m.bsp.diagnostics.PrintDebug("DeleteAllLogFiles complete") deletedLogFiles = true else if action$ = "resetVariables" then if displayStatus then tw.Clear() tw.PushString("Resetting variables.") tw.Show() end if m.bsp.ResetVariables() m.bsp.diagnostics.PrintDebug("Resetting variables complete") else if action$ = "copyVariablesDB" then if m.bsp.variablesDBExists then if displayStatus then tw.Clear() tw.PushString("Copying variables database.") tw.Show() end if serialNumber$ = m.bsp.sysInfo.deviceUniqueID$ dtLocal = m.bsp.systemTime.GetLocalDateTime() year$ = Right(stri(dtLocal.GetYear()), 2) month$ = StripLeadingSpaces(stri(dtLocal.GetMonth())) if len(month$) = 1 then month$ = "0" + month$ end if day$ = StripLeadingSpaces(stri(dtLocal.GetDay())) if len(day$) = 1 then day$ = "0" + day$ end if hour$ = StripLeadingSpaces(stri(dtLocal.GetHour())) if len(hour$) = 1 then hour$ = "0" + hour$ end if minute$ = StripLeadingSpaces(stri(dtLocal.GetMinute())) if len(minute$) = 1 then minute$ = "0" + minute$ end if 'date$ = year$ + month$ + day$ + hour$ + minute$ date$ = year$ + month$ + day$ fileName$ = "BrightSignVariables." + serialNumber$ + "-" + date$ + ".txt" filePath$ = storagePath$ + fileName$ variablesFile = CreateObject("roCreateFile", filePath$) if type(variablesFile) = "roCreateFile" then m.bsp.ExportVariablesDBToAsciiFile(variablesFile) ' determine if write was successful ' partial fix - only works if card was full before this step variablesFile.SeekToEnd() position% = variablesFile.CurrentPosition() if position% = 0 then errorEncountered = true m.bsp.diagnostics.PrintDebug("copyVariablesDB failed - fileLength = 0") else m.bsp.diagnostics.PrintDebug("Wrote variables file to " + filePath$) end if variablesFile = invalid else errorEncountered = true m.bsp.diagnostics.PrintDebug("copyVariablesDB failed - create file failed") end if if errorEncountered then if displayStatus then tw.Clear() tw.PushString("Error encountered while copying variables database.") tw.Show() sleep(5000) end if exit for end if else if displayStatus then tw.Clear() tw.PushString("No variables to copy.") tw.Show() sleep(3000) end if else if action$ = "reboot" then if displayStatus then tw.Clear() tw.PushString("Finalizing data writes, do not remove your drive yet.") tw.Show() EjectStorage(storagePath$) tw.Clear() tw.PushString("Data capture complete - you may remove your drive. The system will reboot shortly.") tw.Show() sleep(5000) tw.Clear() else EjectStorage(storagePath$) end if RebootSystem() return "HANDLED" end if next if displayStatus then tw.Clear() tw.PushString("Finalizing data writes, do not remove your drive yet.") tw.Show() EjectStorage(storagePath$) tw.Clear() if errorEncountered then tw.PushString("Data capture failed - you may remove your drive.") else tw.PushString("Data capture completed successfully - you may remove your drive.") end if tw.Show() sleep(5000) tw = invalid end if ' if the log files were deleted but the system is not rebooting, open a log file if m.bsp.logging.loggingEnabled then m.bsp.logging.OpenOrCreateCurrentLog() end if end if end if end if else if type(event) = "roControlCloudMessageEvent" then if IsString(event.getUserData()) and event.GetUserData() = "bootstrap" then m.bsp.diagnostics.PrintDebug("supervisor / bootstrap roControlCloudMessageEvent received") ccloudData = event.GetData() if IsString(ccloudData) then payload = ParseJson(ccloudData) m.ProcessSupervisorCheckForContentMessage(payload) return "HANDLED" end if end if else if type(event) = "roDatagramEvent" if IsString(event.getUserData()) and event.GetUserData() = "bootstrap" then m.bsp.diagnostics.PrintDebug("supervisor / bootstrap roDatagramEvent received") payload = ParseJson(event.GetString()) m.ProcessSupervisorCheckForContentMessage(payload) return "HANDLED" endif else if type(event) = "roBtEvent" then m.bsp.btManager.HandleEvent(event) return "HANDLED" end if stateData.nextState = m.superState return "SUPER" end function Function GetISODateTimeString(date as object) as string isoDateTime$ = date.ToIsoString() index = instr(1, isoDateTime$, ",") if index >= 1 then isoDateTime$ = mid(isoDateTime$, 1, index - 1) end if return isoDateTime$ end function Sub QueueSnapshotForBSN(snapshotName$ as string, url as string) ' systemTime = m.stateMachine.systemTime utcDateTime = m.stateMachine.systemTime.GetUtcDateTime() localDateTime = m.stateMachine.systemTime.GetLocalDateTime() headers = { } headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8" headers["host"] = m.stateMachine.AwsSqsHost headers["User-Agent"] = m.bsp.userAgent$ headers["X-Amz-Content-SHA256"] = "" xAmzDate$ = Left(utcDateTime.ToIsoString(), 15) + "Z" headers["X-Amz-Date"] = xAmzDate$ headers["x-amz-security-token"] = m.stateMachine.awsSessionToken$ s0 = chr(34) + ":" + chr(34) s1 = chr(34) + ", " + chr(34) localTimestamp$ = FormatDateTime(localDateTime) + "0000" utcTimestamp$ = FormatDateTime(utcDateTime) + "0000Z" account$ = m.stateMachine.currentSync.LookupMetadata("server", "account") ' BSNRT ' user$ = m.stateMachine.currentSync.LookupMetadata("server", "user") password$ = m.stateMachine.currentSync.LookupMetadata("server", "password") group$ = GetGlobalAA().settings.group serialNumber$ = m.bsp.sysInfo.deviceUniqueID$ if type(m.bsp.activePresentation$) = "roString" then presentationName$ = m.bsp.activePresentation$ else presentationName$ = "" end if jsonString = "{" + chr(34) + "AccountName" + s0 + account$ + s1 + "DeviceSerial" + s0 + serialNumber$ + s1 + "Login" + s0 + user$ + s1 + "Password" + s0 + password$ + s1 + "GroupName" + s0 + group$ + s1 + "PresentationName" + s0 + presentationName$ + s1 + "ScreenshotUrl" + s0 + url + s1 + "LocalTimestamp" + s0 + localTimestamp$ + s1 + "UTCTimestamp" + s0 + utcTimestamp$ + chr(34) + " }" parameters = { } parameters["Action"] = "SendMessage" parameters["MessageBody"] = jsonString parameters["Version"] = "2012-11-05" payload = GetRequestPayload(parameters) headers["X-Amz-Content-SHA256"] = ComputeSHA256Hash(payload) canonicalRequest = CanonicalizeRequest(m.stateMachine.AwsSqsAbsolutePath, "POST", headers, headers["X-Amz-Content-SHA256"]) signature = ComputeSignature(m.stateMachine.awsAccessKeyId$, m.stateMachine.awsSecretAccessKey$, m.stateMachine.AwsSqsRegion, utcDateTime, m.stateMachine.AwsSqsService, CanonicalizeHeaderNames(headers), canonicalRequest) authorizationHeader$ = "AWS4-HMAC-SHA256" authorizationHeader$ = authorizationHeader$ + " Credential" + "=" + m.stateMachine.awsAccessKeyId$ + "/" + FormatDateTimeyyyyMMdd(utcDateTime) + "/" + m.stateMachine.AwsSqsRegion + "/" + m.stateMachine.AwsSqsService + "/" + "aws4_request" + "," authorizationHeader$ = authorizationHeader$ + " SignedHeaders" + "=" + "content-type;host;user-agent;x-amz-content-sha256;x-amz-date;x-amz-security-token" + "," authorizationHeader$ = authorizationHeader$ + " Signature" + "=" + signature headers["Authorization"] = authorizationHeader$ m.stateMachine.queueSnapshotUrl = CreateObject("roUrlTransfer") m.stateMachine.queueSnapshotUrl.SetUserData(snapshotName$) m.stateMachine.queueSnapshotUrl.SetPort(m.stateMachine.msgPort) m.stateMachine.queueSnapshotUrl.SetUserAgent(m.bsp.userAgent$) requestHeaders = { } if not m.stateMachine.queueSnapshotUrl.AddHeader("Authorization", headers["Authorization"]) then stop if not m.stateMachine.queueSnapshotUrl.AddHeader("Content-Type", headers["Content-Type"]) then stop if not m.stateMachine.queueSnapshotUrl.AddHeader("x-amz-security-token", headers["x-amz-security-token"]) then stop if not m.stateMachine.queueSnapshotUrl.AddHeader("X-Amz-Date", headers["X-Amz-Date"]) then stop if not m.stateMachine.queueSnapshotUrl.AddHeader("X-Amz-Content-SHA256", headers["X-Amz-Content-SHA256"]) then stop if not m.stateMachine.queueSnapshotUrl.SetUrl(m.stateMachine.incomingDeviceScreenshotsQueue$) then stop aa = { } aa.method = "POST" aa.request_body_string = payload aa.response_body_string = true ok = m.stateMachine.queueSnapshotUrl.AsyncMethod(aa) if not ok then stop end sub Function FormatDateTimeyyyyMMdd(dateTime as object) as string dt$ = StripLeadingSpaces(stri(dateTime.GetYear())) + AddLeadingZeros(StripLeadingSpaces(stri(dateTime.GetMonth())), 2) + AddLeadingZeros(StripLeadingSpaces(stri(dateTime.GetDay())), 2) return dt$ end function Function FormatDateTime(dt as object) as string dt$ = StripLeadingSpaces(stri(dt.GetYear())) dt$ = dt$ + "-" + AddLeadingZeros(StripLeadingSpaces(stri(dt.GetMonth())), 2) dt$ = dt$ + "-" + AddLeadingZeros(StripLeadingSpaces(stri(dt.GetDay())), 2) dt$ = dt$ + "T" + AddLeadingZeros(StripLeadingSpaces(stri(dt.GetHour())), 2) dt$ = dt$ + ":" + AddLeadingZeros(StripLeadingSpaces(stri(dt.GetMinute())), 2) dt$ = dt$ + ":" + AddLeadingZeros(StripLeadingSpaces(stri(dt.GetSecond())), 2) dt$ = dt$ + "." + AddLeadingZeros(StripLeadingSpaces(stri(dt.GetMillisecond())), 3) return dt$ end function Function AddLeadingZeros(str$ as string, numDigits% as integer) as string while len(str$) < numDigits% str$ = "0" + str$ end while return str$ end function Function GetRequestPayload(parameters as object) ' Action ' MessageBody ' Version xfer = CreateObject("roUrlTransfer") payload$ = "" payload$ = payload$ + GetPayloadItem(xfer, "Action", parameters) payload$ = payload$ + GetPayloadItem(xfer, "MessageBody", parameters) payload$ = payload$ + GetPayloadItem(xfer, "Version", parameters) payload$ = Left(payload$, len(payload$) - 1) return payload$ end function Function GetPayloadItem(xfer as object, key as string, values as object) as string payload$ = key payload$ = payload$ + "=" payload$ = payload$ + xfer.Escape(values[key]) payload$ = payload$ + "&" return payload$ end function Function ComputeSHA256Hash(str$ as string) bytes = CreateObject("roByteArray") bytes.FromAsciiString(str$) hashGenerator = CreateObject("roHashGenerator", "SHA256") hash = hashGenerator.hash(bytes) hex$ = hash.ToHexString() hex$ = lcase(hex$) return hex$ end function Function CanonicalizeRequest(resourcePath as string, httpMethod as string, headers as object, precomputedBodyHash as string) as string canonicalRequest$ = "" canonicalRequest$ = canonicalRequest$ + httpMethod + chr(10) canonicalRequest$ = canonicalRequest$ + resourcePath + chr(10) canonicalRequest$ = canonicalRequest$ + chr(10) canonicalRequest$ = canonicalRequest$ + CanonicalizeHeaders(headers) + chr(10) canonicalRequest$ = canonicalRequest$ + CanonicalizeHeaderNames(headers) + chr(10) canonicalRequest$ = canonicalRequest$ + precomputedBodyHash return canonicalRequest$ end function Function CanonicalizeHeaders(headers as object) as string ' Content-Type ' host ' User-Agent ' X-Amz-Content-SHA256 ' X-Amz-Date ' x-amz-security-token canonicalHeaders$ = "" canonicalHeaders$ = canonicalHeaders$ + AddCanonicalHeader(headers, "Content-Type") canonicalHeaders$ = canonicalHeaders$ + AddCanonicalHeader(headers, "host") canonicalHeaders$ = canonicalHeaders$ + AddCanonicalHeader(headers, "User-Agent") canonicalHeaders$ = canonicalHeaders$ + AddCanonicalHeader(headers, "X-Amz-Content-SHA256") canonicalHeaders$ = canonicalHeaders$ + AddCanonicalHeader(headers, "X-Amz-Date") canonicalHeaders$ = canonicalHeaders$ + AddCanonicalHeader(headers, "x-amz-security-token") return canonicalHeaders$ end function Function AddCanonicalHeader(headers as object, entry as string) as string canonicalHeader$ = lcase(entry) canonicalHeader$ = canonicalHeader$ + ":" canonicalHeader$ = canonicalHeader$ + headers[entry] canonicalHeader$ = canonicalHeader$ + chr(10) return canonicalHeader$ end function Function CanonicalizeHeaderNames(headers as object) as string ' Content-Type ' host ' User-Agent ' X-Amz-Content-SHA256 ' X-Amz-Date ' x-amz-security-token ' canonicalHeaderNames$ = "" canonicalHeaderNames$ = canonicalHeaderNames$ + AddCanonicalHeaderName("Content-Type") canonicalHeaderNames$ = canonicalHeaderNames$ + AddCanonicalHeaderName("host") canonicalHeaderNames$ = canonicalHeaderNames$ + AddCanonicalHeaderName("User-Agent") canonicalHeaderNames$ = canonicalHeaderNames$ + AddCanonicalHeaderName("X-Amz-Content-SHA256") canonicalHeaderNames$ = canonicalHeaderNames$ + AddCanonicalHeaderName("X-Amz-Date") canonicalHeaderNames$ = canonicalHeaderNames$ + AddCanonicalHeaderName("x-amz-security-token") canonicalHeaderNames$ = Left(canonicalHeaderNames$, len(canonicalHeaderNames$) - 1) return canonicalHeaderNames$ end function Function AddCanonicalHeaderName(entry as string) as string canonicalHeaderName$ = lcase(entry) canonicalHeaderName$ = canonicalHeaderName$ + ";" return canonicalHeaderName$ end function Function ComputeSignature(awsAccessKey as string, awsSecretAccessKey as string, region as string, signedAt as object, service as string, signedHeaders as string, canonicalRequest as string) as string dateStamp1 = FormatDateTimeyyyyMMdd(signedAt) scope = dateStamp1 + "/" + region + "/" + service + "/aws4_request" dateStamp2 = dateStamp1 + "T" + AddLeadingZeros(StripLeadingSpaces(stri(signedAt.GetHour())), 2) + AddLeadingZeros(StripLeadingSpaces(stri(signedAt.GetMinute())), 2) + AddLeadingZeros(StripLeadingSpaces(stri(signedAt.GetSecond())), 2) + "Z" stringToSign = "AWS4-HMAC-SHA256" + chr(10) + dateStamp2 + chr(10) + scope + chr(10) canonicalRequestHash = ComputeSHA256Hash(canonicalRequest) stringToSign = stringToSign + canonicalRequestHash key = ComposeSigningKey(awsSecretAccessKey, region, dateStamp1, service) stringToSignBytes = CreateObject("roByteArray") stringToSignBytes.FromAsciiString(stringToSign) keyedHash = ComputeKeyedHash(key, stringToSignBytes) keyedHash$ = lcase(keyedHash.ToHexString()) return keyedHash$ end function Function ComposeSigningKey(awsSecretAccessKey as string, region as string, date as string, service as string) as object ksecretBytes = CreateObject("roByteArray") ksecretBytes.FromAsciiString("AWS4" + awsSecretAccessKey) dateBytes = CreateObject("roByteArray") dateBytes.FromAsciiString(date) hashDate = ComputeKeyedHash(ksecretBytes, dateBytes) regionBytes = CreateObject("roByteArray") regionBytes.FromAsciiString(region) hashRegion = ComputeKeyedHash(hashDate, regionBytes) serviceBytes = CreateObject("roByteArray") serviceBytes.FromAsciiString(service) hashService = ComputeKeyedHash(hashRegion, serviceBytes) aws4RequestBytes = CreateObject("roByteArray") aws4RequestBytes.FromAsciiString("aws4_request") keyedHash = ComputeKeyedHash(hashService, aws4RequestBytes) return keyedHash end function Function ComputeKeyedHash(key as object, data as object) as object hashGenerator = CreateObject("roHashGenerator", "SHA256") ok = hashGenerator.SetHmacKey(key) if not ok then stop hash = hashGenerator.hash(data) return hash end function Sub UploadSnapshotToSFN(snapshotName$ as string) headers = { } headers["Content-Type"] = "image/jpeg" headers["SFN-DeviceSerial"] = m.bsp.sysInfo.deviceUniqueID$ if type(m.bsp.activePresentation$) = "roString" then presentationName$ = m.bsp.activePresentation$ else presentationName$ = "" end if headers["SFN-PresentationName"] = presentationName$ localDateTime = m.stateMachine.systemTime.GetLocalDateTime() headers["SFN-LocalTimestamp"] = FormatDateTime(localDateTime) utcDateTime = m.stateMachine.systemTime.GetUtcDateTime() headers["SFN-UTCTimestamp"] = FormatDateTime(utcDateTime) + "Z" snapshotFilePath$ = "snapshots/" + snapshotName$ fileSize% = GetFileSize(snapshotFilePath$) headers["Content-Length"] = stri(fileSize%) m.stateMachine.uploadSnapshotToSFNUrl = CreateObject("roUrlTransfer") m.stateMachine.uploadSnapshotToSFNUrl.SetPort(m.stateMachine.msgPort) m.stateMachine.uploadSnapshotToSFNUrl.SetTimeout(5000) m.stateMachine.uploadSnapshotToSFNUrl.SetUrl(m.stateMachine.uploadSnapshotsURL$) m.stateMachine.uploadSnapshotToSFNUrl.SetHeaders(headers) ok = m.stateMachine.uploadSnapshotToSFNUrl.AsyncPutFromFile(snapshotFilePath$) end sub Sub UploadSnapshotToBSNEE(snapshotName$ as string) headers = { } headers["Content-Type"] = "image/jpeg" ' request.KeepAlive = true; ' headers["Connection"] = "Keep-Alive" headers["BSN-DeviceSerial"] = m.bsp.sysInfo.deviceUniqueID$ ' TEDTODO-Subban: can I eliminate all references to m.stateMachine.currentSync? headers["BSN-AccountName"] = m.stateMachine.currentSync.LookupMetadata("server", "account") ' BSNRT ' headers["BSN-Login"] = m.stateMachine.currentSync.LookupMetadata("server", "user") headers["BSN-Password"] = m.stateMachine.currentSync.LookupMetadata("server", "password") headers["BSN-GroupName"] = GetGlobalAA().settings.group registry = GetGlobalAA().registrySection accessToken = registry.Read("access_token") headers["Authorization"] = "Bearer " + accessToken headers["accessToken"] = accessToken if type(m.bsp.activePresentation$) = "roString" then presentationName$ = m.bsp.activePresentation$ else presentationName$ = "" end if headers["BSN-PresentationName"] = presentationName$ localDateTime = m.stateMachine.systemTime.GetLocalDateTime() headers["BSN-LocalTimestamp"] = FormatDateTime(localDateTime) utcDateTime = m.stateMachine.systemTime.GetUtcDateTime() headers["BSN-UTCTimestamp"] = FormatDateTime(utcDateTime) + "Z" headers["BSN-SecurityToken"] = m.stateMachine.securityToken snapshotFilePath$ = "snapshots/" + snapshotName$ fileSize% = GetFileSize(snapshotFilePath$) headers["Content-Length"] = stri(fileSize%) m.stateMachine.uploadSnapshotToBSNEEUrl = CreateObject("roUrlTransfer") m.stateMachine.uploadSnapshotToBSNEEUrl.SetPort(m.stateMachine.msgPort) m.stateMachine.uploadSnapshotToBSNEEUrl.SetTimeout(5000) m.stateMachine.uploadSnapshotToBSNEEUrl.SetUrl(m.stateMachine.uploadDeviceScreenshotHandlerAddress) m.stateMachine.uploadSnapshotToBSNEEUrl.SetHeaders(headers) m.stateMachine.uploadSnapshotToBSNEEUrl.SetUserAgent(m.bsp.userAgent$) if not m.stateMachine.uploadSnapshotToBSNEEUrl.AsyncPostFromFile(snapshotFilePath$) then stop end sub Sub UploadSnapshotToBSN(snapshotName$ as string) if type(m.stateMachine.uploadSnapshotUrl) = "roUrlTransfer" then m.stateMachine.pendingSnapshotsToUpload.AddReplace(snapshotName$, snapshotName$) return end if globalAA = GetGlobalAA() snapshotFilePath$ = "snapshots/" + snapshotName$ serialNumber$ = m.bsp.sysInfo.deviceUniqueID$ photoTimestamp$ = GetPhotoTimestamp(snapshotName$) AWSResourceKey = m.stateMachine.AwsIncomingDirectory + serialNumber$ + "/" + photoTimestamp$ + ".jpg" headers = { } headers["Content-Type"] = "image/jpeg" headers["Content-Length"] = StripLeadingSpaces(stri(GetFileSize(snapshotFilePath$))) headers["x-amz-security-token"] = m.stateMachine.awsSessionToken$ headers["X-Amz-Date"] = GetDateTime(m.stateMachine.systemTime.GetUtcDateTime()) stringToSign$ = m.BuildStringToSign(headers, AWSResourceKey) auth$ = GetHMACSign(stringToSign$, m.stateMachine.awsSecretAccessKey$) authorization$ = "AWS " + m.stateMachine.awsAccessKeyId$ + ":" + auth$ headers["Authorization"] = authorization$ m.stateMachine.uploadSnapshotUrl = CreateObject("roUrlTransfer") ' url to send snapshots to url = m.stateMachine.deviceScreenShotsTemporaryStorage$ + serialNumber$ + "/" + photoTimestamp$ + ".jpg" if not m.stateMachine.uploadSnapshotUrl.SetUrl(url) then stop snapshot = { } snapshot.name = snapshotName$ snapshot.url = url m.stateMachine.uploadSnapshotUrl.SetUserData(snapshot) if not m.stateMachine.uploadSnapshotUrl.AddHeader("Authorization", headers["Authorization"]) then stop if not m.stateMachine.uploadSnapshotUrl.AddHeader("Content-Type", "image/jpeg") then stop if not m.stateMachine.uploadSnapshotUrl.AddHeader("x-amz-security-token", headers["x-amz-security-token"]) then stop if not m.stateMachine.uploadSnapshotUrl.AddHeader("X-Amz-Date", headers["X-Amz-Date"]) then stop m.stateMachine.uploadSnapshotUrl.SetPort(m.stateMachine.msgPort) m.stateMachine.uploadSnapshotUrl.SetTimeout(5000) m.stateMachine.uploadSnapshotUrl.SetUserAgent(m.bsp.userAgent$) ok = m.stateMachine.uploadSnapshotUrl.AsyncPutFromFile(snapshotFilePath$) if not ok then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SNAPSHOT_PUT_TO_SERVER_ERROR, m.stateMachine.uploadSnapshotUrl.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### AsyncPutFromFile failed: file " + snapshotFilePath$ + ", reason " + m.stateMachine.uploadSnapshotUrl.GetFailureReason()) end if end sub Function BuildStringToSign(headers as object, AWSResourceKey as string) as string result$ = "" result$ = result$ + "PUT" result$ = result$ + chr(10) result$ = result$ + chr(10) result$ = result$ + headers["Content-Type"] result$ = result$ + chr(10) result$ = result$ + chr(10) result$ = result$ + "x-amz-date" + ":" + headers["X-Amz-Date"] + chr(10) result$ = result$ + "x-amz-security-token" + ":" + headers["x-amz-security-token"] + chr(10) result$ = result$ + "/" + m.stateMachine.AwsBucketName + "/" + AwsResourceKey return result$ end function Function GetHMACSign(data as string, key as string) as string binaryData = CreateObject("roByteArray") binaryData.FromAsciiString(data) keyData = CreateObject("roByteArray") keyData.FromAsciiString(key) hashGenerator = CreateObject("roHashGenerator", "SHA1") ok = hashGenerator.SetHmacKey(keyData) bytes = hashGenerator.hash(binaryData) signature = bytes.ToBase64String() return signature end function ' Return RFC1123 date/time string from roDateTime Function GetDateTime(currentTime as object) as string daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] dayOfWeek$ = daysOfWeek[currentTime.GetDayOfWeek()] day$ = StripLeadingSpaces(stri(currentTime.GetDay())) if len(day$) = 1 then day$ = "0" + day$ month$ = months[currentTime.GetMonth() - 1] year$ = StripLeadingSpaces(stri(currentTime.GetYear())) hour$ = StripLeadingSpaces(stri(currentTime.GetHour())) if len(hour$) = 1 then hour$ = "0" + hour$ minute$ = StripLeadingSpaces(stri(currentTime.GetMinute())) if len(minute$) = 1 then minute$ = "0" + minute$ second$ = StripLeadingSpaces(stri(currentTime.GetSecond())) if len(second$) = 1 then second$ = "0" + second$ currentDateTime = dayOfWeek$ + ", " + day$ + " " + month$ + " " + year$ + " " + hour$ + ":" + minute$ + ":" + second$ + " GMT" return currentDateTime end function ' Parse an HTTP date/time value, as specified in RFC 2616 sec 3.3 ' Preferred format is RFC1123, e.g., "Sun, 15 Mar 2016 08:15:10 GMT" Function ParseHTTPDateTime(httpDateTime as string) as object months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] ' Currently, handle only RFC 1123 ' TODO - handle the other 'obsolete' HTTP formats, RFC 850/1036, and ANSI C asctime()format ' return current time if time string not valid systemTime = CreateObject("roSystemTime") dateTime = systemTime.GetUtcDateTime() dateTime.SetMillisecond(0) if mid(httpDateTime, 4, 1) = "," then day% = val(mid(httpDateTime, 6, 2)) if day% > 0 and day% < 32 then dateTime.SetDay(day%) end if httpMonth = mid(httpDateTime, 9, 3) for month% = 0 to 11 if months[month%] = httpMonth then dateTime.SetMonth(month% + 1) exit for end if next year% = val(mid(httpDateTime, 13, 4)) if year% > 0 and year% < dateTime.GetYear() then dateTime.SetYear(year%) end if hour% = val(mid(httpDateTime, 18, 2)) if hour% < 24 then dateTime.SetHour(hour%) end if minute% = val(mid(httpDateTime, 21, 2)) if minute% < 60 then dateTime.SetMinute(minute%) end if second% = val(mid(httpDateTime, 24, 2)) if second% < 60 then dateTime.SetSecond(second%) end if end if return dateTime end function Function GetPhotoTimestamp(snapshotName$ as string) as string year$ = mid(snapshotName$, 1, 4) month$ = mid(snapshotName$, 5, 2) day$ = mid(snapshotName$, 7, 2) hour$ = mid(snapshotName$, 10, 2) minute$ = mid(snapshotName$, 12, 2) second$ = mid(snapshotName$, 14, 2) photoTimestamp$ = year$ + "-" + month$ + "-" + day$ + "T" + hour$ + "-" + minute$ + "-" + second$ + ".0000000Z" return photoTimestamp$ end function Sub EjectStorage(storagePath$ as string) ok = EjectDrive(storagePath$) if not ok then sleep(30000) end if end sub Function SetDeviceSetupSplashScreen(setupType as String, msgPort as Object) as object filepath = "sys:/web-client/postDeviceSetupSplashScreen/dist/index.html" file = CreateObject("roReadFile", filepath) if file <> invalid then filepath = "file:/" + filepath videoMode = CreateObject("roVideoMode") resX = videoMode.GetResX() resY = videoMode.GetResY() r = CreateObject("roRectangle", 0, 0, resX, resY) config = { url: filepath brightsign_js_objects_enabled: true nodejs_enabled: true } htmlWidget = CreateObject("roHtmlWidget", r, config) if type(htmlWidget) = "roHtmlWidget" then htmlWidget.SetPort(msgPort) htmlWidget.AllowJavascriptUrls({ all: "*" }) htmlWidget.EnableJavascript(true) sleep(5000) if lcase(setupType) = "lfn" then htmlWidget.PostJSMessage({ htmlcommand: "setupsplashscreenmessage", headermsg: "Congratulations, your BrightSign player is set up!", message: "Use BrightAuthor:connected to publish content via the Local Network mode." }) end if htmlWidget.Show() return htmlWidget else stop endif endif return invalid End Function Function STWaitingEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") ' set a timer for when the system should become active again if type(m.bsp.schedule.nextScheduledEventTime) = "roDateTime" then dateTime = m.bsp.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.stateMachine.msgPort) newTimer.Start() m.stateMachine.timer = newTimer end if return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") end if end if end if stateData.nextState = m.superState return "SUPER" end function Function STPlayingEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") ' set a timer for when the current presentation should end activeScheduledPresentation = m.bsp.schedule.activeScheduledEvent if type(activeScheduledPresentation) = "roAssociativeArray" then if m.bsp.schedule.activeScheduledEventEndDateTime <> invalid then endDateTime = m.bsp.schedule.activeScheduledEventEndDateTime 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.stateMachine.msgPort) newTimer.Start() m.stateMachine.timer = newTimer m.bsp.diagnostics.PrintDebug("Set STPlayingEventHandler timer to " + endDateTime.GetString()) end if ' check for live data feeds that include content (either MRSS or content for Media Lists / PlayFiles). for each of them, check to see if the feed and/or content already exists. for each liveDataFeedId in m.bsp.liveDataFeeds liveDataFeed = m.bsp.liveDataFeeds.Lookup(liveDataFeedId) liveDataFeed.ReadFeedContent() next ' load live data feeds m.liveDataFeeds = { } m.bsp.liveDataFeedsByTimer = { } ' queue live data feeds for downloading m.bsp.liveDataFeedsToDownload = [] for each liveDataFeedId in m.bsp.liveDataFeeds liveDataFeed = m.bsp.liveDataFeeds.Lookup(liveDataFeedId) m.bsp.QueueRetrieveLiveDataFeed(m.liveDataFeeds, liveDataFeed) next ' launch playback m.bsp.StartPlayback() end if return "HANDLED" else if event["EventType"] = "UPDATE_DATA_FEED" then dataFeedId$ = event["DataFeedId"] for each liveDataFeedId in m.bsp.liveDataFeeds liveDataFeed = m.bsp.liveDataFeeds.Lookup(liveDataFeedId) if dataFeedId$ = liveDataFeedId then liveDataFeed.forceUpdate = true m.bsp.QueueRetrieveLiveDataFeed(m.liveDataFeeds, liveDataFeed) end if next else if event["EventType"] = "UPDATE_DATA_FEED_BY_CATEGORY" then categoryName$ = event["Name"] for each liveDataFeedId in m.bsp.liveDataFeeds liveDataFeed = m.bsp.liveDataFeeds.Lookup(liveDataFeedId) if liveDataFeed.title$ = categoryName$ then liveDataFeed.forceUpdate = true m.bsp.QueueRetrieveLiveDataFeed(m.liveDataFeeds, liveDataFeed) end if next else if event["EventType"] = "UPDATE_ALL_DATA_FEEDS" then for each liveDataFeedId in m.bsp.liveDataFeeds liveDataFeed = m.bsp.liveDataFeeds.Lookup(liveDataFeedId) if liveDataFeed.autoGenerateUserVariables then liveDataFeed.forceUpdate = true m.bsp.QueueRetrieveLiveDataFeed(m.liveDataFeeds, liveDataFeed) end if next else if event["EventType"] = "CONTENT_DATA_FEED_LOADED" then sign = m.bsp.sign for each zone in sign.zonesHSM for each stateName in zone.stateTable state = zone.stateTable[stateName] if state.type$ = "playFile" then if type(state.liveDataFeed) = "roAssociativeArray" and event["Name"] = state.liveDataFeed.id$ then if type(state.liveDataFeed.assetPoolFiles) = "roAssetPoolFiles" then state.PopulatePlayFileFromLiveDataFeed() ' following line is commented out as the code should fall through to the next if statement ' return "HANDLED" end if end if end if next next else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") end if if event["EventType"] = "MRSS_DATA_FEED_LOADED" or event["EventType"] = "CONTENT_DATA_FEED_LOADED" or event["EventType"] = "CONTENT_DATA_FEED_UNCHANGED" then m.bsp.AdvanceToNextLiveDataFeedInQueue(m.liveDataFeeds) end if end if end if if type(event) = "roTimerEvent" then eventIdentity$ = stri(event.GetSourceIdentity()) if m.bsp.liveDataFeedsByTimer.DoesExist(eventIdentity$) then liveDataFeed = m.bsp.liveDataFeedsByTimer.Lookup(eventIdentity$) ' if this feed's download failed, launch download of any pending feeds ' if this isn't done, no new downloads are ever retrieved as the queue is never empty and there's never an event that causes the ' next feed to be retrieved if lcase(type(liveDataFeed.lastDownloadedFailed)) = "roboolean" or lcase(type(liveDataFeed.lastDownloadedFailed)) = "boolean" then if liveDataFeed.lastDownloadedFailed then m.bsp.RetrievePendingLiveDataFeed(m.liveDataFeeds) end if end if ' requeue the feed that failed m.bsp.QueueRetrieveLiveDataFeed(m.liveDataFeeds, liveDataFeed) end if else if type(event) = "roUrlEvent" then return m.PlayingEventUrlHandler(event, stateData) end if stateData.nextState = m.superState return "SUPER" end function Function PlayingEventUrlHandler(event as object, stateData as object) as object eventIdentity$ = stri(event.GetSourceIdentity()) if m.liveDataFeeds.DoesExist(eventIdentity$) then liveDataFeed = m.liveDataFeeds.Lookup(eventIdentity$) m.liveDataFeeds.Delete(eventIdentity$) if event.GetResponseCode() = 200 or event.GetResponseCode() = 0 then updateInterval% = 0 headers = event.GetResponseHeaders() lastModifiedTime = invalid lastModifiedTimeStr = headers["Last-Modified"] if lastModifiedTimeStr <> invalid then lastModifiedTime = ParseHTTPDateTime(lastModifiedTimeStr) end if ' indicate that last download was successful liveDataFeed.lastDownloadedFailed = false if liveDataFeed.headRequest then getFeed = false if type(liveDataFeed.currentModifiedTime) <> "roDateTime" then ' retrieve the feed if there is no currentModifiedTime stamp for the feed m.bsp.diagnostics.PrintDebug("### Check live data feed - no current timestamp - updating") getFeed = true else headers = event.GetResponseHeaders() lastModifiedTimeStr = headers["Last-Modified"] if lastModifiedTimeStr <> invalid then lastModifiedTime = ParseHTTPDateTime(lastModifiedTimeStr) if lastModifiedTime <> liveDataFeed.currentModifiedTime then m.bsp.diagnostics.PrintDebug("### Check live data feed - updating '" + liveDataFeed.id$ + "', last modified = " + lastModifiedTime.GetString() + ", currentModified = " + liveDataFeed.currentModifiedTime.GetString()) ' retrieve the feed if there is a Last-Modified header and it is greater than the modified time of the current feed getFeed = true else m.bsp.diagnostics.PrintDebug("### Check live data feed - no update needed for '" + liveDataFeed.id$ + "', last modified = " + lastModifiedTime.GetString() + ", currentModified = " + liveDataFeed.currentModifiedTime.GetString()) end if else ' retrieve the feed if there is no Last-Modified header m.bsp.diagnostics.PrintDebug("### Check live data feed - no Last-Modified header - updating") getFeed = true end if end if if getFeed then liveDataFeed.headRequest = false m.bsp.RetrieveLiveDataFeed(m.liveDataFeeds, liveDataFeed) else ' No feed download needed, pop the item off the queue and see if something else is pending m.bsp.AdvanceToNextLiveDataFeedInQueue(m.liveDataFeeds) ' Send next HEAD request after interval updateInterval% = liveDataFeed.updateInterval% end if else userVariables = m.bsp.currentUserVariables liveDataFeed.currentModifiedTime = lastModifiedTime if liveDataFeed.usage$ <> "mrss" and liveDataFeed.usage$ <> "mrsswith4k" then ' simple RSS or content if liveDataFeed.usage$ = "content" then if (liveDataFeed.isDynamicPlaylist or liveDataFeed.isLiveMediaFeed) then liveDataFeed.ParseMRSSFeed(liveDataFeed.rssFileName$) liveDataFeed.ConvertMRSSFormatToContent() else liveDataFeed.ParseCustomContentFormat(liveDataFeed.rssFileName$) endif else liveDataFeed.ParseSimpleRSSFeed(liveDataFeed.rssFileName$) end if ' parsing for autogenerated user variables - make it conditional on using autogenerated user variables ' check for uv parser. if exists, send rss feed, array of elements where each element is title, description, mediaURL. also need to get the title back (not sure how). ' if no parser exists, parse it here, filling in the array as above. determine title of feed. liveDataFeed.items = CreateObject("roArray", 1, true) if liveDataFeed.autoGenerateUserVariables then if liveDataFeed.uvParser$ <> "" then ERR_NORMAL_END = &hFC retVal = eval(liveDataFeed.uvParser$ + "(liveDataFeed.rssFileName$, liveDataFeed.items, userVariables, m.bsp)") if retVal <> ERR_NORMAL_END then ' log the failure m.bsp.diagnostics.PrintDebug("Failure invoking Eval to parse live data feed for user variables: return value = " + stri(retVal) + ", parser is " + liveDataFeed.uvParser$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_LIVE_TEXT_PLUGIN_FAILURE, stri(retVal) + chr(9) + liveDataFeed.uvParser$) end if else dataFeedXML = CreateObject("roXMLElement") dataFeedXML.Parse(ReadAsciiFile(liveDataFeed.rssFileName$)) liveDataFeed.title$ = dataFeedXML.channel.title.gettext() allItemsXML = dataFeedXML.channel.item position% = 0 for each itemXML in allItemsXML item = { } item.title$ = itemXML.title.gettext() item.description$ = itemXML.description.gettext() mediaContent = itemXML.GetNamedElements("media:content")[0] if mediaContent = invalid then item.mediaUrl$ = "" else item.mediaUrl$ = mediaContent.GetAttributes()["url"] end if item.position% = position% position% = position% + 1 liveDataFeed.items.push(item) end for end if end if else ' These must be valid objects even for MRSS feeds (at least for now) liveDataFeed.articles = CreateObject("roArray", 1, true) liveDataFeed.articleTitles = CreateObject("roArray", 1, true) liveDataFeed.articlesByTitle = { } end if liveDataFeed.isMRSSFeed = liveDataFeed.FeedIsMRSS(liveDataFeed.rssFileName$) if liveDataFeed.usage$ = "content" then liveDataFeed.DownloadLiveFeedContent() else if liveDataFeed.usage$ = "mrss" or liveDataFeed.usage$ = "mrsswith4k" and (liveDataFeed.parser$ <> "" or liveDataFeed.isMRSSFeed) then liveDataFeed.DownloadMRSSContent() end if if liveDataFeed.autoGenerateUserVariables then m.bsp.CreateUserVariablesFromDataFeed(liveDataFeed) end if DeleteFile(liveDataFeed.rssFileName$) ' update user variables if type(userVariables) = "roAssociativeArray" then updatedUserVariables = { } for each title in liveDataFeed.articlesByTitle ' update user variable if appropriate if userVariables.DoesExist(title) then userVariable = userVariables.Lookup(title) if type(userVariable.liveDataFeed) = "roAssociativeArray" and userVariable.liveDataFeed.id$ = liveDataFeed.id$ then description = liveDataFeed.articlesByTitle[title] userVariable.SetCurrentValue(description, true) updatedUserVariables.AddReplace(title, userVariable) end if end if next m.UpdateTimeClockEvents(updatedUserVariables) end if ' send internal message indicating that the data feed has been updated liveTextDataUpdatedEvent = { } liveTextDataUpdatedEvent["EventType"] = "LIVE_DATA_FEED_UPDATE" liveTextDataUpdatedEvent["EventData"] = liveDataFeed m.bsp.msgPort.PostMessage(liveTextDataUpdatedEvent) liveDataFeed.forceUpdate = false if liveDataFeed.useHeadRequest then ' Set headRequest so that next call will be a HEAD call liveDataFeed.headRequest = true end if updateInterval% = liveDataFeed.updateInterval% end if 'feed download, not HEAD else url$ = liveDataFeed.url.GetCurrentParameterValue() m.bsp.diagnostics.PrintDebug("Failure downloading Live Text Data feed " + url$ + ", responseCode = " + stri(event.GetResponseCode())) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_LIVE_TEXT_FEED_DOWNLOAD_FAILURE, url$ + chr(9) + stri(event.GetResponseCode()) + chr(9) + event.GetFailureReason()) if m.bsp.textDataFeedsNumRetries% >= m.bsp.textMaxRetries% then globalAA = GetGlobalAA() if not globalAA.networkInterfacePriorityLists.DoesExist("textFeedsDownloadEnabled") then ' TEDTODO stop endif networkInterfacePriorityList = globalAA.networkInterfacePriorityLists.Lookup("textFeedsDownloadEnabled") m.bsp.textDataFeedsBindingPriorityIndex = m.bsp.textDataFeedsBindingPriorityIndex + 1 if m.bsp.textDataFeedsBindingPriorityIndex >= networkInterfacePriorityList.count() then ' all network interfaces failed m.bsp.diagnostics.PrintDebug("### text data feed content download failed on all network interfaces") m.bsp.textDataFeedsBindingPriorityIndex = 0 else ' try next network interface m.bsp.diagnostics.PrintDebug("### text data feed content download failed. Try next network interface") endif else m.bsp.textDataFeedsNumRetries% = m.bsp.textDataFeedsNumRetries% + 1 m.bsp.diagnostics.PrintDebug("### retry text data feed content download") endif ' send internal message indicating that the data feed download failed liveTextDataUpdatedEvent = { } liveTextDataUpdatedEvent["EventType"] = "LIVE_DATA_FEED_UPDATE_FAILURE" liveTextDataUpdatedEvent["EventData"] = liveDataFeed m.bsp.msgPort.PostMessage(liveTextDataUpdatedEvent) ' remove the failed feed from the queue in case there's a problem with the feed, but only if it was on the queue if liveDataFeed.usage$ <> "text" then m.bsp.RemoveFailedFeedFromQueue() end if ' start a timer before attempting to retrieve the next feed in case there's a problem with the network updateInterval% = m.stateMachine.dataFeedRetryInterval% ' indicate that last download failed liveDataFeed.lastDownloadedFailed = true end if ' set a timer to update this live data feed liveDataFeed.RestartLiveDataFeedDownloadTimer(updateInterval%) end if return "HANDLED" end function Sub UpdateDataFeed(parameters as object) updateDataFeedParameter = parameters["dataFeed"] dataFeedId$ = updateDataFeedParameter.GetCurrentParameterValue() updateDataFeedMsg = { } updateDataFeedMsg["EventType"] = "UPDATE_DATA_FEED" updateDataFeedMsg["DataFeedId"] = dataFeedId$ m.msgPort.PostMessage(updateDataFeedMsg) end sub Sub CreateUserVariablesFromDataFeed(liveDataFeed as object) ' get the section name if lcase(liveDataFeed.userVariableAccess$) = "shared" then sectionName$ = "Shared" else sectionName$ = m.activePresentation$ end if sectionId% = m.GetDBSectionId(sectionName$) if sectionId% < 0 then m.AddDBSection(sectionName$) sectionId% = m.GetDBSectionId(sectionName$) end if categoryName$ = liveDataFeed.title$ categoryId% = m.GetDBCategoryId(sectionId%, categoryName$) ' desired behavour ' if the category does not exist, create it and populate it ' if the category does exist and the update interval is not Once ' add new user variables ' for matching user variables, update the current value but leave the default alone ' remove deleted user variables if categoryId% < 0 then m.AddDBCategory(sectionId%, categoryName$) categoryId% = m.GetDBCategoryId(sectionId%, categoryName$) for each item in liveDataFeed.items m.AddDBVariable(categoryId%, item.title$, item.description$, item.mediaUrl$, item.position%) next else if liveDataFeed.updateInterval% > 0 or liveDataFeed.forceUpdate then ' get existing user variables for this section (and Shared) / category userVariablesList = m.GetUserVariablesByCategoryList(categoryName$) ' populate an associative array with the items in the live data feed dataFeedItems = { } for each item in liveDataFeed.items dataFeedItems.AddReplace(item.title$, item) next ' delete each user variable that exists but is not in the data feed; update variables that remain userVariables = { } for each userVariable in userVariablesList userVariableName$ = userVariable.name$ userVariables.AddReplace(userVariableName$, userVariable) if not dataFeedItems.DoesExist(userVariableName$) then ' delete variable m.DeleteDBVariable(categoryId%, userVariableName$) else ' update variable dataFeedItem = dataFeedItems.Lookup(userVariableName$) m.UpdateDBVariable(categoryId%, dataFeedItem.title$, dataFeedItem.description$) m.UpdateDBVariableMediaUrl(categoryId%, dataFeedItem.title$, dataFeedItem.mediaUrl$) m.UpdateDBVariablePosition(categoryId%, dataFeedItem.title$, dataFeedItem.position%) end if next ' add new variables for each item in liveDataFeed.items if not userVariables.DoesExist(item.title$) then m.AddDBVariable(categoryId%, item.title$, item.description$, item.mediaUrl$, item.position%) end if next end if end sub ' TEDTODO - see references to syncSpec - no work done here yet. Function STUpdatingFromUSBEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") ' stop all playback, clear screen and background if type(m.bsp.sign) = "roAssociativeArray" and type(m.bsp.sign.zonesHSM) = "roArray" then for each zoneHSM in m.bsp.sign.zonesHSM if IsAudioPlayer(zoneHSM.audioPlayer) then zoneHSM.audioPlayer.Stop() zoneHSM.audioPlayer = invalid end if if type(zoneHSM.videoPlayer) = "roVideoPlayer" then zoneHSM.videoPlayer.Stop() zoneHSM.videoPlayer = invalid end if zoneHSM.ClearImagePlane() next end if m.bsp.sign = invalid videoMode = CreateObject("roVideoMode") if type(videoMode) = "roVideoMode" then resX = videoMode.GetResX() resY = videoMode.GetResY() videoMode.SetBackgroundColor(0) videoMode = invalid ' display update message on the screen twParams = { } twParams.LineCount = 1 twParams.TextMode = 2 twParams.Rotation = 0 twParams.Alignment = 1 r = CreateObject("roRectangle", 0, resY / 2 - resY / 64, resX, resY / 32) m.stateMachine.usbUpdateTW = CreateObject("roTextWidget", r, 1, 2, twParams) ' m.stateMachine.DisplayUSBUpdateStatus("Content update in progress. Do not remove the drive.") m.stateMachine.DisplayUSBUpdateStatus("Update in progress. Do not remove the drive.") end if ' read the sync specs and proceed with update if appropriate performingFWUpdate = false syncSpecFile$ = "/update/local-sync.json" syncSpecFilePath$ = m.stateMachine.storagePath$ + syncSpecFile$ if not FileExists(syncSpecFilePath$) then performingFWUpdate = true syncSpecFile$ = "/fwUpdate/fw-sync.json" syncSpecFilePath$ = m.stateMachine.storagePath$ + syncSpecFile$ end if m.stateMachine.newSync = CreateObject("roSyncSpec") ok = m.stateMachine.newSync.ReadFromFile(syncSpecFilePath$) if not ok then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_READ_SYNCSPEC_FAILURE, "newSync") m.bsp.diagnostics.PrintDebug("### USB drive has an invalid sync spec.") usbUpdateErrorEvent = { } usbUpdateErrorEvent["EventType"] = "USB_UPDATE_ERROR" usbUpdateErrorEvent["Message"] = "Update files are corrupt." m.stateMachine.msgPort.PostMessage(usbUpdateErrorEvent) return "HANDLED" end if ' perform security check usbContentUpdatePassword$ = GetGlobalAA().registrySettings.usbContentUpdatePassword$ ' check for signature file signaturePath$ = m.stateMachine.storagePath$ + "/update/signature.txt" signatureFile = CreateObject("roReadFile", signaturePath$) if type(signatureFile) = "roReadFile" then signatureFileExists = true signature$ = ReadAsciiFile(signaturePath$) else signatureFileExists = false end if signatureFile = invalid securityError = false if not signatureFileExists then ' no signature file and passphrase => error; no signature file and no passphrase => proceed with update if usbContentUpdatePassword$ <> "" then securityError = true end if else if usbContentUpdatePassword$ <> "" then ok = m.stateMachine.newSync.VerifySignature(signature$, usbContentUpdatePassword$) if not ok then securityError = true end if end if if securityError then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_USB_UPDATE_SECURITY_ERROR, "local-sync") m.bsp.diagnostics.PrintDebug("### USB update security error.") usbUpdateErrorEvent = { } usbUpdateErrorEvent["EventType"] = "USB_UPDATE_ERROR" usbUpdateErrorEvent["Message"] = "Update failed - an incorrect password was provided." m.stateMachine.msgPort.PostMessage(usbUpdateErrorEvent) return "HANDLED" end if if performingFWUpdate then globalAA = GetGlobalAA() globalAA.bsp.msgPort.DeferWatchdog(120) assetCollection = m.stateMachine.newSync.GetAssets("download") path$ = m.stateMachine.storagePath$ + "/fwUpdate/pool" pool = CreateObject("roAssetPool", path$) realizer = CreateObject("roAssetRealizer", pool, "/") event = realizer.Realize(assetCollection) ' check return value if event.GetEvent() <> m.stateMachine.EVENT_REALIZE_SUCCESS then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_REALIZE_FAILURE, stri(event.GetEvent()) + chr(9) + event.GetName() + chr(9) + event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### Realize failed " + stri(event.GetEvent()) + chr(9) + event.GetName() + chr(9) + event.GetFailureReason()) m.stateMachine.waitForStorageDetachedMsg$ = "Update failure (Realize). Remove the drive and the system will reboot." usbTransitionEvent = { } usbTransitionEvent["EventType"] = "USB_PERFORM_TRANSITION" m.stateMachine.msgPort.PostMessage(usbTransitionEvent) return "HANDLED" end if ' write out a new sync spec file?? m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### USB FIRWMARE UPDATE FILE DOWNLOAD COMPLETE") m.stateMachine.waitForStorageDetachedMsg$ = "Firmware update complete. Remove the drive and the system will reboot." usbTransitionEvent = { } usbTransitionEvent["EventType"] = "USB_PERFORM_TRANSITION" m.stateMachine.msgPort.PostMessage(usbTransitionEvent) return "HANDLED" else m.stateMachine.currentSync = CreateObject("roSyncSpec") ok = m.stateMachine.currentSync.ReadFromFile("local-sync.json") if not ok then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_READ_SYNCSPEC_FAILURE, "local-sync") m.bsp.diagnostics.PrintDebug("### Unable to read local-sync.json.") usbUpdateErrorEvent = { } usbUpdateErrorEvent["EventType"] = "USB_UPDATE_ERROR" usbUpdateErrorEvent["Message"] = "Unable to perform update." m.stateMachine.msgPort.PostMessage(usbUpdateErrorEvent) return "HANDLED" end if if m.stateMachine.newSync.EqualTo(m.stateMachine.currentSync) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "NO") m.bsp.diagnostics.PrintDebug("### USB drive has a spec that matches current-sync. Nothing more to do.") m.stateMachine.newSync = invalid updateSyncSpecMatchesEvent = { } updateSyncSpecMatchesEvent["EventType"] = "UPDATE_SYNC_SPEC_MATCHES" m.stateMachine.msgPort.PostMessage(updateSyncSpecMatchesEvent) return "HANDLED" end if m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "YES") m.BuildFileUpdateList(m.stateMachine.newSync) errorMsg = m.StartUpdateSyncListDownload() if type(errorMsg) = "roString" then usbUpdateErrorEvent = { } usbUpdateErrorEvent["EventType"] = "USB_UPDATE_ERROR" usbUpdateErrorEvent["Message"] = errorMsg m.stateMachine.msgPort.PostMessage(usbUpdateErrorEvent) end if end if return "HANDLED" else if event["EventType"] = "USB_PERFORM_TRANSITION" then stateData.nextState = m.stateMachine.stWaitForStorageDetached return "TRANSITION" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") else if event["EventType"] = "UPDATE_SYNC_SPEC_MATCHES" then m.stateMachine.waitForStorageDetachedMsg$ = "The content on the USB drive matches the content on the card. Remove the drive and the system will reboot." stateData.nextState = m.stateMachine.stWaitForStorageDetached return "TRANSITION" else if event["EventType"] = "USB_UPDATE_ERROR" then errorMsg$ = event["Message"] m.stateMachine.waitForStorageDetachedMsg$ = errorMsg$ + " Remove the drive and the system will reboot." stateData.nextState = m.stateMachine.stWaitForStorageDetached return "TRANSITION" else if event["EventType"] = "PREPARE_FOR_RESTART" or event["EventType"] = "SWITCH_PRESENTATION" or event["EventType"] = "CONTENT_UPDATED" then ' consume these events during USB updates return "HANDLED" end if end if else if type(event) = "roTimerEvent" or type(event) = "roUrlEvent" then ' consume these events during USB updates return "HANDLED" else if type(event) = "roAssetFetcherProgressEvent" then m.bsp.diagnostics.PrintDebug("### File update progress " + event.GetFileName() + str(event.GetCurrentFilePercentage())) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_FILE_DOWNLOAD_PROGRESS, event.GetFileName() + chr(9) + str(event.GetCurrentFilePercentage())) fileIndex% = event.GetFileIndex() fileItem = m.stateMachine.newSync.GetFile("download", fileIndex%) if event.GetCurrentFilePercentage() = 0 then m.stateMachine.DisplayUSBUpdateStatus("Downloading " + event.GetFileName() + " (" + StripLeadingSpaces(stri(fileIndex%)) + " of " + StripLeadingSpaces(stri(m.listOfUpdateFiles.Count())) + "). Do not remove the drive.") end if return "HANDLED" else if (type(event) = "roAssetFetcherEvent") then if event.GetUserData() = "USB" then nextState = m.HandleUSBAssetFetcherEvent(event) if type(nextState) = "roAssociativeArray" then stateData.nextState = nextState return "TRANSITION" end if return "HANDLED" end if ' this event is currently not received - the script gets the typical roAssetPoolEvent errors. ' else if type(event) = "roStorageDetached" then ' m.stateMachine.DisplayUSBUpdateStatus("The drive was removed before the update was complete - the system will reboot shortly.") ' sleep(5000) ' RebootSystem() end if stateData.nextState = m.superState return "SUPER" end function Sub BuildFileUpdateList(syncSpec as object) fileInPoolStatus = m.bsp.assetPool.QueryFiles(syncSpec) m.listOfUpdateFiles = CreateObject("roArray", 10, true) for each fileName in fileInPoolStatus fileInPool = fileInPoolStatus.Lookup(fileName) if not fileInPool then m.listOfUpdateFiles.push(fileName) end if next end sub Function StartUpdateSyncListDownload() as object m.bsp.diagnostics.PrintDebug("### Start usb update sync list download") m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_DOWNLOAD_START, "") m.bsp.assetPool.ReserveMegabytes(50) m.assetFetcher = CreateObject("roAssetFetcher", m.bsp.assetPool) m.assetFetcher.SetUserData("USB") m.assetFetcher.SetPort(m.stateMachine.msgPort) m.assetFetcher.AddHeader("User-Agent", m.bsp.userAgent$) if not m.bsp.assetPool.ProtectAssets("USB", m.stateMachine.currentSync) then ' don't allow download to delete current files m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, "AssetPool Protect Failure") m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + "AssetPool Protect Failure") return "Update failure (ProtectFiles)." end if prefix$ = "file:///" + m.stateMachine.storagePath$ + "/update/" m.assetFetcher.SetRelativeLinkPrefix(prefix$) if not m.assetFetcher.AsyncDownload(m.stateMachine.newSync) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_IMMEDIATE_FAILURE, m.assetFetcher.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### AsyncDownload failed: " + m.assetFetcher.GetFailureReason()) return "Update failure (AsyncDownload)." end if return invalid end function Function HandleUSBAssetFetcherEvent(event as object) as object m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### usb update pool_event") if (event.GetEvent() = m.stateMachine.POOL_EVENT_FILE_DOWNLOADED) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_FILE_DOWNLOAD_COMPLETE, event.GetName()) m.bsp.diagnostics.PrintDebug("### File downloaded " + event.GetName()) else if (event.GetEvent() = m.stateMachine.POOL_EVENT_FILE_FAILED) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_FILE_DOWNLOAD_FAILURE, event.GetName() + chr(9) + event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### File failed " + event.GetName() + ": " + event.GetFailureReason()) else if (event.GetEvent() = m.stateMachine.POOL_EVENT_ALL_FAILED) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_FAILURE, event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### Sync failed: " + event.GetFailureReason()) m.stateMachine.waitForStorageDetachedMsg$ = "Update failure (file failure). Remove the drive and the system will reboot." return m.stateMachine.stWaitForStorageDetached else if (event.GetEvent() = m.stateMachine.POOL_EVENT_ALL_DOWNLOADED) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_DOWNLOAD_COMPLETE, "") m.bsp.diagnostics.PrintDebug("### All files downloaded") oldSyncSpecScriptsOnly = m.stateMachine.currentSync.FilterFiles("download", { group: "script" }) newSyncSpecScriptsOnly = m.stateMachine.newSync.FilterFiles("download", { group: "script" }) rebootRequired = false if not oldSyncSpecScriptsOnly.FilesEqualTo(newSyncSpecScriptsOnly) then ' Protect all the media files that the current sync spec is using in case we fail part way through and need to continue using it. if not (m.bsp.assetPool.ProtectAssets("current", m.stateMachine.currentSync) and m.bsp.assetPool.ProtectAssets("new", m.stateMachine.newSync)) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, "AssetPool Protect Failure") m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + "AssetPool Protect Failure") m.stateMachine.waitForStorageDetachedMsg$ = "Update failure (ProtectFiles). Remove the drive and the system will reboot." return m.stateMachine.stWaitForStorageDetached end if realizer = CreateObject("roAssetRealizer", m.bsp.assetPool, "/") globalAA = GetGlobalAA() globalAA.bsp.msgPort.DeferWatchdog(120) event = realizer.Realize(newSyncSpecScriptsOnly) realizer = invalid if event.GetEvent() <> m.stateMachine.EVENT_REALIZE_SUCCESS then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_REALIZE_FAILURE, stri(event.GetEvent()) + chr(9) + event.GetName() + chr(9) + event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### Realize failed " + stri(event.GetEvent()) + chr(9) + event.GetName() + chr(9) + event.GetFailureReason()) m.stateMachine.waitForStorageDetachedMsg$ = "Update failure (Realize). Remove the drive and the system will reboot." return m.stateMachine.stWaitForStorageDetached end if end if ' Save to current-sync.json then do cleanup if not m.stateMachine.newSync.WriteToFile("local-sync.json") then stop m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### USB UPDATE FILE DOWNLOAD COMPLETE") m.stateMachine.waitForStorageDetachedMsg$ = "Content update complete. Remove the drive and the system will reboot." return m.stateMachine.stWaitForStorageDetached end if return invalid end function Function STWaitForStorageDetachedEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") ' check to see if the drive is still in the device du = CreateObject("roStorageInfo", m.stateMachine.storagePath$) if type(du) = "roStorageInfo" then m.stateMachine.DisplayUSBUpdateStatus(m.stateMachine.waitForStorageDetachedMsg$) else m.stateMachine.DisplayUSBUpdateStatus("The drive was removed before the update was complete - the system will reboot shortly.") sleep(5000) RebootSystem() end if return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") else if event["EventType"] = "PREPARE_FOR_RESTART" or event["EventType"] = "SWITCH_PRESENTATION" or event["EventType"] = "CONTENT_UPDATED" then ' consume these events during USB updates return "HANDLED" end if end if else if type(event) = "roTimerEvent" or type(event) = "roUrlEvent" then ' consume these events during USB updates return "HANDLED" else if type(event) = "roStorageDetached" then m.stateMachine.logging.FlushLogFile() RebootSystem() end if stateData.nextState = m.superState return "SUPER" end function Function CheckForUSBUpdate(storagePath$ as string) as object syncSpecFilePath$ = storagePath$ + "/update/local-sync.json" if FileExists(syncSpecFilePath$) then return true end if syncSpecFilePath$ = storagePath$ + "/fwUpdate/fw-sync.json" if FileExists(syncSpecFilePath$) then return true end if return false end function Sub DisplayUSBUpdateStatus(status$ as string) ' If the text widget is invalid, ' log it instead of show on screen if type(m.usbUpdateTW) <> "roTextWidget" then m.bsp.diagnostics.PrintDebug(status$) else m.usbUpdateTW.Clear() m.usbUpdateTW.PushString(status$) m.usbUpdateTW.Show() end if end sub Sub UpdateTimeClockEvents(updatedUserVariables as object) ' m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_LIVE_TEXT_FEED_DOWNLOAD_FAILURE, url$ + chr(9) + stri(event.GetResponseCode()) + chr(9) + event.GetFailureReason()) if type(m.bsp.sign) = "roAssociativeArray" then sign = m.bsp.sign if type(sign.zonesHSM) = "roArray" then for each zoneHSM in sign.zonesHSM if type(zoneHSM.activeState) = "roAssociativeArray" then activeState = zoneHSM.activeState if type(activeState.timeClockEvents) = "roArray" then for each timeClockEvent in activeState.timeClockEvents if type(timeClockEvent.userVariable) = "roAssociativeArray" then updatedUserVariable = updatedUserVariables.Lookup(timeClockEvent.userVariableName$) if type(updatedUserVariable) = "roAssociativeArray" then dateTime$ = updatedUserVariable.GetCurrentValue() dateTime = FixDateTime(dateTime$) if type(dateTime) = "roDateTime" then ' if timer is in the future, set it. if IsTimeoutInFuture(dateTime) setTimer = true m.bsp.diagnostics.PrintDebug("Set timeout to " + dateTime.GetString()) else setTimer = false end if if type(timeClockEvent.timer) = "roTimer" then timeClockEvent.timer.Stop() else if setTimer then timeClockEvent.timer = CreateObject("roTimer") end if if setTimer then timeClockEvent.timer.SetDateTime(dateTime) timeClockEvent.timer.SetPort(zoneHSM.msgPort) timeClockEvent.timer.Start() end if else m.bsp.diagnostics.PrintDebug("Timeout specification " + dateTime$ + " is invalid") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_INVALID_DATE_TIME_SPEC, dateTime$) end if end if end if next end if end if next end if end if end sub Function GenerateNonce(systemTime as object) as string ' Nonce just needs to be a reasonably unique alphanumeric string bytes = CreateObject("roByteArray") bytes.FromAsciiString(systemTime.GetUtcDateTime().GetString()) nonce = bytes.ToBase64String() ' Remove non word chars - just replace with arbitrary character rx = CreateObject("roRegEx", "\W", "") return rx.ReplaceAll(nonce, "z") end function Function GenerateTimestamp(systemTime as object) as string return systemTime.GetUtcDateTime().ToSecondsSinceEpoch().ToStr() end function Function GenerateOAuthSignature(urlTransfer as object, authenticationData as object, nonce as string, timestamp as string) as string url$ = urlTransfer.GetUrl() ' Generate sorted array of all parameters (header and query string) paramArray = CreateObject("roArray", 8, TRUE) ' First, get parameters from URL query string queryIndex = instr(1, url$, "?") if queryIndex > 0 then params = mid(url$, queryIndex + 1).tokenize("&") for each param in params nameval = param.tokenize("=") if nameval.Count() > 1 then paramItem = { } paramItem.name = nameVal[0] paramItem.value = nameVal[1] paramArray.push(paramItem) end if next end if ' Next, add the oauth parameters paramArray.push({ name: "oauth_consumer_key", value: authenticationData.ConsumerKey }) paramArray.push({ name: "oauth_nonce", value: urlTransfer.Escape(nonce) }) paramArray.push({ name: "oauth_signature_method", value: "HMAC-SHA1" }) paramArray.push({ name: "oauth_timestamp", value: timestamp }) paramArray.push({ name: "oauth_token", value: urlTransfer.Escape(authenticationData.AuthToken) }) paramArray.push({ name: "oauth_version", value: "1.0" }) ' Now sort the parameter array max = paramArray.Count() sortedParamArray = CreateObject("roArray", max, FALSE) while (paramArray.Count() > 0) index = 0 for i = 1 to paramArray.Count() - 1 if paramArray[i].name < paramArray[index].name then index = i end if end for sortedParamArray.push(paramArray[index]) paramArray.Delete(index) end while ' normalized parameter string normParams$ = "" for i = 0 to sortedParamArray.Count() - 1 normParams$ = normParams$ + urlTransfer.Escape(sortedParamArray[i].name) + "=" + urlTransfer.Escape(sortedParamArray[i].value) if i < sortedParamArray.Count() - 1 then normParams$ = normParams$ + "&" end if end for ' create signature base string if authenticationData.DoesExist("HttpMethod") and type(authenticationData.HttpMethod) = "roString" then sigBase$ = authenticationData.HttpMethod + "&" else sigBase$ = "GET&" end if if (queryIndex > 0) normUrl$ = left(url$, queryIndex - 1) else normUrl$ = url$ end if sigBase$ = sigBase$ + urlTransfer.Escape(normUrl$) + "&" + urlTransfer.Escape(normParams$) hashGen = CreateObject("roHashGenerator", "SHA1") hashGen.SetObfuscatedHmacKey(authenticationData.EncryptedTwitterSecrets) ' get hash - we will NOT escape this here - that will be done when we generate the header hashStr$ = hashGen.hash(sigBase$).ToBase64String() return hashStr$ end function Function GetOAuthAuthorizationHeader(urlTransfer as object, authenticationData as object) as string systemTime = CreateObject("roSystemTime") nonce = GenerateNonce(systemTime) timestamp = GenerateTimestamp(systemTime) s = "OAuth " s = s + "oauth_consumer_key=" + chr(34) + urlTransfer.Escape(authenticationData.ConsumerKey) + chr(34) + "," s = s + "oauth_nonce=" + chr(34) + nonce + chr(34) + "," s = s + "oauth_signature=" + chr(34) + urlTransfer.Escape(GenerateOAuthSignature(urlTransfer, authenticationData, nonce, timestamp)) + chr(34) + "," s = s + "oauth_signature_method=" + chr(34) + "HMAC-SHA1" + chr(34) + "," s = s + "oauth_timestamp=" + chr(34) + timestamp + chr(34) + "," s = s + "oauth_token=" + chr(34) + urlTransfer.Escape(authenticationData.AuthToken) + chr(34) + "," s = s + "oauth_version=" + chr(34) + "1.0" + chr(34) return s end function Sub RemoveFailedFeedFromQueue() ' remove failed feed - it will get added back to queue when retry timeout occurs failedFeed = m.liveDataFeedsToDownload.Shift() end sub Sub RetrievePendingLiveDataFeed(liveDataFeeds as object) if m.liveDataFeedsToDownload.Count() > 0 then m.RetrieveLiveDataFeed(liveDataFeeds, m.liveDataFeeds[m.liveDataFeedsToDownload[0]]) end if end sub Sub QueueRetrieveLiveDataFeed(liveDataFeeds as object, liveDataFeed as object) ' download feeds that are neither MRSS nor content immediately (simple RSS) if liveDataFeed.usage$ = "text" then m.RetrieveLiveDataFeed(liveDataFeeds, liveDataFeed) else m.liveDataFeedsToDownload.push(liveDataFeed.id$) ' launch download of first feed if m.liveDataFeedsToDownload.Count() = 1 then m.RetrieveLiveDataFeed(liveDataFeeds, liveDataFeed) end if end if end sub Sub AdvanceToNextLiveDataFeedInQueue(liveDataFeeds as object) ' Remove top entry m.liveDataFeedsToDownload.Shift() if m.liveDataFeedsToDownload.Count() > 0 then liveDataFeedId = m.liveDataFeedsToDownload[0] liveDataFeed = m.liveDataFeeds.Lookup(liveDataFeedId) m.RetrieveLiveDataFeed(liveDataFeeds, liveDataFeed) end if end sub Sub RetrieveLiveDataFeed(liveDataFeeds as object, liveDataFeed as object) url$ = liveDataFeed.url.GetCurrentParameterValue() auth = liveDataFeed.authenticationData if liveDataFeed.headRequest then m.diagnostics.PrintDebug("### Checking live text data feed from " + url$) m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_CHECK_LIVE_TEXT_FEED_HEAD, url$) else m.diagnostics.PrintDebug("### Retrieve live text data feed from " + url$) m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_RETRIEVE_LIVE_TEXT_FEED, url$) end if liveDataFeed.rssURLXfer = CreateObject("roUrlTransfer") liveDataFeed.rssURLXfer.SetUrl(url$) liveDataFeed.rssURLXfer.SetPort(m.msgPort) if not liveDataFeed.headRequest then liveDataFeed.rssFileName$ = m.GetRSSTempFilename() end if liveDataFeed.rssURLXfer.SetTimeout(55000) ' 55 second timeout ' Set User agent string - see in there is a custom parser function for the user agent userAgent$ = "" if liveDataFeed.customUserAgent$ <> "" then data = { userAgent: m.userAgent$ } retVal = eval(liveDataFeed.customUserAgent$ + "(m, data)") ERR_NORMAL_END = &hFC if retVal = ERR_NORMAL_END then if IsString(data.userAgent) then userAgent$ = data.userAgent m.diagnostics.PrintDebug("Using custom user agent string for " + liveDataFeed.id$ + ": " + userAgent$) end if else ' log the failure m.diagnostics.PrintDebug("Failure invoking Eval to parse custom User Agent for data feed: return value = " + stri(retVal) + ", parser is " + liveDataFeed.customUserAgent$) m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_CUSTOM_USER_AGENT_FAILURE, stri(retVal) + chr(9) + liveDataFeed.customUserAgent$) end if end if if userAgent$ <> "" then liveDataFeed.rssURLXfer.SetUserAgent(userAgent$) else liveDataFeed.rssURLXfer.SetUserAgent(m.userAgent$) end if ' Set authorization header, if authentication data is present if type(auth) = "roAssociativeArray" and type(auth.AuthType) = "roString" then if auth.AuthType = "OAuth 1.0a" then ' Set OAuth header if not liveDataFeed.rssURLXfer.AddHeader("Authorization", GetOAuthAuthorizationHeader(liveDataFeed.rssURLXfer, auth)) then m.diagnostics.PrintDebug("Failed to set authorization header, reason: " + liveDataFeed.rssURLXfer.GetFailureReason()) end if end if end if aa = GetBinding("textFeedsDownloadEnabled", m.textDataFeedsBindingPriorityIndex) binding = aa.network_interface m.textDataFeedsBindingPriorityIndex = aa.priorityIndex m.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for RetrieveLiveDataFeed is ", binding)) ok = liveDataFeed.rssURLXfer.BindToInterface(binding) if not ok then stop if liveDataFeed.headRequest then liveDataFeed.rssURLXfer.AsyncHead() else liveDataFeed.rssURLXfer.AsyncGetToFile(liveDataFeed.rssFileName$) end if liveDataFeeds.AddReplace(stri(liveDataFeed.rssURLXfer.GetIdentity()), liveDataFeed) end sub 'endregion 'region Networking State Machine ' ************************************************* ' ' Networking State Machine ' ' ************************************************* Function newNetworkingStateMachine(bsp as object, msgPort as object) as object NetworkingStateMachine = newHSM() NetworkingStateMachine.InitialPseudostateHandler = InitializeNetworkingStateMachine NetworkingStateMachine.bsp = bsp NetworkingStateMachine.msgPort = msgPort NetworkingStateMachine.systemTime = bsp.systemTime NetworkingStateMachine.diagnostics = bsp.diagnostics NetworkingStateMachine.logging = bsp.logging NetworkingStateMachine.RestartContentDownloadWindowStartTimer = RestartContentDownloadWindowStartTimer NetworkingStateMachine.RestartContentDownloadWindowEndTimer = RestartContentDownloadWindowEndTimer NetworkingStateMachine.RestartWindowStartTimer = RestartWindowStartTimer NetworkingStateMachine.RestartWindowEndTimer = RestartWindowEndTimer NetworkingStateMachine.SetSystemInfo = SetSystemInfo NetworkingStateMachine.AddMiscellaneousHeaders = AddMiscellaneousHeaders NetworkingStateMachine.DeviceDownloadItems = CreateObject("roArray", 8, true) NetworkingStateMachine.DeviceDownloadItemsPendingUpload = CreateObject("roArray", 8, true) NetworkingStateMachine.AddDeviceDownloadItem = AddDeviceDownloadItem NetworkingStateMachine.UploadDeviceDownload = UploadDeviceDownload NetworkingStateMachine.FileListPendingUpload = true NetworkingStateMachine.DeviceDownloadProgressItems = { } NetworkingStateMachine.DeviceDownloadProgressItemsPendingUpload = { } NetworkingStateMachine.PushDeviceDownloadProgressItem = PushDeviceDownloadProgressItem NetworkingStateMachine.AddDeviceDownloadProgressItem = AddDeviceDownloadProgressItem NetworkingStateMachine.UploadDeviceDownloadProgressItems = UploadDeviceDownloadProgressItems NetworkingStateMachine.UploadDeviceDownloadProgressFileList = UploadDeviceDownloadProgressFileList NetworkingStateMachine.BuildFileDownloadList = BuildFileDownloadList NetworkingStateMachine.SendTrafficUpload = SendTrafficUpload NetworkingStateMachine.UploadTrafficDownload = UploadTrafficDownload NetworkingStateMachine.UploadMRSSTrafficDownload = UploadMRSSTrafficDownload NetworkingStateMachine.pendingMRSSContentDownloaded# = 0 NetworkingStateMachine.lastMRSSContentDownloaded# = 0 NetworkingStateMachine.EventItems = CreateObject("roArray", 8, true) NetworkingStateMachine.AddEventItem = AddEventItem NetworkingStateMachine.UploadEvent = UploadEvent NetworkingStateMachine.DeviceErrorItems = CreateObject("roArray", 8, true) NetworkingStateMachine.AddDeviceErrorItem = AddDeviceErrorItem NetworkingStateMachine.UploadDeviceError = UploadDeviceError NetworkingStateMachine.deviceDownloadProgressUploadURL = invalid NetworkingStateMachine.deviceDownloadUploadURL = invalid NetworkingStateMachine.trafficDownloadUploadURL = invalid NetworkingStateMachine.mrssTrafficDownloadUploadURL = invalid NetworkingStateMachine.eventUploadURL = invalid NetworkingStateMachine.deviceErrorUploadURL = invalid NetworkingStateMachine.LogProtectFilesFailure = LogProtectFilesFailure ' logging NetworkingStateMachine.UploadLogFiles = UploadLogFiles NetworkingStateMachine.UploadLogFileHandler = UploadLogFileHandler NetworkingStateMachine.uploadLogFileURLXfer = invalid NetworkingStateMachine.uploadLogFileURL$ = "" NetworkingStateMachine.uploadLogFolder = "logs" NetworkingStateMachine.uploadLogArchiveFolder = "archivedLogs" NetworkingStateMachine.uploadLogFailedFolder = "failedLogs" NetworkingStateMachine.enableLogDeletion = true NetworkingStateMachine.AddUploadHeaders = AddUploadHeaders NetworkingStateMachine.RebootAfterEventsSent = RebootAfterEventsSent NetworkingStateMachine.WaitForTransfersToComplete = WaitForTransfersToComplete NetworkingStateMachine.ParseAWSURLs = ParseAWSURLs NetworkingStateMachine.SetRemoteSnapshotUrls = SetRemoteSnapshotUrls NetworkingStateMachine.UpdateRemoteSnapshotSettingsFromSyncSpec = UpdateRemoteSnapshotSettingsFromSyncSpec NetworkingStateMachine.ResetDownloadTimerToDoRetry = ResetDownloadTimerToDoRetry NetworkingStateMachine.retryInterval% = 60 NetworkingStateMachine.numRetries% = 0 NetworkingStateMachine.maxRetries% = 3 NetworkingStateMachine.networkingBindingPriorityIndex = 0 NetworkingStateMachine.logFileUploadsBindingPriorityIndex = 0 NetworkingStateMachine.logFileUploadsNumRetries% = 0 NetworkingStateMachine.logFileUploadsMaxRetries% = 3 NetworkingStateMachine.fileDownloadFailureCount% = 0 NetworkingStateMachine.maxFileDownloadFailures% = 3 NetworkingStateMachine.POOL_EVENT_FILE_DOWNLOADED = 1 NetworkingStateMachine.POOL_EVENT_FILE_FAILED = -1 NetworkingStateMachine.POOL_EVENT_ALL_DOWNLOADED = 2 NetworkingStateMachine.POOL_EVENT_ALL_FAILED = -2 NetworkingStateMachine.SYNC_ERROR_CANCELLED = -10001 NetworkingStateMachine.SYNC_ERROR_CHECKSUM_MISMATCH = -10002 NetworkingStateMachine.SYNC_ERROR_EXCEPTION = -10003 NetworkingStateMachine.SYNC_ERROR_DISK_ERROR = -10004 NetworkingStateMachine.SYNC_ERROR_POOL_UNSATISFIED = -10005 NetworkingStateMachine.EVENT_REALIZE_SUCCESS = 101 NetworkingStateMachine.stTop = NetworkingStateMachine.newHState(bsp, "Top") NetworkingStateMachine.stTop.HStateEventHandler = STTopEventHandler NetworkingStateMachine.stNetworkScheduler = NetworkingStateMachine.newHState(bsp, "NetworkScheduler") NetworkingStateMachine.stNetworkScheduler.HStateEventHandler = STNetworkSchedulerEventHandler NetworkingStateMachine.stNetworkScheduler.QueueSnapshotForBSN = QueueSnapshotForBSN NetworkingStateMachine.stNetworkScheduler.UploadSnapshotToBSN = UploadSnapshotToBSN NetworkingStateMachine.stNetworkScheduler.UploadSnapshotToBSNEE = UploadSnapshotToBSNEE NetworkingStateMachine.stNetworkScheduler.UploadSnapshotToSFN = UploadSnapshotToSFN NetworkingStateMachine.stNetworkScheduler.BuildStringToSign = BuildStringToSign NetworkingStateMachine.stNetworkScheduler.superState = NetworkingStateMachine.stTop NetworkingStateMachine.stWaitForTimeout = NetworkingStateMachine.newHState(bsp, "WaitForTimeout") NetworkingStateMachine.stWaitForTimeout.HStateEventHandler = STWaitForTimeoutEventHandler NetworkingStateMachine.stWaitForTimeout.ProcessSupervisorCheckForUpdateScheduleMessage = ProcessSupervisorCheckForUpdateScheduleMessage NetworkingStateMachine.stWaitForTimeout.superState = NetworkingStateMachine.stNetworkScheduler NetworkingStateMachine.stRetrievingSyncList = NetworkingStateMachine.newHState(bsp, "RetrievingSyncList") NetworkingStateMachine.stRetrievingSyncList.StartSync = StartSync NetworkingStateMachine.stRetrievingSyncList.SyncSpecXferEvent = SyncSpecXferEvent NetworkingStateMachine.stRetrievingSyncList.GetSyncSpecChangeType = GetSyncSpecChangeType NetworkingStateMachine.stRetrievingSyncList.HandleSyncSpecUnchanged = HandleSyncSpecUnchanged NetworkingStateMachine.stRetrievingSyncList.HStateEventHandler = STRetrievingSyncListEventHandler NetworkingStateMachine.stRetrievingSyncList.superState = NetworkingStateMachine.stNetworkScheduler NetworkingStateMachine.stRetrievingSyncList.ConfigureNetwork = ConfigureNetwork NetworkingStateMachine.stRetrievingSyncList.UpdateRegistrySetting = UpdateRegistrySetting NetworkingStateMachine.stRetrievingSyncList.UpdateBoolRegistrySetting = UpdateBoolRegistrySetting ' TEDTODO - rename these functions NetworkingStateMachine.stRetrievingSyncList.UpdateSettingsFromSyncSpec = UpdateSettingsFromSyncSpec NetworkingStateMachine.stRetrievingSyncList.ProcessSyncSpecSettingsUpdates0 = ProcessSyncSpecSettingsUpdates0 NetworkingStateMachine.stRetrievingSyncList.ProcessSyncSpecSettingsUpdates2 = ProcessSyncSpecSettingsUpdates2 NetworkingStateMachine.stRetrievingSyncList.ProcessSyncSpecSettingsUpdates3 = ProcessSyncSpecSettingsUpdates3 NetworkingStateMachine.stDownloadingSyncFiles = NetworkingStateMachine.newHState(bsp, "DownloadingSyncFiles") NetworkingStateMachine.stDownloadingSyncFiles.StartSyncListDownload = StartSyncListDownload NetworkingStateMachine.stDownloadingSyncFiles.HandleAssetFetcherEvent = HandleAssetFetcherEvent NetworkingStateMachine.stDownloadingSyncFiles.HStateEventHandler = STDownloadingSyncFilesEventHandler NetworkingStateMachine.stDownloadingSyncFiles.superState = NetworkingStateMachine.stNetworkScheduler NetworkingStateMachine.topState = NetworkingStateMachine.stTop return NetworkingStateMachine end function Function InitializeNetworkingStateMachine() as object activeSyncSpec = GetActiveSyncSpec() activeSettings = GetActiveSettings() activeSyncSpecSettings = GetActiveSyncSpecSettings() ' 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 OnlyDownloadIfCached$ = GetGlobalAA().registrySettings.OnlyDownloadIfCached$ if OnlyDownloadIfCached$ = "true" then m.downloadOnlyIfCached = true end if end if nc = invalid globalAA = GetGlobalAA() ' Load up the current sync specification so we have it ready m.currentSync = GetActiveSyncSpec() base$ = activeSyncSpecSettings.base nextURL = GetURL(base$, activeSyncSpecSettings.next) m.eventURL = GetURL(base$, activeSyncSpecSettings.event) m.deviceDownloadProgressURL = GetURL(base$, activeSyncSpecSettings.devicedownloadprogress) m.deviceDownloadURL = GetURL(base$, activeSyncSpecSettings.devicedownload) m.trafficDownloadURL = GetURL(base$, activeSyncSpecSettings.trafficdownload) m.deviceErrorURL = GetURL(base$, activeSyncSpecSettings.deviceerror) m.uploadLogFileURL$ = GetURL(base$, activeSyncSpecSettings.uploadlogs) timezone = activeSettings.timezone if timezone <> "" then m.systemTime.SetTimeZone(timezone) end if m.diagnostics.PrintTimestamp() m.diagnostics.PrintDebug("### Current active sync list suggests next URL of " + nextURL) if nextURL = "" then stop if m.eventURL = "" then stop ' BSNRT ' ' TEDTODO - okay that accessToken doesn't exist for sfn? m.accessToken$ = activeSyncSpecSettings.accessToken user$ = activeSyncSpecSettings.user password$ = activeSyncSpecSettings.password if user$ <> "" or password$ <> "" then m.setUserAndPassword = true m.enableBasicAuthentication = activeSyncSpecSettings.enableBasicAuthentication else m.setUserAndPassword = false m.enableBasicAuthentication = false end if inheritNetworkProperties = activeSettings.inheritNetworkProperties ' get net connect parameters, setup timer, and rate limits timeBetweenNetConnects$ = activeSyncSpecSettings.timeBetweenNetConnects contentDownloadsRestricted = activeSettings.contentDownloadsRestricted contentDownloadRangeStart = activeSettings.contentDownloadRangeStart contentDownloadRangeLength = activeSettings.contentDownloadRangeLength m.timeBetweenNetConnects% = val(timeBetweenNetConnects$) m.diagnostics.PrintDebug("### Time between net connects = " + timeBetweenNetConnects$) m.currentTimeBetweenNetConnects% = m.timeBetweenNetConnects% m.networkTimerDownload = { } m.networkTimerDownload.timerType = "TIMERTYPEPERIODIC" m.networkTimerDownload.timerInterval = m.timeBetweenNetConnects% newTimer = CreateObject("roTimer") newTimer.SetPort(m.msgPort) m.networkTimerDownload.timer = newTimer ' get time range for when net connects can occur if contentDownloadsRestricted then m.diagnostics.PrintDebug("### Content downloads are restricted to the time from " + stri(contentDownloadRangeStart) + " for " + stri(contentDownloadRangeLength) + " minutes.") else m.diagnostics.PrintDebug("### Content downloads are unrestricted") end if ' program the rate limit for networking notInDownloadWindow = false if contentDownloadsRestricted then currentTime = m.systemTime.GetLocalDateTime() startOfRange% = GetActiveSettings().contentDownloadRangeStart endOfRange% = startOfRange% + GetActiveSettings().contentDownloadRangeLength notInDownloadWindow = OutOfDownloadWindow(currentTime, startOfRange%, endOfRange%) end if ' remote snapshot values m.SetRemoteSnapshotUrls() m.pendingSnapshotsToUpload = { } ' diagnostic web server dwsRebootRequired = false if not GetGlobalAA().useSupervisorConfigSpec then dwsRebootRequired = GetAndSaveDWSParams(activeSettings, GetGlobalAA().registrySettings) endif downloadRateLimits = GetDownloadRateLimits(activeSettings, not notInDownloadWindow) SetDownloadRateLimits(m.diagnostics, downloadRateLimits) m.bsp.obfuscatedEncryptionKey = "" if dwsRebootRequired then RebootSystem() return m.stRetrievingSyncList end function Sub SetRemoteSnapshotUrls() m.deviceScreenShotsTemporaryStorage$ = GetActiveSyncSpecSettings().deviceScreenShotsTemporaryStorage m.incomingDeviceScreenshotsQueue$ = GetActiveSyncSpecSettings().incomingDeviceScreenshotsQueue m.awsAccessKeyId$ = GetActiveSyncSpecSettings().awsAccessKeyId m.awsSecretAccessKey$ = GetActiveSyncSpecSettings().awsSecretAccessKey m.awsSessionToken$ = GetActiveSyncSpecSettings().awsSessionToken m.ParseAWSURLs() m.uploadSnapshotsURL$ = GetActiveSyncSpecSettings().uploadSnapshots m.uploadDeviceScreenshotHandlerAddress = GetActiveSyncSpecSettings().uploadDeviceScreenshotHandlerAddress m.securityToken = GetActiveSyncSpecSettings().securityToken end sub Sub ParseAWSURLs() m.AwsBucketName = "" ' bsnm2 m.AwsBaseAddress = "" ' https://s3.amazonaws.com/ m.AwsIncomingDirectory = "" ' DeviceScreenShots/Incoming/ m.AwsSqsHost = "" ' sqs.us-east-1.amazonaws.com m.AwsSqsAbsolutePath = "" ' /965175186373/bsn-QA-RS-IDSS m.AwsSqsService = "" ' sqs m.AwsSqsRegion = "" ' us-east-1 if m.deviceScreenShotsTemporaryStorage$ <> "" then regexSlash = CreateObject("roRegEx", "/", "i") regexDot = CreateObject("roRegEx", "\.", "i") urlItems = regexSlash.Split(m.deviceScreenShotsTemporaryStorage$) hostItems = regexDot.Split(urlItems[2]) sqsUrlItems = regexSlash.Split(m.incomingDeviceScreenshotsQueue$) ' Get Bucket m.AwsBucketName = hostItems[0] ' Get Base Address m.AwsBaseAddress = urlItems[0] + "//" + hostItems[1] for i = 2 to (hostItems.Count() - 1) m.AwsBaseAddress = m.AwsBaseAddress + "." + hostItems[i] next m.AwsBaseAddress = m.AwsBaseAddress + "/" ' Get Resource Key m.AwsIncomingDirectory = "" for i = 3 to (urlItems.Count() - 1) m.AwsIncomingDirectory = m.AwsIncomingDirectory + urlItems[i] + "/" next ' sqs url host m.AwsSqsHost = sqsUrlItems[2] ' sqs absolute path for i = 3 to (sqsUrlItems.Count() - 1) m.AwsSqsAbsolutePath = m.AwsSqsAbsolutePath + "/" + sqsUrlItems[i] next ' sqs service,region sqsHostItems = regexDot.Split(m.AwsSqsHost) m.AwsSqsService = sqsHostItems[0] m.AwsSqsRegion = sqsHostItems[1] end if end sub Function OutOfDownloadWindow(currentTime as object, startOfRangeInMinutes% as integer, endOfRangeInMinutes% as integer) secondsPerDay% = 24 * 60 * 60 secondsSinceMidnight% = currentTime.GetHour() * 3600 + currentTime.GetMinute() * 60 + currentTime.GetSecond() startOfRangeInSeconds% = startOfRangeInMinutes% * 60 endOfRangeInSeconds% = endOfRangeInMinutes% * 60 notInDownloadWindow = false if endOfRangeInSeconds% <= secondsPerDay% then if not(secondsSinceMidnight% >= startOfRangeInSeconds% and secondsSinceMidnight% <= endOfRangeInSeconds%) then notInDownloadWindow = true end if else if not(((secondsSinceMidnight% >= startOfRangeInSeconds%) and (secondsSinceMidnight% < secondsPerDay%)) or (secondsSinceMidnight% < (endOfRangeInSeconds% - secondsPerDay%))) then notInDownloadWindow = true end if end if return notInDownloadWindow 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$ end if return url$ end function Function CleanServerHeaders(serverMetadata as object) as object serverHeaders = GetServerMetadata(GetGlobalAA().registrySection, serverMetadata) if lcase(getGlobalAA().settings.setupType) = "sfn" then serverHeaders.delete("user") serverHeaders.delete("password") endif return serverHeaders end function Sub AddUploadHeaders(url as object, contentDisposition$) serverHeaders = CleanServerHeaders(m.currentSync.GetMetadata("server")) url.SetHeaders(serverHeaders) ' 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 BuildFileDownloadList(syncSpec as object) listOfDownloadFiles = syncSpec.GetFileList("download") fileInPoolStatus = m.bsp.assetPool.QueryFiles(syncSpec) m.filesToDownload = { } m.chargeableFiles = { } for each downloadFile in listOfDownloadFiles if not m.filesToDownload.DoesExist(downloadFile.hash) then fileToDownload = { } fileToDownload.name = downloadFile.name fileToDownload.size = downloadFile.size fileToDownload.hash = downloadFile.hash 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" end if end if m.filesToDownload.AddReplace(downloadFile.hash, fileToDownload) end if if IsString(downloadFile.chargeable) then if lcase(downloadFile.chargeable) = "yes" then m.chargeableFiles[downloadFile.name] = true end if end if next end sub Sub PushDeviceDownloadProgressItem(fileItem as object, type$ as string, currentFilePercentage$ as string, status$ as string) if type(fileItem) <> "roAssociativeArray" then return end if deviceDownloadProgressItem = { } deviceDownloadProgressItem.type$ = type$ deviceDownloadProgressItem.name$ = fileItem.name deviceDownloadProgressItem.hash$ = fileItem.hash deviceDownloadProgressItem.size$ = fileItem.size deviceDownloadProgressItem.currentFilePercentage$ = currentFilePercentage$ deviceDownloadProgressItem.status$ = status$ deviceDownloadProgressItem.utcTime$ = m.systemTime.GetUtcDateTime().GetString() if m.DeviceDownloadProgressItems.DoesExist(fileItem.name) existingDeviceDownloadProgressItem = m.DeviceDownloadProgressItems.Lookup(fileItem.name) deviceDownloadProgressItem.type$ = existingDeviceDownloadProgressItem.type$ end if m.DeviceDownloadProgressItems.AddReplace(fileItem.name, deviceDownloadProgressItem) end sub Sub AddDeviceDownloadProgressItem(fileItem as object, currentFilePercentage$ as string, status$ as string) if type(fileItem) <> "roAssociativeArray" then return end if m.PushDeviceDownloadProgressItem(fileItem, "deviceDownloadProgressItem", currentFilePercentage$, status$) m.UploadDeviceDownloadProgressItems() end sub Sub UploadDeviceDownloadProgressItems() if m.deviceDownloadProgressURL = "" then m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems - deviceDownloadProgressURL not set, return") return else m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems") end if ' verify that there is content to upload if m.DeviceDownloadProgressItems.IsEmpty() and m.DeviceDownloadProgressItemsPendingUpload.IsEmpty() then return ' create roUrlTransfer if needed if type(m.deviceDownloadProgressUploadURL) <> "roUrlTransfer" then m.deviceDownloadProgressUploadURL = CreateObject("roUrlTransfer") m.deviceDownloadProgressUploadURL.SetUrl(m.deviceDownloadProgressURL) m.deviceDownloadProgressUploadURL.SetPort(m.msgPort) m.deviceDownloadProgressUploadURL.SetTimeout(900000) m.deviceDownloadProgressUploadURL.SetUserAgent(m.bsp.userAgent$) end if ' if a transfer is in progress, return if not m.deviceDownloadProgressUploadURL.SetUrl(m.deviceDownloadProgressURL) then m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems - upload already in progress") return else m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems - proceed with post") end if ' merge new items into pending items for each deviceDownloadProgressItemKey in m.DeviceDownloadProgressItems deviceDownloadProgressItem = m.DeviceDownloadProgressItems.Lookup(deviceDownloadProgressItemKey) if m.DeviceDownloadProgressItemsPendingUpload.DoesExist(deviceDownloadProgressItem.name$) existingDeviceDownloadProgressItem = m.DeviceDownloadProgressItemsPendingUpload.Lookup(deviceDownloadProgressItem.name$) deviceDownloadProgressItem.type$ = existingDeviceDownloadProgressItem.type$ end if m.DeviceDownloadProgressItemsPendingUpload.AddReplace(deviceDownloadProgressItem.name$, deviceDownloadProgressItem) next ' generate the XML and upload the data root = CreateObject("roXMLElement") root.SetName("DeviceDownloadProgressItems") for each deviceDownloadProgressItemKey in m.DeviceDownloadProgressItemsPendingUpload deviceDownloadProgressItem = m.DeviceDownloadProgressItemsPendingUpload.Lookup(deviceDownloadProgressItemKey) BuildDeviceDownloadProgressItemXML(root, deviceDownloadProgressItem) next xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) ' prepare the upload contentDisposition$ = GetContentDisposition("UploadDeviceDownloadProgressItems.xml") m.AddUploadHeaders(m.deviceDownloadProgressUploadURL, contentDisposition$) m.deviceDownloadProgressUploadURL.AddHeader("updateDeviceLastDownload", "true") aa = GetBinding("contentDownloadEnabled", 0) binding = aa.network_interface m.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for UploadDeviceDownloadProgressItems is ", binding)) ok = m.deviceDownloadProgressUploadURL.BindToInterface(binding) if not ok then stop ok = m.deviceDownloadProgressUploadURL.AsyncPostFromString(xml) if not ok then m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressItems - AsyncPostFromString failed") end if m.DeviceDownloadProgressItems.Clear() end sub Sub BuildDeviceDownloadProgressItemXML(root as object, deviceDownloadProgressItem as object) item = root.AddBodyElement() item.SetName(deviceDownloadProgressItem.type$) 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$) end sub Sub UploadDeviceDownloadProgressFileList() if m.deviceDownloadProgressURL = "" then m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressFileList - deviceDownloadProgressURL not set, return") return else m.diagnostics.PrintDebug("### UploadDeviceDownloadProgressFileList") end if ' create roUrlTransfer if needed if type(m.deviceDownloadProgressUploadURL) <> "roUrlTransfer" then m.deviceDownloadProgressUploadURL = CreateObject("roUrlTransfer") m.deviceDownloadProgressUploadURL.SetUrl(m.deviceDownloadProgressURL) m.deviceDownloadProgressUploadURL.SetPort(m.msgPort) m.deviceDownloadProgressUploadURL.SetTimeout(900000) m.deviceDownloadProgressUploadURL.SetUserAgent(m.bsp.userAgent$) else ' cancel any uploads of this type that are in progress m.deviceDownloadProgressUploadURL.AsyncCancel() end if ' this data will overwrite any pending data so clear the existing data structures m.DeviceDownloadProgressItems.Clear() m.DeviceDownloadProgressItemsPendingUpload.Clear() ' create progress items for each file in the sync spec for each fileToDownloadKey in m.filesToDownload fileToDownload = m.filesToDownload.Lookup(fileToDownloadKey) m.PushDeviceDownloadProgressItem(fileToDownload, "fileInSyncSpec", fileToDownload.currentFilePercentage$, fileToDownload.status$) next ' create progress items for each file in each feed for each liveDataFeedId in m.bsp.liveDataFeeds liveDataFeed = m.bsp.liveDataFeeds.Lookup(liveDataFeedId) for each fileToDownloadKey in liveDataFeed.feedContentFilesToDownload fileToDownload = liveDataFeed.feedContentFilesToDownload.Lookup(fileToDownloadKey) if type(fileToDownload) = "roAssociativeArray" then m.PushDeviceDownloadProgressItem(fileToDownload, "fileInSyncSpec", fileToDownload.currentFilePercentage$, fileToDownload.status$) end if next next 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() > 100 m.DeviceDownloadItems.Shift() end while deviceDownloadItem = { } deviceDownloadItem.downloadEvent$ = downloadEvent$ deviceDownloadItem.fileName$ = fileName$ deviceDownloadItem.downloadData$ = downloadData$ m.DeviceDownloadItems.push(deviceDownloadItem) m.UploadDeviceDownload() end sub Sub UploadDeviceDownload() if m.deviceDownloadURL = "" then m.diagnostics.PrintDebug("### UploadDeviceDownload - deviceDownloadURL not set, return") return else m.diagnostics.PrintDebug("### UploadDeviceDownload") end if ' verify that there is content to upload if m.DeviceDownloadItems.Count() = 0 and m.DeviceDownloadItemsPendingUpload.Count() = 0 then return ' create roUrlTransfer if needed if type(m.deviceDownloadUploadURL) <> "roUrlTransfer" then m.deviceDownloadUploadURL = CreateObject("roUrlTransfer") m.deviceDownloadUploadURL.SetUrl(m.deviceDownloadURL) m.deviceDownloadUploadURL.SetPort(m.msgPort) m.deviceDownloadUploadURL.SetTimeout(900000) m.deviceDownloadUploadURL.SetUserAgent(m.bsp.userAgent$) end if ' 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.DeviceDownloadItemsPendingUpload.Count() > 100 then m.diagnostics.PrintDebug("### UploadDeviceDownload - clear pending items from queue") m.DeviceDownloadItemsPendingUpload.Clear() end if if m.DeviceDownloadItems.Count() > 100 then m.diagnostics.PrintDebug("### UploadDeviceDownload - clear items from queue") m.DeviceDownloadItems.Clear() end if return else m.diagnostics.PrintDebug("### UploadDeviceDownload - proceed with post") end if ' generate the XML and upload the data root = CreateObject("roXMLElement") root.SetName("DeviceDownloadBatch") ' first add the items that failed the last time for each deviceDownloadItem in m.DeviceDownloadItemsPendingUpload BuildDeviceDownloadItemXML(root, deviceDownloadItem) next ' now add the new items for each deviceDownloadItem in m.DeviceDownloadItems BuildDeviceDownloadItemXML(root, deviceDownloadItem) next xml = root.GenXML({ indent: " ", newline: chr(10), header: true }) ' prepare the upload contentDisposition$ = GetContentDisposition("UploadDeviceDownload.xml") m.AddUploadHeaders(m.deviceDownloadUploadURL, contentDisposition$) aa = GetBinding("contentDownloadEnabled", 0) binding = aa.network_interface m.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for UploadDeviceDownload is ", binding)) ok = m.deviceDownloadUploadURL.BindToInterface(binding) if not ok then stop ok = m.deviceDownloadUploadURL.AsyncPostFromString(xml) if not ok then m.diagnostics.PrintDebug("### UploadDeviceDownload - AsyncPostFromString failed") end if for each deviceDownloadItem in m.DeviceDownloadItems m.DeviceDownloadItemsPendingUpload.push(deviceDownloadItem) next m.DeviceDownloadItems.Clear() end sub Sub BuildDeviceDownloadItemXML(root as object, deviceDownloadItem as object) 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$) end sub Sub UploadLogFiles() if m.uploadLogFileURL$ = "" then return ' create roUrlTransfer if needed if type(m.uploadLogFileURLXfer) <> "roUrlTransfer" then m.uploadLogFileURLXfer = CreateObject("roUrlTransfer") m.uploadLogFileURLXfer.SetUrl(m.uploadLogFileURL$) m.uploadLogFileURLXfer.SetPort(m.msgPort) m.uploadLogFileURLXfer.SetMinimumTransferRate(1, 300) m.uploadLogFileURLXfer.SetUserAgent(m.bsp.userAgent$) end if ' 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 aa = GetBinding("logsUploadEnabled", m.logFileUploadsBindingPriorityIndex) binding = aa.network_interface m.logFileUploadsBindingPriorityIndex = aa.priorityIndex m.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for UploadLogFiles is ", binding)) ok = m.uploadLogFileURLXfer.BindToInterface(binding) ' upload the first file for each file in listOfLogFiles fullFilePath = m.uploadLogFolder + "/" + file if not m.logging.inErrorState then m.diagnostics.PrintDebug("### UploadLogFiles " + file + " to " + m.uploadLogFileURL$) 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 end if else ' if any of the files get back 401 or 403, put the rest of files in FailedUpload folder target$ = m.uploadLogFailedFolder + "/" + file ok = MoveFile(fullFilePath, target$) end if next end sub Sub UploadLogFileHandler(msg as object) responseCode = msg.GetResponseCode() if responseCode = 200 then if IsString(m.logFileUpload) 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$) end if m.logFileUpload = invalid end if m.logFileUploadsBindingPriorityIndex = 0 m.logFileUploadsNumRetries% = 0 else if responseCode = 401 or responseCode = 403 then m.logging.inErrorState = true end if if IsString(m.logFileUpload) then m.diagnostics.PrintDebug("### Failed to upload log file " + m.logFileUpload + ", error code = " + str(responseCode)) if responseCode = 400 then DeleteFile(m.logFileUpload) else if responseCode = 413 then target$ = m.uploadLogArchiveFolder + "/" + m.logFile$.Left(m.logFile$.Len()-4)+"-413.log" ok = MoveFile(m.logFileUpload, target$) else ' move file so that the script doesn't try to upload it again immediately target$ = m.uploadLogFailedFolder + "/" + m.logFile$ ok = MoveFile(m.logFileUpload, target$) end if end if m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_LOGFILE_UPLOAD_FAILURE, str(responseCode)) if m.logFileUploadsNumRetries% >= m.logFileUploadsMaxRetries% then globalAA = GetGlobalAA() if not globalAA.networkInterfacePriorityLists.DoesExist("logsUploadEnabled") then ' TEDTODO stop endif networkInterfacePriorityList = globalAA.networkInterfacePriorityLists.Lookup("logsUploadEnabled") m.logFileUploadsBindingPriorityIndex = m.logFileUploadsBindingPriorityIndex + 1 if m.logFileUploadsBindingPriorityIndex >= networkInterfacePriorityList.count() then ' all network interfaces failed m.diagnostics.PrintDebug("### mrss data feed content download failed on all network interfaces") m.logFileUploadsBindingPriorityIndex = 0 else ' try next network interface m.diagnostics.PrintDebug("### mrss data feed content download failed. Try next network interface") endif else m.logFileUploadsNumRetries% = m.logFileUploadsNumRetries% + 1 m.diagnostics.PrintDebug("### retry mrss data feed content download") endif end if m.UploadLogFiles() 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") end if ' create roUrlTransfer if needed if type(m.trafficDownloadUploadURL) <> "roUrlTransfer" then m.trafficDownloadUploadURL = CreateObject("roUrlTransfer") m.trafficDownloadUploadURL.SetUrl(m.trafficDownloadURL) m.trafficDownloadUploadURL.SetPort(m.msgPort) m.trafficDownloadUploadURL.SetTimeout(900000) m.trafficDownloadUploadURL.SetUserAgent(m.bsp.userAgent$) end if ' 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# aa = GetBinding("contentDownloadEnabled", 0) binding = aa.network_interface m.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for UploadTrafficDownload is ", binding)) ok = m.trafficDownloadUploadURL.BindToInterface(binding) if not ok then stop return m.SendTrafficUpload(m.trafficDownloadUploadURL, contentDownloaded#, false) end function Function UploadMRSSTrafficDownload(contentDownloaded# as double) as boolean if m.trafficDownloadURL = "" then m.diagnostics.PrintDebug("### UploadMRSSTrafficDownload - trafficDownloadURL not set, return") return false else m.diagnostics.PrintDebug("### UploadMRSSTrafficDownload") end if ' create roUrlTransfer if needed if type(m.mrssTrafficDownloadUploadURL) <> "roUrlTransfer" then m.mrssTrafficDownloadUploadURL = CreateObject("roUrlTransfer") m.mrssTrafficDownloadUploadURL.SetUrl(m.trafficDownloadURL) m.mrssTrafficDownloadUploadURL.SetPort(m.msgPort) m.mrssTrafficDownloadUploadURL.SetTimeout(900000) m.mrssTrafficDownloadUploadURL.SetUserAgent(m.bsp.userAgent$) end if ' if a transfer is in progress, return if not m.mrssTrafficDownloadUploadURL.SetUrl(m.trafficDownloadURL) then m.diagnostics.PrintDebug("### UploadMRSSTrafficDownload - upload already in progress") totalContentDownloaded# = m.pendingMRSSContentDownloaded# totalContentDownloaded# = totalContentDownloaded# + contentDownloaded# m.pendingMRSSContentDownloaded# = totalContentDownloaded# return false end if m.pendingMRSSContentDownloaded# = 0 m.lastMRSSContentDownloaded# = contentDownloaded# aa = GetBinding("mediaFeedsDownloadEnabled", 0) binding = aa.network_interface m.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for UploadMRSSTrafficDownload is ", binding)) ok = m.mrssTrafficDownloadUploadURL.BindToInterface(binding) if not ok then stop m.diagnostics.PrintDebug("### UploadMRSSTrafficDownload: Content downloaded = " + str(contentDownloaded#)) return m.SendTrafficUpload(m.mrssTrafficDownloadUploadURL, contentDownloaded#, true) end function Sub SendTrafficUpload(url as object, contentDownloaded# as double, intermediateTrafficReport as boolean) as boolean ' convert contentDownloaded# to contentDownloaded in KBytes which can be stored in an integer contentDownloaded% = contentDownloaded# / 1024 url.SetHeaders(GetServerMetadata(GetGlobalAA().registrySection, m.currentSync.GetMetadata("server"))) url.AddHeader("DeviceID", m.deviceUniqueID$) url.AddHeader("contentDownloadedInKBytes", StripLeadingSpaces(stri(contentDownloaded%))) url.AddHeader("DeviceFWVersion", m.firmwareVersion$) url.AddHeader("DeviceSWVersion", m.autorunVersion$) url.AddHeader("CustomAutorunVersion", m.customAutorunVersion$) url.AddHeader("timezone", m.systemTime.GetTimeZone()) url.AddHeader("utcTime", m.systemTime.GetUtcDateTime().GetString()) if intermediateTrafficReport then url.AddHeader("intermediateTrafficReport", "yes") end if ok = url.AsyncPostFromString("UploadTrafficDownload") if not ok then m.diagnostics.PrintDebug("### SendTrafficUpload - AsyncPostFromString failed") return false end if return ok 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 = { } 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 = { } deviceErrorItem.event$ = event$ deviceErrorItem.name$ = name$ deviceErrorItem.failureReason$ = failureReason$ deviceErrorItem.responseCode$ = responseCode$ m.DeviceErrorItems.push(deviceErrorItem) m.UploadDeviceError() end sub Sub UploadEvent() m.diagnostics.PrintDebug("### UploadEvent") ' verify that there is content to upload if m.EventItems.Count() = 0 then return ' create roUrlTransfer if needed if type(m.eventUploadURL) <> "roUrlTransfer" then m.eventUploadURL = CreateObject("roUrlTransfer") m.eventUploadURL.SetUrl(m.eventURL) m.eventUploadURL.SetPort(m.msgPort) m.eventUploadURL.SetTimeout(900000) m.eventUploadURL.SetUserAgent(m.bsp.userAgent$) end if ' 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() end if 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$) aa = GetBinding("contentDownloadEnabled", 0) binding = aa.network_interface m.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for UploadEvent is ", binding)) ok = m.eventUploadURL.BindToInterface(binding) if not ok then stop 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() end if end sub Sub UploadDeviceError() m.diagnostics.PrintDebug("### UploadDeviceError") ' verify that there is content to upload if m.DeviceErrorItems.Count() = 0 then return ' create roUrlTransfer if needed if type(m.deviceErrorUploadURL) <> "roUrlTransfer" then m.deviceErrorUploadURL = CreateObject("roUrlTransfer") m.deviceErrorUploadURL.SetUrl(m.deviceErrorURL) m.deviceErrorUploadURL.SetPort(m.msgPort) m.deviceErrorUploadURL.SetTimeout(900000) m.deviceErrorUploadURL.SetUserAgent(m.bsp.userAgent$) end if ' 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() end if 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$) aa = GetBinding("contentDownloadEnabled", 0) binding = aa.network_interface m.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for UploadDeviceError is ", binding)) ok = m.deviceErrorUploadURL.BindToInterface(binding) if not ok then stop 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() end if end sub Function STNetworkSchedulerEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") currentTime = m.stateMachine.systemTime.GetLocalDateTime() ' set timer for when content download window starts / ends if GetActiveSettings().contentDownloadsRestricted then startOfRange% = GetActiveSettings().contentDownloadRangeStart endOfRange% = startOfRange% + GetActiveSettings().contentDownloadRangeLength notInDownloadWindow = OutOfDownloadWindow(currentTime, startOfRange%, endOfRange%) if notInDownloadWindow then m.stateMachine.RestartContentDownloadWindowStartTimer(currentTime, startOfRange%) else m.stateMachine.RestartContentDownloadWindowEndTimer(currentTime, endOfRange%) end if end if return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") else if event["EventType"] = "DISK_ERROR" then errorEvent = event["DiskError"] m.stateMachine.AddDeviceErrorItem("diskError", errorEvent["source"] + " " + errorEvent["device"], errorEvent["error"], errorEvent["param"]) return "HANDLED" else if event["EventType"] = "SNAPSHOT_CAPTURED" then snapshotName$ = event["SnapshotName"] if type(m.stateMachine.awsAccessKeyId$) = "roString" and m.stateMachine.awsAccessKeyId$ <> "" then m.UploadSnapshotToBSN(snapshotName$) else if type(m.stateMachine.securityToken) = "roString" and m.stateMachine.securityToken <> "" then m.UploadSnapshotToBSNEE(snapshotName$) else if m.stateMachine.uploadSnapshotsURL$ <> "" then m.UploadSnapshotToSFN(snapshotName$) end if end if end if else if type(event) = "roTimerEvent" then if type(m.stateMachine.contentDownloadWindowStartTimer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(m.stateMachine.contentDownloadWindowStartTimer.GetIdentity()) then downloadRateLimits = GetDownloadRateLimits(GetActiveSettings(), true) SetDownloadRateLimits(m.bsp.diagnostics, downloadRateLimits) ' start window end timer if GetActiveSettings().contentDownloadsRestricted then currentTime = m.stateMachine.systemTime.GetLocalDateTime() startOfRange% = GetActiveSettings().contentDownloadRangeStart endOfRange% = startOfRange% + GetActiveSettings().contentDownloadRangeLength m.stateMachine.RestartContentDownloadWindowEndTimer(currentTime, endOfRange%) end if return "HANDLED" end if end if if type(m.stateMachine.contentDownloadWindowEndTimer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(m.stateMachine.contentDownloadWindowEndTimer.GetIdentity()) then ' send internal message to indicate that any in-progress sync pool downloads should stop cancelDownloadsEvent = { } cancelDownloadsEvent["EventType"] = "CANCEL_DOWNLOADS" m.stateMachine.msgPort.PostMessage(cancelDownloadsEvent) ' change rate limit values - outside window downloadRateLimits = GetDownloadRateLimits(GetActiveSettings(), false) SetDownloadRateLimits(m.bsp.diagnostics, downloadRateLimits) ' start window start timer currentTime = m.stateMachine.systemTime.GetLocalDateTime() startOfRange% = GetActiveSettings().contentDownloadRangeStart endOfRange% = startOfRange% + GetActiveSettings().contentDownloadRangeLength m.stateMachine.RestartContentDownloadWindowStartTimer(currentTime, startOfRange%) return "HANDLED" end if end if if type(m.stateMachine.retrySnapshotUploadTimer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(m.stateMachine.retrySnapshotUploadTimer.GetIdentity()) then if type(m.stateMachine.uploadSnapshotUrl) = "roUrlTransfer" then ' a snapshot upload is currently in progress - return and wait for completion event handler else ' make sure there's an outstanding snapshot to upload if not m.stateMachine.pendingSnapshotsToUpload.IsEmpty() then m.stateMachine.pendingSnapshotsToUpload.Reset() snapshotName = m.stateMachine.pendingSnapshotsToUpload.Next() m.stateMachine.pendingSnapshotsToUpload.Delete(snapshotName) m.UploadSnapshotToBSN(snapshotName) end if end if m.stateMachine.retrySnapshotUploadTimer = invalid return "HANDLED" end if end if else if type(event) = "roUrlEvent" then if type(m.stateMachine.uploadSnapshotToBSNEEUrl) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.uploadSnapshotToBSNEEUrl.GetIdentity() then if event.GetResponseCode() = 200 then m.bsp.diagnostics.PrintDebug("### Snapshot file uploaded to BSNEE") else ' log failure m.bsp.diagnostics.PrintDebug("### snapshot upload failure " + stri(event.GetResponseCode()) + " " + event.GetFailureReason()) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SCREENSHOT_UPLOAD_ERROR, stri(event.GetResponseCode()) + " " + event.GetFailureReason()) end if m.stateMachine.uploadSnapshotToBSNEEUrl = invalid return "HANDLED" end if end if if type(m.stateMachine.uploadSnapshotToSFNUrl) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.uploadSnapshotToSFNUrl.GetIdentity() then if event.GetResponseCode() = 200 then m.bsp.diagnostics.PrintDebug("### snapshot sucessfully uploaded to simple file networking handler: " + m.stateMachine.uploadSnapshotsURL$) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SCREENSHOT_UPLOADED, " ") else m.bsp.diagnostics.PrintDebug("### snapshot upload failure " + stri(event.GetResponseCode()) + " " + event.GetFailureReason()) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SCREENSHOT_UPLOAD_ERROR, stri(event.GetResponseCode()) + " " + event.GetFailureReason()) end if end if end if if type(m.stateMachine.uploadSnapshotToSFNUrl) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.uploadSnapshotToSFNUrl.GetIdentity() then if event.GetResponseCode() = 200 then m.bsp.diagnostics.PrintDebug("### snapshot sucessfully uploaded to simple file networking handler: " + m.stateMachine.uploadSnapshotsURL$) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SCREENSHOT_UPLOADED, " ") else m.bsp.diagnostics.PrintDebug("### snapshot upload failure " + stri(event.GetResponseCode()) + " " + event.GetFailureReason()) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SCREENSHOT_UPLOAD_ERROR, stri(event.GetResponseCode()) + " " + event.GetFailureReason()) end if end if end if if type(m.stateMachine.uploadSnapshotUrl) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.uploadSnapshotUrl.GetIdentity() then snapshot = m.stateMachine.uploadSnapshotUrl.GetUserData() snapshotName$ = snapshot.name url = snapshot.url if event.GetResponseCode() = 200 then ' note - don't check for prior upload failures here - go ahead and queue this upload so that users can see ' this latest snapshots as soon as possible. m.QueueSnapshotForBSN(snapshotName$, url) else ' log failure m.bsp.diagnostics.PrintDebug("### snapshot upload failure " + snapshotName$ + stri(event.GetResponseCode()) + " " + event.GetFailureReason()) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SCREENSHOT_UPLOAD_ERROR, snapshotName$ + stri(event.GetResponseCode()) + " " + event.GetFailureReason()) ' retry m.stateMachine.pendingSnapshotsToUpload.AddReplace(snapshotName$, snapshotName$) if type(m.stateMachine.retrySnapshotUploadTimer) <> "roTimer" then m.stateMachine.retrySnapshotUploadTimer = CreateObject("roTimer") m.stateMachine.retrySnapshotUploadTimer.SetPort(m.stateMachine.msgPort) m.stateMachine.retrySnapshotUploadTimer.SetElapsed(30, 0) m.stateMachine.retrySnapshotUploadTimer.Start() end if end if ' indicate that transfer is no longer in progress m.stateMachine.uploadSnapshotUrl = invalid return "HANDLED" end if end if if type(m.stateMachine.queueSnapshotUrl) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.queueSnapshotUrl.GetIdentity() then snapshotName$ = m.stateMachine.queueSnapshotUrl.GetUserData() if event.GetResponseCode() = 200 then m.bsp.diagnostics.PrintDebug("### snapshot uploaded and queued" + snapshotName$) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SCREENSHOT_UPLOADED_AND_QUEUED, snapshotName$) ' if prior uploads failed and another upload is not in progress, retry upload if not m.stateMachine.pendingSnapshotsToUpload.IsEmpty() and type(m.stateMachine.uploadSnapshotUrl) <> "roUrlTransfer" then m.stateMachine.pendingSnapshotsToUpload.Reset() snapshotName = m.stateMachine.pendingSnapshotsToUpload.Next() m.stateMachine.pendingSnapshotsToUpload.Delete(snapshotName) m.UploadSnapshotToBSN(snapshotName) end if else ' queue operation failed - retry entire upload / queue sequence ' log failure m.bsp.diagnostics.PrintDebug("### snapshot queue failure " + snapshotName$ + stri(event.GetResponseCode()) + " " + event.GetFailureReason()) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SCREENSHOT_QUEUE_ERROR, snapshotName$ + stri(event.GetResponseCode()) + " " + event.GetFailureReason()) m.stateMachine.pendingSnapshotsToUpload.AddReplace(snapshotName$, snapshotName$) if type(m.stateMachine.retrySnapshotUploadTimer) <> "roTimer" then m.stateMachine.retrySnapshotUploadTimer = CreateObject("roTimer") m.stateMachine.retrySnapshotUploadTimer.SetPort(m.stateMachine.msgPort) m.stateMachine.retrySnapshotUploadTimer.SetElapsed(30, 0) m.stateMachine.retrySnapshotUploadTimer.Start() end if end if return "HANDLED" end if end if if type (m.stateMachine.deviceDownloadUploadURL) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.deviceDownloadUploadURL.GetIdentity() then if event.GetResponseCode() = 200 then m.stateMachine.DeviceDownloadItemsPendingUpload.Clear() else m.bsp.diagnostics.PrintDebug("### DeviceDownloadURLEvent: " + stri(event.GetResponseCode())) end if m.stateMachine.deviceDownloadUploadURL = invalid m.stateMachine.UploadDeviceDownload() return "HANDLED" end if end if if type (m.stateMachine.deviceDownloadProgressUploadURL) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.deviceDownloadProgressUploadURL.GetIdentity() then if event.GetResponseCode() = 200 then m.stateMachine.DeviceDownloadProgressItemsPendingUpload.Clear() else m.bsp.diagnostics.PrintDebug("### DeviceDownloadProgressURLEvent: " + stri(event.GetResponseCode())) end if m.stateMachine.deviceDownloadProgressUploadURL = invalid m.stateMachine.UploadDeviceDownloadProgressItems() return "HANDLED" end if end if if type (m.stateMachine.uploadLogFileURLXfer) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.uploadLogFileURLXfer.GetIdentity() then m.stateMachine.uploadLogFileURLXfer = invalid m.stateMachine.UploadLogFileHandler(event) return "HANDLED" end if end if if type (m.stateMachine.eventUploadURL) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.eventUploadURL.GetIdentity() then m.stateMachine.eventUploadURL = invalid m.stateMachine.UploadEvent() return "HANDLED" end if end if if type (m.stateMachine.deviceErrorUploadURL) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.deviceErrorUploadURL.GetIdentity() then m.stateMachine.deviceErrorUploadURL = invalid m.stateMachine.UploadDeviceError() return "HANDLED" end if end if if type (m.stateMachine.trafficDownloadUploadURL) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.trafficDownloadUploadURL.GetIdentity() then if event.GetInt() = m.URL_EVENT_COMPLETE then m.bsp.diagnostics.PrintDebug("### URLTrafficDownloadXferEvent: " + stri(event.GetResponseCode())) m.stateMachine.trafficDownloadUploadURL = invalid if event.GetResponseCode() <> 200 then m.stateMachine.UploadTrafficDownload(m.lastContentDownloaded#) end if end if return "HANDLED" end if end if if type (m.stateMachine.mrssTrafficDownloadUploadURL) = "roUrlTransfer" then if event.GetSourceIdentity() = m.stateMachine.mrssTrafficDownloadUploadURL.GetIdentity() then if event.GetInt() = m.URL_EVENT_COMPLETE then m.bsp.diagnostics.PrintDebug("### URLMRSSTrafficDownloadXferEvent: " + stri(event.GetResponseCode())) m.stateMachine.mrssTrafficDownloadUploadURL = invalid if event.GetResponseCode() <> 200 then m.stateMachine.UploadMRSSTrafficDownload(m.lastMRSSContentDownloaded#) end if end if return "HANDLED" end if end if end if stateData.nextState = m.superState return "SUPER" end function Sub RestartWindowStartTimer(timer as object, currentTime as object, startOfRange% as integer) hour% = startOfRange% / 60 minute% = startOfRange% - (hour% * 60) timeoutTime = CopyDateTime(currentTime) timeoutTime.SetHour(hour%) timeoutTime.SetMinute(minute%) timeoutTime.SetSecond(0) timeoutTime.SetMillisecond(0) GetNextTimeout(m.systemTime, timeoutTime) timer.SetDateTime(timeoutTime) timer.SetPort(m.msgPort) timer.Start() m.bsp.diagnostics.PrintDebug("RestartWindowStartTimer: set timer to start of window - " + timeoutTime.GetString()) end sub Sub RestartContentDownloadWindowStartTimer(currentTime as object, startOfRange% as integer) m.contentDownloadWindowStartTimer = CreateObject("roTimer") m.RestartWindowStartTimer(m.contentDownloadWindowStartTimer, currentTime, startOfRange%) end sub Function RestartWindowEndTimer(currentTime as object, endOfRange% as integer) as object currentTime.SetHour(0) currentTime.SetMinute(0) currentTime.SetSecond(0) currentTime.SetMillisecond(0) currentTime.AddSeconds(endOfRange% * 60) currentTime.Normalize() GetNextTimeout(m.systemTime, currentTime) timer = CreateObject("roTimer") timer.SetDateTime(currentTime) timer.SetPort(m.msgPort) timer.Start() m.bsp.diagnostics.PrintDebug("RestartWindowEndTimer: set timer to end of window - " + currentTime.GetString()) return timer end function Sub RestartContentDownloadWindowEndTimer(currentTime as object, endOfRange% as integer) m.contentDownloadWindowEndTimer = m.RestartWindowEndTimer(currentTime, endOfRange%) end sub Sub GetNextTimeout(systemTime as object, timerDateTime as object) as object currentDateTime = systemTime.GetLocalDateTime() if timerDateTime.GetString() <= currentDateTime.GetString() then timerDateTime.AddSeconds(24 * 60 * 60) timerDateTime.Normalize() end if end sub Sub WaitForTransfersToComplete() if type(m.trafficDownloadUploadURL) = "roUrlTransfer" 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 end if if type(m.deviceDownloadProgressUploadURL) = "roUrlTransfer" 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 end if if type(m.deviceDownloadUploadURL) = "roUrlTransfer" 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 end if end sub Sub RebootAfterEventsSent() ' temporary sleep(2000) m.WaitForTransfersToComplete() m.UploadDeviceDownloadProgressItems() m.UploadDeviceDownload() m.WaitForTransfersToComplete() RebootSystem() end sub Sub ResetDownloadTimerToDoRetry() if m.numRetries% >= m.maxRetries% then globalAA = GetGlobalAA() if not globalAA.networkInterfacePriorityLists.DoesExist("contentDownloadEnabled") then ' TEDTODO stop endif networkInterfacePriorityList = globalAA.networkInterfacePriorityLists.Lookup("contentDownloadEnabled") m.networkingBindingPriorityIndex = m.networkingBindingPriorityIndex + 1 if m.networkingBindingPriorityIndex >= networkInterfacePriorityList.count() then ' all network interfaces failed m.currentTimeBetweenNetConnects% = m.timeBetweenNetConnects% m.diagnostics.PrintDebug("### reset_download_timer_to_do_retry - wait " + stri(m.currentTimeBetweenNetConnects%) + " seconds.") m.networkingBindingPriorityIndex = 0 else ' try next network interface m.currentTimeBetweenNetConnects% = m.retryInterval% m.diagnostics.PrintDebug("### reset_download_timer_to_do_retry - max retries attempted on current network interface - try next one.") endif m.numRetries% = 0 else m.numRetries% = m.numRetries% + 1 m.currentTimeBetweenNetConnects% = m.retryInterval% * m.numRetries% m.diagnostics.PrintDebug("### reset_download_timer_to_do_retry - wait " + stri(m.currentTimeBetweenNetConnects%) + " seconds.") end if m.assetFetcher = invalid end sub Function STWaitForTimeoutEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.stateMachine.networkTimerDownload.timer.SetElapsed(m.stateMachine.currentTimeBetweenNetConnects%, 0) m.stateMachine.networkTimerDownload.timer.Start() return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") end if end if else if type(event) = "roTimerEvent" then if type(m.stateMachine.networkTimerDownload.timer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(m.stateMachine.networkTimerDownload.timer.GetIdentity()) then stateData.nextState = m.stateMachine.stRetrievingSyncList return "TRANSITION" end if end if else if type(event) = "roControlCloudMessageEvent" and IsString(event.getUserData()) and event.GetUserData() = "bootstrap" then m.bsp.diagnostics.PrintDebug("supervisor / bootstrap roControlCloudMessageEvent received") ccloudData = event.GetData() if IsString(ccloudData) then payload=ParseJson(ccloudData) action = m.ProcessSupervisorCheckForUpdateScheduleMessage(stateData, payload) if IsString(action) then return action endif endif ' Check user data to distinguish between presentation udp messages and bootstrap udp messages else if type(event) = "roDatagramEvent" and IsString(event.getUserData()) and event.GetUserData() = "bootstrap" then payload = ParseJson(event.GetString()) action = m.ProcessSupervisorCheckForUpdateScheduleMessage(stateData, payload) if IsString(action) then return action endif ' BCN-4613: Player should check for content upon network hotplug else if type(event) = "roNetworkAttached" then stateData.nextState = m.stateMachine.stRetrievingSyncList return "TRANSITION" end if stateData.nextState = m.superState return "SUPER" end function Sub ProcessSupervisorCheckForContentMessage(payload as object) if type(payload) = "roAssociativeArray" and IsString(payload.message) then if payload.message = "checkforcontent" and IsBoolean(payload.updateSettings) and payload.updateSettings then if GetGlobalAA().useSupervisorConfigSpec m.bsp.diagnostics.PrintDebug("supervisor updateSettings roControlCloudMessageEvent received") SetPendingSettings() UpdateSettingsFromConfig() SetActiveSettingsFromPendingSettings() else m.bsp.diagnostics.PrintDebug("ignore supervisor updateSettings roControlCloudMessageEvent received - settings handler disabled") endif else if payload.message = "cellular-status" then if isBoolean(payload.value) then globalAA = GetGlobalAA() globalAA.cellularModemActive = payload.value endif endif endif end sub Function ProcessSupervisorCheckForUpdateScheduleMessage(stateData as object, payload as object) as object if type(payload) = "roAssociativeArray" then if IsString(payload.message) and payload.message = "checkforcontent" then m.bsp.diagnostics.PrintDebug("===Successfully read checkforcontent message.") if (not GetGlobalAA().useSupervisorConfigSpec) or (IsBoolean(payload.updateSchedule) and payload.updateSchedule) then stateData.nextState = m.stateMachine.stRetrievingSyncList return "TRANSITION" endif end if endif return invalid end function Function STRetrievingSyncListEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") m.StartSync("download") return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") end if end if else if type(event) = "roUrlEvent" then m.bsp.diagnostics.PrintDebug("STRetrievingSyncListEventHandler: roUrlEvent") if stri(event.GetSourceIdentity()) = stri(m.xfer.GetIdentity()) then stateData.nextState = m.SyncSpecXferEvent(event) return "TRANSITION" end if end if stateData.nextState = m.superState return "SUPER" end function Sub StartSync(syncType$ as string) ' Call when you want to start a sync operation m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### start_sync " + syncType$) if type(m.stateMachine.assetFetcher) = "roAssetFetcher" 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.bsp.diagnostics.PrintDebug("### sync already active so we'll let it continue") m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNC_ALREADY_ACTIVE, "") return end if if syncType$ = "cache" then if not m.stateMachine.proxy_mode then m.bsp.diagnostics.PrintDebug("### cache download requested but the BrightSign is not configured to use a cache server") return end if end if m.xfer = CreateObject("roUrlTransfer") m.xfer.SetPort(m.stateMachine.msgPort) m.xfer.SetUserAgent(m.bsp.userAgent$) m.stateMachine.syncType$ = syncType$ m.bsp.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. activeSettings = GetActiveSettings() activeSyncSpecSettings = GetActiveSyncSpecSettings() base$ = activeSyncSpecSettings.base nextURL = GetURL(base$, activeSyncSpecSettings.next) m.bsp.diagnostics.PrintDebug("### Looking for new sync list from " + nextURL) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_CHECK_CONTENT, nextURL) m.xfer.SetUrl(nextURL) if m.stateMachine.setUserAndPassword then m.xfer.SetUserAndPassword(activeSyncSpecSettings.user, activeSyncSpecSettings.password) m.xfer.EnableUnsafeAuthentication(m.stateMachine.enableBasicAuthentication) m.xfer.SetMinimumTransferRate(10, 240) headers = CleanServerHeaders(m.stateMachine.currentSync.GetMetadata("server")) headers.group = GetGlobalAA().settings.group m.xfer.SetHeaders(headers) ' Add presentation name to header if type(m.bsp.sign) = "roAssociativeArray" then m.xfer.AddHeader("presentationName", m.bsp.sign.name$) else m.xfer.AddHeader("presentationName", "none") end if ' Add device unique identifier, timezone m.xfer.AddHeader("DeviceID", m.stateMachine.deviceUniqueID$) m.xfer.AddHeader("DeviceModel", m.stateMachine.deviceModel$) m.xfer.AddHeader("DeviceFamily", m.stateMachine.deviceFamily$) m.xfer.AddHeader("DeviceFWVersion", m.stateMachine.firmwareVersion$) m.xfer.AddHeader("DeviceSWVersion", m.stateMachine.autorunVersion$) m.xfer.AddHeader("CustomAutorunVersion", m.stateMachine.customAutorunVersion$) m.xfer.AddHeader("timezone", m.stateMachine.systemTime.GetTimeZone()) m.xfer.AddHeader("localTime", m.stateMachine.systemTime.GetLocalDateTime().GetString()) m.stateMachine.AddMiscellaneousHeaders(m.xfer, m.bsp.assetPool) ' Add headers for BrightWall m.xfer.AddHeader("BrightWallName", GetGlobalAA().registrySettings.brightWallName$) m.xfer.AddHeader("BrightWallScreenNumber", GetGlobalAA().registrySettings.brightWallScreenNumber$) aa = GetBinding("contentDownloadEnabled", m.stateMachine.networkingBindingPriorityIndex) binding = aa.network_interface m.stateMachine.networkingBindingPriorityIndex = aa.priorityIndex m.bsp.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for StartSync is ", binding)) ok = m.xfer.BindToInterface(binding) if not ok then stop if not m.xfer.AsyncGetToObject("roSyncSpec") then stop end sub ' TEDTODO - accessToken - some places read from registry; other places from sync spec. is this correct? didn't exist in old autorun Function GetServerMetadata(registry as object, serverMetadata as object) as object accessToken$ = registry.Read("access_token") ' add the accessToken from registry in the request header serverMetadata.AddReplace("accessToken", accessToken$) ' if refresh Token or bsnrt exist in checkforcontent header, ' BSN will exchange for a new accessToken. So delete them in the request headers. if (serverMetadata.DoesExist("refreshToken")) then serverMetadata.Delete("refreshToken") end if if (serverMetadata.DoesExist("registrationToken")) then serverMetadata.Delete("registrationToken") end if return serverMetadata end function Sub AddMiscellaneousHeaders(urlXfer as object, assetPool 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 tempRealizer = CreateObject("roAssetRealizer", assetPool, "/") tempSpec = CreateObject("roSyncSpec") if tempSpec.ReadFromFile("current-sync.json") or tempSpec.ReadFromFile("localToBSN-sync.json") then urlXfer.AddHeader("storage-current-used", str(tempRealizer.EstimateRealizedSizeInMegabytes(tempSpec))) end if tempRealizer = invalid tempSpec = invalid end sub Function SyncSpecXferEvent(event as object) as object nextState = invalid xferInUse = false rc = event.GetResponseCode() if rc = 200 then m.stateMachine.newSync = event.GetObject() m.bsp.diagnostics.PrintDebug("### Spec received from server") ' save last successful connection to BSN in local time currentTime = m.bsp.systemTime.GetLocalDateTime() WriteRegistrySetting("lastBSNConnectionTime", currentTime) GetGlobalAA().registrySettings.lastBSNConnectionTime = currentTime headers = event.getResponseHeaders() if headers.DoesExist("bsn-content-passphrase") then m.bsp.obfuscatedEncryptionKey = headers["bsn-content-passphrase"] else m.bsp.obfuscatedEncryptionKey = "" end if if headers.DoesExist("last-modified") then lastModifiedHeader$ = headers["last-modified"] lastModifiedDt = ParseHTTPDateTime(lastModifiedHeader$) else lastModifiedDt = m.bsp.systemTime.GetUtcDateTime() endif lastModifiedTime$ = FormatDateTime(lastModifiedDt) + "Z" ' Determine if device is in BrightWall group, then clear brightWall settings in registry if they are currently set but device is no longer part of BrightWall group if GetGlobalAA().registrySettings.brightWallName$ <> "" and GetGlobalAA().registrySettings.brightWallScreenNumber$ <> "" then clearBrightWallSettings = false serverMetadata = m.stateMachine.newSync.GetMetadata("server") if type(serverMetadata) = "roAssociativeArray" then if not serverMetadata.DoesExist("brightWall") or serverMetadata["brightWall"] = "" then clearBrightWallSettings = true end if end if if clearBrightWallSettings then registrySection = GetGlobalAA().registrySection registrySettings = GetGlobalAA().registrySettings registrySection.Write("brightWallName", "") registrySection.Write("brightWallScreenNumber", "") registrySection.Flush() registrySettings.brightWallName$ = "" registrySettings.brightWallScreenNumber$ = "" end if end if ' proceed with download, etc if content changed or metadata other than aws properties changed syncSpecChange = m.GetSyncSpecChangeType() if syncSpecChange = "noChange" or syncSpecChange = "unknownChange" then return m.stateMachine.stWaitForTimeout else if syncSpecChange = "awsPropertiesChanged" then activeSyncSpecSettings = GetActiveSyncSpecSettings() activeSyncSpecSettings.awsAccessKeyId = m.stateMachine.newSync.LookupMetadata("client", "awsAccessKeyId") activeSyncSpecSettings.awsSecretAccessKey = m.stateMachine.newSync.LookupMetadata("client", "awsSecretAccessKey") activeSyncSpecSettings.awsSessionToken = m.stateMachine.newSync.LookupMetadata("client", "awsSessionToken") m.stateMachine.UpdateRemoteSnapshotSettingsFromSyncSpec(m.stateMachine.newSync) return m.stateMachine.stWaitForTimeout endif m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "YES") m.stateMachine.assetCollection = m.stateMachine.newSync.GetAssets("download") m.stateMachine.assetPoolFiles = CreateObject("roAssetPoolFiles", m.bsp.assetPool, m.stateMachine.assetCollection) m.stateMachine.BuildFileDownloadList(m.stateMachine.newSync) m.stateMachine.UploadDeviceDownloadProgressFileList() m.stateMachine.FileListPendingUpload = false m.stateMachine.AddDeviceDownloadItem("SyncSpecDownloadStarted", "", "") m.stateMachine.contentDownloaded# = 0 ' Use of syncSettings, pendingSyncSettings is made tricky due to the different combinations of ' bsn vs. sfn ' impacts whether a given parameter can be changed publish operation ' impacts whether a settings message would have been received (no for SFN, yes for BSN if Settings Supervisor (but ' in the current release, there are no settings that change on a BSN publish)) ' Settings Supervisor vs Non Settings Supervisor ' only update settings if pre Settings supervisor or sfn if not GetGlobalAA().useSupervisorConfigSpec or lcase(GetGlobalAA().settings.setupType) = "sfn" SetPendingSyncSpecAndSettings(m.stateMachine.newSync) syncSettings = GetPendingSettings() pendingSyncSpecSettings = GetPendingSyncSpecSettings() if not GetGlobalAA().useSupervisorConfigSpec then ' pre settings supervisor m.UpdateSettingsFromSyncSpec(syncSettings, pendingSyncSpecSettings) else ' sfn with settings supervisor SendUpdatedSettingsToSupervisor(lastModifiedTime$, syncSettings) end if else ' if Settings Supervisor and bsn, continue to use existing settings but use new sync spec SetPendingSyncSpec(m.stateMachine.newSync) syncSettings = GetActiveSettings() pendingSyncSpecSettings = GetPendingSyncSpecSettings() endif ' Update the pool sizes based on the newly downloaded sync spec m.bsp.SetPoolSizes(pendingSyncSpecSettings) m.stateMachine.UpdateRemoteSnapshotSettingsFromSyncSpec(m.stateMachine.newSync) ' Only proceed with sync list download if the current time is within the range of allowed times for content downloads currentTime = m.stateMachine.systemTime.GetLocalDateTime() notInDownloadWindow = false if syncSettings.contentDownloadsRestricted then startOfRange% = syncSettings.contentDownloadRangeStart endOfRange% = startOfRange% + syncSettings.contentDownloadRangeLength notInDownloadWindow = OutOfDownloadWindow(currentTime, startOfRange%, endOfRange%) if notInDownloadWindow then m.bsp.diagnostics.PrintDebug("### Not in window to download content") m.stateMachine.AddDeviceDownloadItem("SyncSpecUnchanged", "", "") m.stateMachine.networkingBindingPriorityIndex = 0 m.stateMachine.numRetries% = 0 m.stateMachine.currentTimeBetweenNetConnects% = m.stateMachine.timeBetweenNetConnects% ' if necessary, upload the list of current files to the server if m.stateMachine.FileListPendingUpload then m.stateMachine.BuildFileDownloadList(m.stateMachine.currentSync) m.stateMachine.UploadDeviceDownloadProgressFileList() m.stateMachine.FileListPendingUpload = false end if m.stateMachine.newSync = invalid ' set timer to go off when download window starts and program rate limit appropriately m.stateMachine.contentDownloadWindowStartTimer = CreateObject("roTimer") hour% = startOfRange% / 60 minute% = startOfRange% - (hour% * 60) timeoutTime = CopyDateTime(currentTime) timeoutTime.SetHour(hour%) timeoutTime.SetMinute(minute%) timeoutTime.SetSecond(0) timeoutTime.SetMillisecond(0) GetNextTimeout(m.stateMachine.systemTime, timeoutTime) m.stateMachine.contentDownloadWindowStartTimer.SetDateTime(timeoutTime) m.stateMachine.contentDownloadWindowStartTimer.SetPort(m.stateMachine.msgPort) m.stateMachine.contentDownloadWindowStartTimer.Start() m.bsp.diagnostics.PrintDebug("SyncSpecXferEvent: set timer to start of content download window" + timeoutTime.GetString()) downloadRateLimits = GetDownloadRateLimits(syncSettings, not notInDownloadWindow) SetDownloadRateLimits(m.bsp.diagnostics, downloadRateLimits) return m.stateMachine.stWaitForTimeout else ' set timer to go off when download window ends and program rate limit appropriately m.stateMachine.contentDownloadWindowEndTimer = CreateObject("roTimer") currentTime.SetHour(0) currentTime.SetMinute(0) currentTime.SetSecond(0) currentTime.SetMillisecond(0) currentTime.AddSeconds(endOfRange% * 60) currentTime.Normalize() GetNextTimeout(m.stateMachine.systemTime, currentTime) m.stateMachine.contentDownloadWindowEndTimer.SetDateTime(currentTime) m.stateMachine.contentDownloadWindowEndTimer.SetPort(m.stateMachine.msgPort) m.stateMachine.contentDownloadWindowEndTimer.Start() m.bsp.diagnostics.PrintDebug("STNetworkSchedulerEventHandler: set timer to end of content download window - " + currentTime.GetString()) downloadRateLimits = GetDownloadRateLimits(syncSettings, not notInDownloadWindow) SetDownloadRateLimits(m.bsp.diagnostics, downloadRateLimits) end if else downloadRateLimits = GetDownloadRateLimits(syncSettings, not notInDownloadWindow) SetDownloadRateLimits(m.bsp.diagnostics, downloadRateLimits) end if return m.stateMachine.stDownloadingSyncFiles else if rc = 404 then m.bsp.diagnostics.PrintDebug("### Server has no sync list for us: 404") m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_NO_SYNCSPEC_AVAILABLE, "404") else if rc <> 503 then ' retry - server returned something other than a 200, a 404 or a 503 m.stateMachine.ResetDownloadTimerToDoRetry() if event.GetFailureReason() <> "" then eventData$ = event.GetFailureReason() else eventData$ = str(rc) end if m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_RETRIEVE_SYNCSPEC_FAILURE, eventData$) m.bsp.diagnostics.PrintDebug("### Failed to download sync list: " + eventData$) m.stateMachine.AddDeviceErrorItem("deviceError", "Failed to download sync list", eventData$, str(rc)) else print "received 503" end if end if return m.stateMachine.stWaitForTimeout end function Sub HandleSyncSpecUnchanged(debugMsg as string) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNCSPEC_RECEIVED, "NO") m.bsp.diagnostics.PrintDebug(debugMsg) m.stateMachine.AddDeviceDownloadItem("SyncSpecUnchanged", "", "") m.stateMachine.newSync = invalid m.stateMachine.networkingBindingPriorityIndex = 0 m.stateMachine.numRetries% = 0 m.stateMachine.currentTimeBetweenNetConnects% = m.stateMachine.timeBetweenNetConnects% ' if necessary, upload the list of current files to the server if m.stateMachine.FileListPendingUpload then m.stateMachine.BuildFileDownloadList(m.stateMachine.currentSync) m.stateMachine.UploadDeviceDownloadProgressFileList() m.stateMachine.FileListPendingUpload = false end if end sub Function GetSyncSpecChangeType() as string if m.stateMachine.newSync.EqualTo(m.stateMachine.currentSync) then m.HandleSyncSpecUnchanged("GetSyncSpecChangeType ### Server has given us a spec that matches current-sync. Nothing more to do.") return "noChange" else if m.stateMachine.newSync.FilesEqualTo(m.stateMachine.currentSync) then ' no files have changed - only metadata has changed ' sync spec changes can mostly be ignored if the only changes are to the following ' awsAccessKeyId ' awsSecretAccessKey ' awsSessionToken metaDataMatches = true awsPropertyChanged = false newClientMetadata = m.stateMachine.newSync.getMetadata("client") currentClientMetadata = m.stateMachine.currentSync.getMetadata("client") for each propertyName in currentClientMetadata if currentClientMetadata.DoesExist(propertyName) then currentPropertyValue = currentClientMetadata.Lookup(propertyName) if not newClientMetadata.DoesExist(propertyName) then metaDataMatches = false m.bsp.diagnostics.PrintDebug("GetSyncSpecChangeType " + propertyName + " exists in current sync spec but not new sync spec") else newPropertyValue = newClientMetadata.Lookup(propertyName) if type(currentPropertyValue) = type(newPropertyValue) then if newPropertyValue <> currentPropertyValue then if lcase(propertyName) = "awsaccesskeyid" or lcase(propertyName) = "awssecretaccesskey" or lcase(propertyName)= "awssessiontoken" then m.bsp.diagnostics.PrintDebug("GetSyncSpecChangeType aws property updated") awsPropertyChanged = true else metaDataMatches = false m.bsp.diagnostics.PrintDebug("GetSyncSpecChangeType sync spec change due to updated value for property " + propertyName) endif endif else metaDataMatches = false m.bsp.diagnostics.PrintDebug("GetSyncSpecChangeType " + propertyName + " types differ between old and new") endif endif endif if not metaDataMatches then exit for endif next if metaDataMatches then if awsPropertyChanged then return "awsPropertiesChanged" else ' TEDTODO - is this possible? m.HandleSyncSpecUnchanged("GetSyncSpecChangeType ### Server has given us a spec that meaningfully matches current-sync. Nothing more to do.") return "unknownChange" endif else return "metadataChanged" endif else return "fullChange" end if end function Function NetworkingIsActive() return m.networkingActive end function Function GetRateLimitValue(mode as string, rate as string) as integer if mode = "unlimited" then rate% = 0 else if mode = "specified" then rate% = int(val(rate)) else rate% = -1 endif return rate% end function Function GetDownloadRateLimits(settings as object, inDownloadWindow as boolean) as object downloadRateLimits = [] for each interface in settings.network.interfaces ' TEDTODO -currently only implemented for sync spec settings and likely not correctly. ' I'm not sure this is really true - test and either fix or remove comment. if settings.contentDownloadsRestricted then if inDownloadWindow then rateLimit% = interface.ratelimitrateinwindow% else rateLimit% = interface.ratelimitrateoutsidewindow% endif else rateLimit% = interface.rateLimitRateOutsideWindow% end if interfaceRateLimits = {} interfaceRateLimits.networkInterface = interface.networkInterface interfaceRateLimits.rateLimit% = rateLimit% downloadRateLimits.push(interfaceRateLimits) next return downloadRateLimits end function Sub SetDownloadRateLimits(diagnostics as object, downloadRateLimits as object) for each rateLimitSpec in downloadRateLimits nc = CreateObject("roNetworkConfiguration", rateLimitSpec.networkInterface) if type(nc) = "roNetworkConfiguration" diagnostics.PrintDebug("SetInboundShaperRate to " + stri(rateLimitSpec.rateLimit%)) ok = nc.SetInboundShaperRate(rateLimitSpec.rateLimit%) if not ok then diagnostics.PrintDebug("Failure calling SetInboundShaperRate with parameter " + stri(rateLimitSpec.rateLimit%)) ok = nc.Apply() if not ok then diagnostics.PrintDebug("Failure calling roNetworkConfiguration.Apply()") end if next end sub Function UpdateRegistrySetting(newValue$ as string, existingValue$ as string, registryKey$ as string) as string if lcase(newValue$) <> lcase(existingValue$) then WriteRegistrySetting(registryKey$, newValue$) end if return newValue$ end function Sub UpdateBoolRegistrySetting(newValue as boolean, existingValue as boolean, registryKey$ as string) if newValue <> existingValue then WriteRegistrySetting(registryKey$, GetStringFromBool(newValue)) end if end sub Function STDownloadingSyncFilesEventHandler(event as object, stateData as object) as object stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") nextState = m.StartSyncListDownload() if type(nextState) = "roAssociativeArray" then stop ' can't do this - no transitions on entry '!!!!!!!!!!!!!!!!!! - is this a violation? performing a transition on an entry signal? stateData.nextState = nextState return "TRANSITION" end if return "HANDLED" else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") else if event["EventType"] = "CANCEL_DOWNLOADS" then m.bsp.diagnostics.PrintDebug("Cancel assetFetcher downloads message received") if type(m.stateMachine.assetFetcher) = "roAssetFetcher" then m.bsp.diagnostics.PrintDebug("Cancel assetFetcher downloads") m.stateMachine.assetFetcher.AsyncCancel() m.stateMachine.assetFetcher = invalid stateData.nextState = m.stateMachine.stWaitForTimeout return "TRANSITION" else return "HANDLED" end if end if end if else if type(event) = "roAssetFetcherProgressEvent" then m.bsp.diagnostics.PrintDebug("### File download progress " + event.GetFileName() + str(event.GetCurrentFilePercentage())) m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_FILE_DOWNLOAD_PROGRESS, event.GetFileName() + chr(9) + str(event.GetCurrentFilePercentage())) fileIndex% = event.GetFileIndex() fileItem = m.stateMachine.newSync.GetFile("download", fileIndex%) if type(fileItem) = "roAssociativeArray" then m.stateMachine.AddDeviceDownloadProgressItem(fileItem, str(event.GetCurrentFilePercentage()), "ok") end if return "HANDLED" else if type(event) = "roAssetFetcherEvent" then if event.GetUserData() = "BSN" then nextState = m.HandleAssetFetcherEvent(event) if type(nextState) = "roAssociativeArray" then stateData.nextState = nextState return "TRANSITION" end if return "HANDLED" end if end if stateData.nextState = m.superState return "SUPER" end function Function StartSyncListDownload() as object activeSyncSpecSettings = GetActiveSyncSpecSettings() m.bsp.diagnostics.PrintDebug("### Start sync list download") m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_DOWNLOAD_START, "") m.stateMachine.AddEventItem("StartSyncListDownload", m.stateMachine.newSync.GetName(), "") m.bsp.assetPool.ReserveMegabytes(50) m.stateMachine.assetFetcher = CreateObject("roAssetFetcher", m.bsp.assetPool) m.stateMachine.assetFetcher.SetUserData("BSN") m.stateMachine.assetFetcher.SetPort(m.stateMachine.msgPort) if m.stateMachine.setUserAndPassword then m.stateMachine.assetFetcher.SetUserAndPassword(activeSyncSpecSettings.user, activeSyncSpecSettings.password) m.stateMachine.assetFetcher.EnableUnsafeAuthentication(m.stateMachine.enableBasicAuthentication) m.stateMachine.assetFetcher.SetMinimumTransferRate(1000, 60) serverHeaders = CleanServerHeaders(m.stateMachine.newSync.GetMetadata("server")) m.stateMachine.assetFetcher.SetHeaders(serverHeaders) m.stateMachine.assetFetcher.AddHeader("User-Agent", m.bsp.userAgent$) m.stateMachine.assetFetcher.AddHeader("DeviceID", m.stateMachine.deviceUniqueID$) m.stateMachine.assetFetcher.AddHeader("DeviceModel", m.stateMachine.deviceModel$) m.stateMachine.assetFetcher.AddHeader("DeviceFamily", m.stateMachine.deviceFamily$) m.stateMachine.assetFetcher.SetFileProgressIntervalSeconds(15) aa = GetBinding("contentDownloadEnabled", m.stateMachine.networkingBindingPriorityIndex) binding = aa.network_interface m.stateMachine.networkingBindingPriorityIndex = aa.priorityIndex m.bsp.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for assetFetcher is (StartSyncListDownload)", binding)) ok = m.stateMachine.assetFetcher.BindToInterface(binding) if not ok then stop ' clear file download failure count m.stateMachine.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.bsp.assetPool.ProtectAssets("BNM-new", m.stateMachine.newSync)) or (not m.bsp.assetPool.ProtectAssets("current", m.stateMachine.currentSync)) then ' don't allow download to delete current files m.stateMachine.LogProtectFilesFailure() end if if m.stateMachine.proxy_mode then m.stateMachine.assetFetcher.AddHeader("Roku-Cache-Request", "Yes") end if if m.stateMachine.syncType$ = "download" then if m.stateMachine.downloadOnlyIfCached then m.stateMachine.assetFetcher.AddHeader("Cache-Control", "only-if-cached") if not m.stateMachine.assetFetcher.AsyncDownload(m.stateMachine.newSync) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_IMMEDIATE_FAILURE, m.stateMachine.assetFetcher.GetFailureReason()) m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### AsyncDownload failed: " + m.stateMachine.assetFetcher.GetFailureReason()) m.stateMachine.AddDeviceErrorItem("deviceError", m.stateMachine.newSync.GetName(), "AsyncDownloadFailure: " + m.stateMachine.assetFetcher.GetFailureReason(), "") m.stateMachine.ResetDownloadTimerToDoRetry() m.stateMachine.newSync = invalid return m.stateMachine.stWaitForTimeout end if else m.stateMachine.assetFetcher.AsyncSuggestCache(m.stateMachine.newSync) end if return 0 end function Function HandleAssetFetcherEvent(event as object) as object newSyncSettings = GetPendingSettings() newSyncSpecSettings = GetPendingSyncSpecSettings() m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### assetFetcher_event") if (event.GetEvent() = m.stateMachine.POOL_EVENT_FILE_DOWNLOADED) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_FILE_DOWNLOAD_COMPLETE, event.GetName()) m.bsp.diagnostics.PrintDebug("### File downloaded " + event.GetName()) ' see if the user should be charged for this download if m.stateMachine.chargeableFiles.DoesExist(event.GetName()) then filePath$ = m.stateMachine.assetPoolFiles.GetPoolFilePath(event.GetName()) file = CreateObject("roReadFile", filePath$) if type(file) = "roReadFile" then file.SeekToEnd() totalContentDownloaded# = m.stateMachine.contentDownloaded# totalContentDownloaded# = totalContentDownloaded# + file.CurrentPosition() m.stateMachine.contentDownloaded# = totalContentDownloaded# m.bsp.diagnostics.PrintDebug("### File size " + str(file.CurrentPosition())) m.bsp.diagnostics.PrintDebug("### Content downloaded = " + str(m.stateMachine.contentDownloaded#)) end if file = invalid end if else if (event.GetEvent() = m.stateMachine.POOL_EVENT_FILE_FAILED) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_FILE_DOWNLOAD_FAILURE, event.GetName() + chr(9) + event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### File failed " + event.GetName() + ": " + event.GetFailureReason()) m.stateMachine.AddDeviceErrorItem("FileDownloadFailure", event.GetName(), event.GetFailureReason(), str(event.GetResponseCode())) ' log this error to the download progress handler fileIndex% = event.GetFileIndex() fileItem = m.stateMachine.newSync.GetFile("download", fileIndex%) if type(fileItem) = "roAssociativeArray" then m.stateMachine.AddDeviceDownloadProgressItem(fileItem, "-1", event.GetFailureReason()) end if m.stateMachine.fileDownloadFailureCount% = m.stateMachine.fileDownloadFailureCount% + 1 if m.stateMachine.fileDownloadFailureCount% >= m.stateMachine.maxFileDownloadFailures% then m.bsp.diagnostics.PrintDebug("### " + stri(m.stateMachine.maxFileDownloadFailures%) + " file download failures - set timer for retry.") m.stateMachine.assetFetcher.AsyncCancel() m.stateMachine.ResetDownloadTimerToDoRetry() m.stateMachine.assetFetcher = invalid return m.stateMachine.stWaitForTimeout end if else if (event.GetEvent() = m.stateMachine.POOL_EVENT_ALL_FAILED) then if m.stateMachine.syncType$ = "download" then m.stateMachine.ResetDownloadTimerToDoRetry() m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_FAILURE, event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### Sync failed: " + event.GetFailureReason()) m.stateMachine.AddDeviceErrorItem("POOL_EVENT_ALL_FAILED", "", event.GetFailureReason(), str(event.GetResponseCode())) ' capture total content downloaded m.bsp.diagnostics.PrintDebug("### Total content downloaded = " + str(m.stateMachine.contentDownloaded#)) ok = m.stateMachine.UploadTrafficDownload(m.stateMachine.contentDownloaded#) if ok then m.stateMachine.contentDownloaded# = 0 end if else m.bsp.diagnostics.PrintDebug("### Proxy mode sync complete") end if m.stateMachine.newSync = invalid m.stateMachine.assetFetcher = invalid return m.stateMachine.stWaitForTimeout else if (event.GetEvent() = m.stateMachine.POOL_EVENT_ALL_DOWNLOADED) then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_DOWNLOAD_COMPLETE, "") m.bsp.diagnostics.PrintDebug("### All files downloaded") m.stateMachine.AddDeviceDownloadItem("All files downloaded", "", "") ' send up the list of files downloaded m.stateMachine.BuildFileDownloadList(m.stateMachine.newSync) m.stateMachine.UploadDeviceDownloadProgressFileList() m.stateMachine.FileListPendingUpload = false ' capture total content downloaded m.bsp.diagnostics.PrintDebug("### Total content downloaded = " + str(m.stateMachine.contentDownloaded#)) ok = m.stateMachine.UploadTrafficDownload(m.stateMachine.contentDownloaded#) if ok then m.stateMachine.contentDownloaded# = 0 end if ' Log the end of sync list download m.stateMachine.AddEventItem("EndSyncListDownload", m.stateMachine.newSync.GetName(), str(event.GetResponseCode())) ' Clear retry count and reset binding priority index and timeout period m.stateMachine.networkingBindingPriorityIndex = 0 m.stateMachine.numRetries% = 0 m.stateMachine.currentTimeBetweenNetConnects% = m.stateMachine.timeBetweenNetConnects% ' diagnostic web server - pre Settings Supervisor dwsRebootRequired = false if not GetGlobalAA().useSupervisorConfigSpec then dwsRebootRequired = GetAndSaveDWSParams(newSyncSettings, GetGlobalAA().registrySettings) m.stateMachine.stRetrievingSyncList.UpdateBoolRegistrySetting(newSyncSettings.dwsEnabled, GetGlobalAA().registrySettings.dwsEnabled, "dwse") m.stateMachine.stRetrievingSyncList.UpdateRegistrySetting(newSyncSettings.dwsPassword, GetGlobalAA().registrySettings.dwsPassword$, "dwsp") endif oldSyncSpecScriptsOnly = m.stateMachine.currentSync.FilterFiles("download", { group: "script" }) newSyncSpecScriptsOnly = m.stateMachine.newSync.FilterFiles("download", { group: "script" }) rebootRequired = false if not oldSyncSpecScriptsOnly.FilesEqualTo(newSyncSpecScriptsOnly) then realizer = CreateObject("roAssetRealizer", m.bsp.assetPool, "/") globalAA = GetGlobalAA() globalAA.bsp.msgPort.DeferWatchdog(120) event = realizer.Realize(newSyncSpecScriptsOnly) realizer = invalid if event.GetEvent() <> m.stateMachine.EVENT_REALIZE_SUCCESS then m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_REALIZE_FAILURE, stri(event.GetEvent()) + chr(9) + event.GetName() + chr(9) + event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### Realize failed " + stri(event.GetEvent()) + chr(9) + event.GetName() + chr(9) + event.GetFailureReason()) m.stateMachine.AddDeviceErrorItem("RealizeFailure", event.GetName(), event.GetFailureReason(), str(event.GetEvent())) m.stateMachine.newSync = invalid m.stateMachine.assetFetcher = invalid return m.stateMachine.stWaitForTimeout end if ' reboot if successful rebootRequired = true end if ' Save to current-sync.json then do cleanup jsonSyncSpec$ = m.stateMachine.newSync.WriteToString({ format : "json" }) ok = WriteAsciiFile("current-sync.json", jsonSyncSpec$) if not ok then stop ' timeZone - pre Settings Supervisor if not GetGlobalAA().useSupervisorConfigSpec then timezone = newSyncSettings.timezone if timezone <> "" then m.stateMachine.systemTime.SetTimeZone(timezone) end if endif m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### DOWNLOAD COMPLETE") if rebootRequired then m.bsp.diagnostics.PrintDebug("### new script or upgrade found - reboot") m.stateMachine.AddEventItem("DownloadComplete - new script or upgrade file found", m.stateMachine.newSync.GetName(), "") m.stateMachine.RebootAfterEventsSent() end if ' dws - pre Settings Supervisor if not GetGlobalAA().useSupervisorConfigSpec then if dwsRebootRequired then m.bsp.diagnostics.PrintDebug("### DWS parameter change - reboot") m.stateMachine.AddEventItem("DownloadComplete - DWS parameter change", m.stateMachine.newSync.GetName(), "") m.stateMachine.RebootAfterEventsSent() end if endif m.stateMachine.assetCollection = m.stateMachine.newSync.GetAssets("download") m.stateMachine.assetPoolFiles = CreateObject("roAssetPoolFiles", m.bsp.assetPool, m.stateMachine.assetCollection) if type(m.stateMachine.assetPoolFiles) <> "roAssetPoolFiles" then stop globalAA = GetGlobalAA() globalAA.autoscheduleFilePath$ = GetPoolFilePath(m.stateMachine.assetPoolFiles, "autoschedule.json") if globalAA.autoscheduleFilePath$ = "" then stop globalAA.boseProductsFilePath$ = GetPoolFilePath(m.stateMachine.assetPoolFiles, "PartnerProducts.json") m.stateMachine.newSync = invalid m.stateMachine.assetFetcher = invalid m.stateMachine.currentSync = CreateObject("roSyncSpec") if type(m.stateMachine.currentSync) <> "roSyncSpec" then stop if not m.stateMachine.currentSync.ReadFromFile("current-sync.json") then stop ' settings supervisor ' for bsn, values are updated via message from supervisor (none are updated via Publish with 'current' bacon) ' for sfn, the user can make changes for ' contentDownloadsRestricted, loggingEnabled and remoteSnapshots when publishing ' these settings are updated in a different way ' 'TEDTODO - verify that this works? I think it's a roundabout way. ' non settings supervisor ' update settings (only changes for sfn, see above) if not GetGlobalAA().useSupervisorConfigSpec then UpdateSyncSpecAndSettings(m.stateMachine.currentSync, "current-sync.json", "network") settings = GetGlobalAA().settings endif m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### return from HandleAssetFetcherEvent") debugOn = newSyncSpecSettings.enableSerialDebugging m.bsp.diagnostics.UpdateDebugOn(debugOn) systemLogDebugOn = newSyncSpecSettings.enableSystemLogDebugging m.bsp.diagnostics.UpdateSystemLogDebugOn(systemLogDebugOn) m.bsp.contentEncrypted = false deviceCustomization = CreateObject("roDeviceCustomization") deviceCustomization.StoreObfuscatedEncryptionKey("AesCtrHmac", m.bsp.obfuscatedEncryptionKey) if m.bsp.obfuscatedEncryptionKey <> "" then m.bsp.contentEncrypted = true end if m.bsp.SetPerFileEncryptionStatus(m.stateMachine.currentSync) ' send internal message to prepare for restart prepareForRestartEvent = { } prepareForRestartEvent["EventType"] = "PREPARE_FOR_RESTART" m.stateMachine.msgPort.PostMessage(prepareForRestartEvent) ' send internal message indicating that new content is available contentUpdatedEvent = { } contentUpdatedEvent["EventType"] = "CONTENT_UPDATED" m.stateMachine.msgPort.PostMessage(contentUpdatedEvent) return m.stateMachine.stWaitForTimeout end if end function Sub LogProtectFilesFailure() m.stateMachine.logging.WriteDiagnosticLogEntry(m.stateMachine.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, "AssetPool Protect Failure") m.stateMachine.logging.FlushLogFile() DeleteFile("autorun.brs") m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + "AssetPool Protect Failure") m.stateMachine.AddDeviceErrorItem("deviceError", m.stateMachine.currentSync.GetName(), "ProtectFilesFailure: " + "AssetPool Protect Failure", "") globalAA = GetGlobalAA() globalAA.bsp.msgPort.DeferWatchdog(15) msg = wait(10000, 0) ' wait for either a timeout (10 seconds) or a message indicating that the post was complete a = RebootSystem() end sub 'endregion 'region BP State Machine ' ************************************************* ' ' BP State Machine ' ' ************************************************* Function newBPStateMachine(bsp as object, inputPortIdentity$ as string, buttonPanelIndex% as integer, buttonNumber% as integer) as object BPStateMachine = { } BPStateMachine.bsp = bsp BPStateMachine.msgPort = bsp.msgPort BPStateMachine.inputPortIdentity$ = inputPortIdentity$ BPStateMachine.buttonPanelIndex% = buttonPanelIndex% BPStateMachine.buttonNumber% = buttonNumber% BPStateMachine.timer = invalid BPStateMachine.configuration$ = "press" BPStateMachine.initialHoldoff% = -1 BPStateMachine.repeatInterval% = -1 BPStateMachine.ConfigureButton = ConfigureButton BPStateMachine.EventHandler = BPEventHandler BPStateMachine.state$ = "ButtonUp" return BPStateMachine end function Sub BPEventHandler(event as object) if m.state$ = "ButtonUp" then if type(event) = "roControlDown" and stri(event.GetSourceIdentity()) = m.inputPortIdentity$ and m.buttonNumber% = event.GetInt() then m.bsp.diagnostics.PrintDebug("BP control down" + str(event.GetInt())) bpControlDown = { } bpControlDown["EventType"] = "BPControlDown" bpControlDown["ButtonPanelIndex"] = StripLeadingSpaces(str(m.buttonPanelIndex%)) bpControlDown["ButtonNumber"] = StripLeadingSpaces(str(event.GetInt())) m.msgPort.PostMessage(bpControlDown) if m.configuration$ = "pressContinuous" then m.timer = CreateObject("roTimer") m.timer.SetPort(m.msgPort) m.timer.SetElapsed(0, m.initialHoldoff%) m.timer.Start() end if m.state$ = "ButtonDown" end if else if type(event) = "roControlUp" and stri(event.GetSourceIdentity()) = m.inputPortIdentity$ and m.buttonNumber% = event.GetInt() then m.bsp.diagnostics.PrintDebug("BP control up" + str(event.GetInt())) ' if continuous, stop and destroy the timer if type(m.timer) = "roTimer" then m.timer.Stop() m.timer = invalid end if m.state$ = "ButtonUp" ' else check for repeat timeout else if type(event) = "roTimerEvent" and type(m.timer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(m.timer.GetIdentity()) then m.bsp.diagnostics.PrintDebug("BP REPEAT control down" + str(m.buttonNumber%)) bpControlDown = { } bpControlDown["EventType"] = "BPControlDown" bpControlDown["ButtonPanelIndex"] = StripLeadingSpaces(str(m.buttonPanelIndex%)) bpControlDown["ButtonNumber"] = StripLeadingSpaces(str(m.buttonNumber%)) m.msgPort.PostMessage(bpControlDown) m.timer.SetElapsed(0, m.repeatInterval%) m.timer.Start() end if end if end if end sub Sub ConfigureButton(bpSpec as object) bpConfiguration$ = bpSpec.configuration$ ' no change necessary if the old and new configurations are the same and are a simple press if bpConfiguration$ = "press" and m.configuration$ = "press" then return ' if the old configuration was continuous and the new configuration is a simple press, stop the timer and destroy the timer object if bpConfiguration$ = "press" and m.configuration$ = "pressContinuous" then if type(m.timer) = "roTimer" then m.timer.Stop() m.timer = invalid end if m.configuration$ = "press" return end if ' capture the repeat rates if the new configuration is continuous if bpConfiguration$ = "pressContinuous" then m.initialHoldoff% = int(val(bpSpec.initialHoldoff$)) m.repeatInterval% = int(val(bpSpec.repeatInterval$)) end if ' if both the old and new configurations are continuous, restart the timer (if it is active) if bpConfiguration$ = "pressContinuous" and m.configuration$ = "pressContinuous" then if type(m.timer) = "roTimer" then m.timer.Stop() m.timer.SetElapsed(0, m.initialHoldoff%) m.timer.Start() end if end if ' if the old configuration was simple press and the new configuration is continuous, then capture the new values ' but don't start a timer (repeating won't start if the button was down at the time the state is entered). m.configuration$ = bpConfiguration$ end sub 'endregion 'region GPIO State Machine ' ************************************************* ' ' GPIO State Machine ' ' ************************************************* Function newGPIOStateMachine(bsp as object, controlPort as object, inputPortIdentity$ as string, buttonNumber% as integer) as object GPIOStateMachine = { } GPIOStateMachine.bsp = bsp GPIOStateMachine.msgPort = bsp.msgPort GPIOStateMachine.inputPortIdentity$ = inputPortIdentity$ GPIOStateMachine.buttonNumber% = buttonNumber% GPIOStateMachine.timer = invalid GPIOStateMachine.configuration$ = "press" GPIOStateMachine.initialHoldoff% = -1 GPIOStateMachine.repeatInterval% = -1 GPIOStateMachine.ConfigureButton = ConfigureButton GPIOStateMachine.EventHandler = GPIOEventHandler if IsControlPort(controlPort) then if controlPort.IsInputActive(buttonNumber%) then GPIOStateMachine.state$ = "ButtonDown" else GPIOStateMachine.state$ = "ButtonUp" end if end if return GPIOStateMachine end function Sub GPIOEventHandler(event as object) if m.state$ = "ButtonUp" then if type(event) = "roControlDown" and stri(event.GetSourceIdentity()) = m.inputPortIdentity$ and m.buttonNumber% = event.GetInt() then m.bsp.diagnostics.PrintDebug("GPIO control down" + str(event.GetInt())) gpioControlDown = { } gpioControlDown["EventType"] = "GPIOControlDown" gpioControlDown["ButtonNumber"] = StripLeadingSpaces(str(event.GetInt())) m.msgPort.PostMessage(gpioControlDown) if m.configuration$ = "pressContinuous" then m.timer = CreateObject("roTimer") m.timer.SetPort(m.msgPort) m.timer.SetElapsed(0, m.initialHoldoff%) m.timer.Start() end if m.state$ = "ButtonDown" end if else if type(event) = "roControlUp" and stri(event.GetSourceIdentity()) = m.inputPortIdentity$ and m.buttonNumber% = event.GetInt() then m.bsp.diagnostics.PrintDebug("GPIO control up" + str(event.GetInt())) ' if continuous, stop and destroy the timer if type(m.timer) = "roTimer" then m.timer.Stop() m.timer = invalid end if gpioControlUp = { } gpioControlUp["EventType"] = "GPIOControlUp" gpioControlUp["ButtonNumber"] = StripLeadingSpaces(str(event.GetInt())) m.msgPort.PostMessage(gpioControlUp) m.state$ = "ButtonUp" ' else check for repeat timeout else if type(event) = "roTimerEvent" and type(m.timer) = "roTimer" then if stri(event.GetSourceIdentity()) = stri(m.timer.GetIdentity()) then m.bsp.diagnostics.PrintDebug("GPIO REPEAT control down" + str(m.buttonNumber%)) gpioControlDown = { } gpioControlDown["EventType"] = "GPIOControlDown" gpioControlDown["ButtonNumber"] = StripLeadingSpaces(str(m.buttonNumber%)) m.msgPort.PostMessage(gpioControlDown) m.timer.SetElapsed(0, m.repeatInterval%) m.timer.Start() end if end if end if end sub 'endregion 'region EventLoop ' ************************************************* ' ' Event Loop and associated processing ' ' ************************************************* Sub EventLoop() SQLITE_COMPLETE = 100 while true msg = wait(0, m.msgPort) m.diagnostics.PrintTimestamp() m.diagnostics.PrintDebug("msg received - type=" + type(msg)) if type(msg) = "roControlDown" and stri(msg.GetSourceIdentity()) = stri(m.svcPort.GetIdentity()) then if msg.GetInt() = 12 then stop end if end if eventHandled = false for each scriptPlugin in m.scriptPlugins ' ERR_NORMAL_END = &hFC if scriptPlugin.plugin = invalid then m.diagnostics.PrintDebug("Plugin for " + scriptPlugin.name$ + " is invalid.") m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SCRIPT_PLUGIN_FAILURE, scriptPlugin.name$) else eventHandled = scriptPlugin.plugin.ProcessEvent(msg) if eventHandled then exit for end if end if ' retVal = Eval("eventHandled = scriptPlugin.plugin.ProcessEvent(msg)") ' if retVal <> &hFC then ' ' log the failure ' m.diagnostics.PrintDebug("Failure executing Eval to execute script plugin file event handler: return value = " + stri(retVal)) ' m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_SCRIPT_PLUGIN_FAILURE, stri(retVal) + chr(9) + "EventHandler") ' endif next ' don't propagate the event if it was handled by a plugin if not eventHandled then if type(msg) = "roSqliteEvent" then if msg.GetSqlResult() <> SQLITE_COMPLETE then m.diagnostics.PrintDebug("roSqliteEvent.GetSqlResult() <> SQLITE_COMPLETE") if type(msg.GetSqlResult()) = "roInt" then m.diagnostics.PrintDebug("roSqliteEvent.GetSqlResult() = " + stri(roSqliteEvent.GetSqlResult())) end if end if end if if type(msg) = "roHttpEvent" then userdata = msg.GetUserData() if type(userdata) = "roAssociativeArray" and type(userdata.HandleEvent) = "roFunction" then userData.HandleEvent(userData, msg) end if else m.playerHSM.Dispatch(msg) for buttonPanelIndex% = 0 to 3 for i% = 0 to 10 if type(m.bpSM[buttonPanelIndex%, i%]) = "roAssociativeArray" then m.bpSM[buttonPanelIndex%, i%].EventHandler(msg) end if next next for i% = 0 to 7 if type(m.gpioSM[i%]) = "roAssociativeArray" then m.gpioSM[i%].EventHandler(msg) end if next if type(m.sign) = "roAssociativeArray" then numZones% = m.sign.zonesHSM.Count() for i% = 0 to numZones% - 1 m.dispatchingZone = m.sign.zonesHSM[i%] m.dispatchingZone.Dispatch(msg) next end if if m.networkingActive then m.networkingHSM.Dispatch(msg) end if m.diagnostics.DiagnoseAndRecoverWifiNetwork(msg) end if end if end while end sub Function ExecuteSwitchPresentationCommand(presentationName$ as string) as boolean ' retrieve target presentation presentation = m.presentations.Lookup(presentationName$) if type(presentation) = "roAssociativeArray" then ' check for existence of target presentation - if it's not present, don't try to switch to it autoplayFileName$ = "autoplay-" + presentation.presentationName + ".json" jsonFileName$ = m.assetPoolFiles.GetPoolFilePath(autoplayFileName$) if jsonFileName$ = "" then m.diagnostics.PrintDebug("switchPresentation: target presentation not found - " + presentationName$) return false end if ' send internal message to prepare for restart prepareForRestartEvent = { } prepareForRestartEvent["EventType"] = "PREPARE_FOR_RESTART" m.msgPort.PostMessage(prepareForRestartEvent) ' send switch presentation internal message switchPresentationEvent = { } switchPresentationEvent["EventType"] = "SWITCH_PRESENTATION" switchPresentationEvent["Presentation"] = presentation.presentationName m.msgPort.PostMessage(switchPresentationEvent) return true end if return false end function Sub ExecuteMediaStateCommands(zoneHSM as object, cmds as object) if type(cmds) = "roArray" then for each cmd in cmds if cmd.name$ = "switchPresentation" then m.diagnostics.PrintDebug("switchPresentation: not supported by media state commands") return end if m.ExecuteCmd(zoneHSM, cmd.name$, cmd.parameters) next end if end sub Function ExecuteTransitionCommands(zoneHSM as object, transition as object) as boolean transitionCmds = transition.transitionCmds if type(transitionCmds) = "roArray" then for each transitionCmd in transitionCmds command$ = transitionCmd.name$ if command$ = "synchronize" and type(m.SyncManager) <> "roSyncManager" then ' if the next command is synchronize, get the file to preload nextState$ = transition.targetMediaState$ zoneHSM.preloadState = zoneHSM.stateTable[nextState$] else if command$ = "switchPresentation" then presentationName$ = transitionCmd.parameters["presentationName"].GetCurrentParameterValue() return m.ExecuteSwitchPresentationCommand(presentationName$) else if command$ = "internalSynchronize" then if type(transition.internalSynchronizeEventsMaster) = "roAssociativeArray" then activeState = zoneHSM.activeState if type(activeState) = "roAssociativeArray" then activeState.internalSynchronizeEventsMaster = transition.internalSynchronizeEventsMaster end if end if end if m.ExecuteCmd(zoneHSM, transitionCmd.name$, transitionCmd.parameters) next end if return false 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$) end if return "" end function Sub InitializeNonPrintableKeyboardCodeList() ' Space 32 ' Left arrow 32848 ' Right arrow 32847 ' Up arrow 32850 ' Down arrow 32849 ' Return 10 ' Enter 13 ' Escape 27 ' Page Up 32843 ' Page Down 32846 ' F1 32826 ' F2 32827 ' F3 32828 ' F4 32829 ' F5 32830 ' F6 32831 ' F7 32832 ' F8 32833 ' F9 32834 ' F10 32835 ' F11 32836 ' F12 32837 ' F13 (Print Screen) 32838 ' F14 (Scroll Lock) 32839 ' F15 (Pause Break) 32840 ' Backspace 8 ' Tab 9 ' Insert 32841 ' Delete 127 ' Home 32842 ' End 32845 ' Capslock 32825 ' Mute 32895 ' Volume down 32897 ' Volume up 32896 ' Next track 786613 ' Previous track 786614 ' Play/Pause 786637 ' Stop music 786615 ' Stop browsing 786982 ' Power 65665 ' Back 786980 ' Forward 786981 ' Refresh 786983 m.nonPrintableKeyboardKeys = { } m.nonPrintableKeyboardKeys.AddReplace("8", "") m.nonPrintableKeyboardKeys.AddReplace("9", "") m.nonPrintableKeyboardKeys.AddReplace("10", "") m.nonPrintableKeyboardKeys.AddReplace("13", "") m.nonPrintableKeyboardKeys.AddReplace("27", "") m.nonPrintableKeyboardKeys.AddReplace("32", "") m.nonPrintableKeyboardKeys.AddReplace("127", "") m.nonPrintableKeyboardKeys.AddReplace("32848", "") m.nonPrintableKeyboardKeys.AddReplace("32847", "") m.nonPrintableKeyboardKeys.AddReplace("32850", "") m.nonPrintableKeyboardKeys.AddReplace("32849", "") m.nonPrintableKeyboardKeys.AddReplace("32843", "") m.nonPrintableKeyboardKeys.AddReplace("32846", "") m.nonPrintableKeyboardKeys.AddReplace("32826", "") m.nonPrintableKeyboardKeys.AddReplace("32827", "") m.nonPrintableKeyboardKeys.AddReplace("32828", "") m.nonPrintableKeyboardKeys.AddReplace("32829", "") m.nonPrintableKeyboardKeys.AddReplace("32830", "") m.nonPrintableKeyboardKeys.AddReplace("32831", "") m.nonPrintableKeyboardKeys.AddReplace("32832", "") m.nonPrintableKeyboardKeys.AddReplace("32833", "") m.nonPrintableKeyboardKeys.AddReplace("32834", "") m.nonPrintableKeyboardKeys.AddReplace("32835", "") m.nonPrintableKeyboardKeys.AddReplace("32836", "") m.nonPrintableKeyboardKeys.AddReplace("32837", "") m.nonPrintableKeyboardKeys.AddReplace("32838", "") m.nonPrintableKeyboardKeys.AddReplace("32839", "") m.nonPrintableKeyboardKeys.AddReplace("32840", "") m.nonPrintableKeyboardKeys.AddReplace("32841", "") m.nonPrintableKeyboardKeys.AddReplace("32842", "") m.nonPrintableKeyboardKeys.AddReplace("32845", "") m.nonPrintableKeyboardKeys.AddReplace("32825", "") m.nonPrintableKeyboardKeys.AddReplace("32895", "") m.nonPrintableKeyboardKeys.AddReplace("32897", "") m.nonPrintableKeyboardKeys.AddReplace("32896", "") m.nonPrintableKeyboardKeys.AddReplace("786613", "") m.nonPrintableKeyboardKeys.AddReplace("786614", "") m.nonPrintableKeyboardKeys.AddReplace("786637", "") m.nonPrintableKeyboardKeys.AddReplace("786615", "") m.nonPrintableKeyboardKeys.AddReplace("786982", "") m.nonPrintableKeyboardKeys.AddReplace("65665", "") m.nonPrintableKeyboardKeys.AddReplace("786980", "") m.nonPrintableKeyboardKeys.AddReplace("786981", "") m.nonPrintableKeyboardKeys.AddReplace("786983", "") end sub 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 then return "" end if return remoteCommands[remoteCommand%] end function Function GetIntegerParameterValue(parameters as object, parameterName$ as string, defaultValue% as integer) as integer parameter = parameters[parameterName$] parameter$ = parameter.GetCurrentParameterValue() parameter% = defaultValue% if parameter$ <> "" then parameter% = int(val(parameter$)) end if return parameter% end function Function GetBmapOperatorValue(bmapOperator as object) as integer operatorValue = -1 operator = bmapOperator.Operator if operator = "Operators.Set" then operatorValue = 0 else if operator = "Operators.Get" then operatorValue = 1 else if operator = "Operators.SetGet" then operatorValue = 2 else if operator = "Operators.Status" then operatorValue = 3 else if operator = "Operators.Error" then operatorValue = 4 else if operator = "Operators.Start" then operatorValue = 5 else if operator = "Operators.Result" then operatorValue = 6 else if operator = "Operators.Processing" then operatorValue = 7 end if return operatorValue end function Sub ExecuteSendBMapHexCommand(parameters as object) port$ = parameters["port"].getCurrentParameterValue() runtimeConnector$ = m.GetRuntimeUsbConnector(port$) if type(m.bmapByPort) = "roAssociativeArray" then bmap = m.bmapByPort.Lookup(runtimeConnector$) endif if type(bmap) <> "roBmap" then m.diagnostics.PrintDebug("ExecuteSendBmapSendHexCommand: bmap object does not exist on runtimeConnector: " + runtimeConnector$) else bmapHexStringParameter = parameters["message"] hexString$ = bmapHexStringParameter.GetCurrentParameterValue() request = CreateObject("roByteArray") request.FromHexString(hexString$) m.diagnostics.PrintDebug("BMAP Hex Command byte array:") m.diagnostics.PrintDebug(hexString$) if not bmap.Send(request) then m.diagnostics.PrintDebug("BMAP send failure") m.diagnostics.PrintDebug(bmap.GetFailureReason()) end if endif End Sub Sub ExecuteSendBMapCommand(parameters as object) connector$ = parameters["port"].getCurrentParameterValue() port$ = m.GetRuntimeUsbConnector(connector$) if type(m.bmapByPort) = "roAssociativeArray" then bmap = m.bmapByPort.Lookup(port$) endif if type(bmap) <> "roBmap" then m.diagnostics.PrintDebug("ExecuteSendBMapCommand: bmap object does not exist") else boseProduct = m.sign.boseProductsByConnector[connector$] bmapCommunicationSpec = boseProduct.bmapCommunicationSpec bmapFunctionalBlockName = parameters.bmapFunctionBlock.getCurrentParameterValue() bmapFunctionName = parameters.bmapFunction.getCurrentParameterValue() bmapOperatorName = parameters.bmapOperator.getCurrentParameterValue() matchedFunctionBlock = invalid matchedFunction = invalid matchedOperator = invalid payload = [] payloadSize = 0 functionBlocks = bmapCommunicationSpec.FunctionBlocks for each functionBlock in functionBlocks if functionBlock.Name = bmapFunctionalBlockName then matchedFunctionBlock = functionBlock functionBlockValue = matchedFunctionBlock.Value exit for end if next if matchedFunctionBlock <> invalid then for each bmapFunction in matchedFunctionBlock.Functions if bmapFunction.Name = bmapFunctionName then matchedFunction = bmapFunction functionValue = matchedFunction.Value exit for end if next if matchedFunction <> invalid then for each operator in matchedFunction.operators if operator.Operator = bmapOperatorName then operatorValue = GetBmapOperatorValue(operator) fields = operator.Fields for each field in fields fieldName = field.Name bitfields = field.bitfields if type(bitfields) = "roArray" and bitfields.Count() > 0 then fieldValueInt = 0 shiftNextBitcount = 0 for each bitfield in bitfields key = fieldName + ":" + bitfield.Name bitfieldParameter = parameters.Lookup(key) bitFieldValue$ = bitfieldParameter.getCurrentParameterValue() ' TODOBMAP 'bitFieldValue = int(val(bitFieldValue$)) 'bitFieldValue$ is hex, at least some of the time. all the time? ' length of bitFieldValue$ - numBits > 4? ba = CreateObject("roByteArray") if len(bitFieldValue$) = 1 then bitFieldValue$ = "0" + bitFieldValue$ endif ba.FromHexString(bitFieldValue$) bitFieldValue = ba[0] bitFieldValue = ShiftLeft(bitfieldValue, shiftNextBitcount) fieldValueInt = fieldValueInt + bitFieldValue shiftNextBitcount = shiftNextBitcount + bitfield.NumBits next ba = CreateObject("roByteArray") ba.push(fieldValueInt) fieldValue = ba.ToHexString() if len(fieldValue) = 0 then str = "00" else if len(fieldValue) = 1 then str = "0" + fieldValue else if len(fieldValue) > 2 then str = fieldValue else str = fieldValue endif for i = 0 to len(str) - 1 payload.push(mid(str, i + 1, 1)) next payloadSize = payloadSize + (len(str) / 2) else fieldParameter = parameters.Lookup(field.Name) if fieldParameter <> invalid then fieldValue = fieldParameter.getCurrentParameterValue() if field.Units = "ASCII" then ba = CreateObject("roByteArray") ba.FromAsciiString(fieldValue) hex$ = ba.ToHexString() for i = 0 to len(hex$) - 1 charValue = hex$.mid(i, 1) payload.push(charValue) next payloadSize = payloadSize + len(hex$) else if field.type = "uint8" then if len(fieldValue) = 0 then str = "00" else if len(fieldValue) = 1 then str = "0" + fieldValue else if len(fieldValue) > 2 then str = fieldValue else str = fieldValue endif for i = 0 to len(str) - 1 payload.push(mid(str, i + 1, 1)) next payloadSize = payloadSize + (len(str) / 2) else if field.type = "uint16" then ' disregard count for now. ' assume fieldValue is a single uint16 if len(fieldValue) = 0 then str = "0000" else if len(fieldValue) = 1 then str = "000" + fieldValue else if len(fieldValue) = 2 then str = "00" + fieldValue else if len(fieldValue) = 3 then str = "0" + fieldValue else if len(fieldValue) > 4 then stop else str = fieldValue endif for i = 0 to len(str) - 1 payload.push(mid(str, i + 1, 1)) next payloadSize = payloadSize + (len(str) / 2) else if field.type = "int8" if len(fieldValue) = 0 then str = "00" else if len(fieldValue) = 1 then str = "0" + fieldValue else if len(fieldValue) = 2 then str = fieldValue else if len(fieldValue) > 2 then stop endif for i = 0 to len(str) - 1 payload.push(mid(str, i + 1, 1)) next payloadSize = payloadSize + (len(str) / 2) else ' unsupposed field type stop endif end if endif next end if next end if endif request = CreateObject("roByteArray") request.push(functionBlockValue) request.push(functionValue) request.push(operatorValue) request.push(payloadSize) i = 0 while i < payload.count() firstSubstring = payload[i] secondSubString = payload[i + 1] substring = firstSubstring + secondSubString hexValueBA = CreateObject("roByteArray") hexValueBA.FromHexString(substring) request.push(hexValueBA[0]) i = i + 2 endwhile m.diagnostics.PrintDebug("BMAP Command byte array:") m.diagnostics.PrintDebug(request.ToHexString()) if not bmap.Send(request) then print "BMAP FAILURE" print bmap.GetFailureReason() bmap = m.CreateBMap(connector$, m.sign) if type(bmap) = "roBmap" then ok = bmap.Send(request) if not ok then print "BMAP FAILURE again" print bmap.GetFailureReason() stop endif endif end if endif end sub Function IsBmapField(parameterName as string) as boolean if parameterName = "port" or parameterName = "bmapFunctionBlock" or parameterName = "bmapFunction" or parameterName = "bmapOperator" then return false end if return true end function Function ShiftLeft(val as integer, shiftCount as integer) as integer for i% = 0 to shiftCount -1 val = val * 2 next return val end function Function ShiftRight(val as integer, shiftCount as integer) as integer for i% = 0 to shiftCount -1 val = val / 2 next return val end function Sub ExecuteSendWssCommand(parameters as object) gaa = GetGlobalAA() specifiedPort$ = parameters["port"].getCurrentParameterValue() runtimePort$ = m.GetRuntimeUsbConnector(specifiedPort$) if not gaa.usbConnectorNameToUsbSpec.DoesExist(runtimePort$) then m.diagnostics.PrintDebug("ExecuteSendWssCommand: speaker not found at connector: " + runtimePort$) return end if ' check to see whether or not the speaker specified in this command has been discovered if type(m.bose) <> "roAssociativeArray" or type(m.bose.speakers) <> "roArray" or m.bose.speakers.Count() = 0 or type(m.bose.speakers[0].address) <> "roString" then m.diagnostics.PrintDebug("ExecuteSendWssCommand: no speakers discovered yet") return end if speakerDiscovered = false speakerSpec$ = gaa.usbConnectorNameToUsbSpec[runtimePort$].hidOutputSpec for i% = 0 to m.bose.speakers.Count() - 1 speaker = m.bose.speakers[i%] if speaker.fid <> invalid then speakerFid$ = speaker.fid index = instr(1, speaker.fid, mid(speakerSpec$, 5)) if index = 1 then speakerDiscovered = true exit for end if end if next if not speakerDiscovered then m.diagnostics.PrintDebug("ExecuteSendWssCommand: speaker not discovered at connector: " + runtimePort$) return end if wssCommand = parameters.wssCommand wssCommandName$ = wssCommand.getCurrentParameterValue() ' command data from wssCommunicationSpec boseProduct = m.sign.boseProductsByConnector[specifiedPort$] wssCommunicationSpec = boseProduct.wssCommunicationSpec commands = wssCommunicationSpec.commands ' get structure for this specific command wssCommandTemplate = commands[wssCommandName$] ' start building command ' header - start with fixed parameters from the template or otherwise fixed header = { } header.resource = wssCommandTemplate.header.resource header.version = wssCommandTemplate.header.version header.msgtype = "REQUEST" header.token = "las9kdfjaslkjdbhgsdkKbldkfbvnl?adkfjnvlk" header.method = wssCommandTemplate.header.method header.device = "GUID" header.reqID = 36 ' body body = { } for each parameterName in wssCommandTemplate.body if wssCommandTemplate.body.DoesExist(parameterName) then wssParameter = wssCommandTemplate.body.Lookup(parameterName) if type(wssParameter) = "roAssociativeArray" and wssParameter.DoesExist("propertyType") and wssParameter.DoesExist("uniqueName") then ' command parameter that is set by the user wssParameterId = wssParameter.uniqueName if parameters.DoesExist(wssParameterId) then parameterValue = parameters[wssParameterId].GetCurrentParameterValue() if wssParameter.propertyType = "${INT}" then parameterValue = int(val(parameterValue)) end if body[parameterName] = parameterValue end if else '' Bose pending change - do anything here? I think this represents a hardcoded value. Maybe it is in fact an integer if that's what is needed. body[parameterName] = wssParameter end if end if next bsWebSocket = { } bsWebSocket.header = header bsWebSocket.body = body ' Send the REST command to initNode plugin aa = { fid : speakerFid$, bsWebSocket : FormatJson(bsWebSocket) } pluginMessageCmd = { EventType : "SEND_PLUGIN_MESSAGE", PluginName : "initNode", PluginMessage : "boseWebsocket", BoseWebsocketArray : aa } m.diagnostics.PrintDebug("WebSocket command:") m.diagnostics.PrintDebug(formatJson(pluginMessageCmd)) globalAA = getGlobalAA() globalAA.eddieDumpFile.sendLine("**** NEW WssCommand") dumpJsonBody(globalAA.eddieDumpFile, pluginMessageCmd) globalAA.eddieDumpFile.flush() m.msgPort.PostMessage(pluginMessageCmd) end sub ' m is bsp Sub ExecuteGpioOnCommand(zoneHSM as object, command$ as string, parameters as object) gpioNumberParameter = parameters["gpioNumber"] gpioNumber$ = gpioNumberParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("Turn on gpioNumber " + gpioNumber$) if IsControlPort(m.controlPort) then if gpioNumber$ = "-1" and m.sysinfo.deviceModel$ = "AU325" then m.controlPort.SetOutputState(1, 1) m.controlPort.SetOutputState(3, 1) m.controlPort.SetOutputState(5, 1) m.controlPort.SetOutputState(7, 1) else m.controlPort.SetOutputState(int(val(gpioNumber$)), 1) endif end if end sub Sub ExecuteGpioOffCommand(zoneHSM as object, command$ as string, parameters as object) gpioNumberParameter = parameters["gpioNumber"] gpioNumber$ = gpioNumberParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("Turn off gpioNumber " + gpioNumber$) if IsControlPort(m.controlPort) then if gpioNumber$ = "-1" and m.sysinfo.deviceModel$ = "AU325" then m.controlPort.SetOutputState(1, 0) m.controlPort.SetOutputState(3, 0) m.controlPort.SetOutputState(5, 0) m.controlPort.SetOutputState(7, 0) else m.controlPort.SetOutputState(int(val(gpioNumber$)), 0) endif end if end sub Sub ExecuteGpioSetStateCommand(zoneHSM as object, command$ as string, parameters as object) gpioStateParameter = parameters["gpioState"] gpioState$ = gpioStateParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("Set GPIO's to " + gpioState$) if IsControlPort(m.controlPort) then m.controlPort.SetWholeState(int(val(gpioState$))) end if end sub Sub ExecuteSerialSendStringCommand(zoneHSM as object, command$ as string, parameters as object) portParameter = parameters["port"] port$ = portParameter.GetCurrentParameterValue() port$ = zoneHSM.bsp.GetRuntimeUsbConnector(port$) serialStringParameter = parameters["message"] serialString$ = serialStringParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("sendSerialStringCommand " + serialString$ + " to port " + port$) if type(m.serial) = "roAssociativeArray" then serial = m.serial[port$] if type(serial) = "roSerialPort" or type(serial) = "roUsbTap" then serial.SendLine(serialString$) end if end if end sub Sub ExecuteSendSerialBlockCommand(zoneHSM as object, command$ as string, parameters as object) portParameter = parameters["port"] port$ = portParameter.GetCurrentParameterValue() port$ = zoneHSM.bsp.GetRuntimeUsbConnector(port$) serialStringParameter = parameters["serialString"] serialString$ = serialStringParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("sendSerialBlockCommand " + serialString$ + " to port " + port$) if type(m.serial) = "roAssociativeArray" then serial = m.serial[port$] if type(serial) = "roSerialPort" or type(serial) = "roUsbTap" then serial.SendBlock(serialString$) end if end if end sub Sub ExecuteSendSerialByteCommand(zoneHSM as object, command$ as string, parameters as object) portParameter = parameters["port"] port$ = portParameter.GetCurrentParameterValue() port$ = zoneHSM.bsp.GetRuntimeUsbConnector(port$) byteValueParameter = parameters["message"] byteValue$ = byteValueParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("sendSerialByteCommand " + byteValue$ + " to port " + port$) if type(m.serial) = "roAssociativeArray" then serial = m.serial[port$] if type(serial) = "roSerialPort" or type(serial) = "roUsbTap" then serial.SendByte(int(val(byteValue$))) end if end if end sub Sub ExecuteSendSerialBytesCommand(zoneHSM as object, command$ as string, parameters as object) portParameter = parameters["port"] port$ = portParameter.GetCurrentParameterValue() port$ = zoneHSM.bsp.GetRuntimeUsbConnector(port$) byteValueParameter = parameters["message"] byteValues$ = byteValueParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("sendSerialBytesCommand " + byteValues$ + " to port " + port$) if type(m.serial) = "roAssociativeArray" then serial = m.serial[port$] if type(serial) = "roSerialPort" or type(serial) = "roUsbTap" 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))) end if byteString$ = mid(byteString$, commaPosition + 1) end while end if end if end if end sub Sub ExecuteSendUDPCommand(zoneHSM as object, command$ as string, parameters as object) udpStringParameter = parameters["message"] udpString$ = udpStringParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("Send UDP command " + udpString$) m.udpSender.Send(udpString$) end sub Sub ExecuteSendUDPBytesCommand(zoneHSM as object, command$ as string, parameters as object) byteValueParameter = parameters["message"] byteValues$ = byteValueParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("sendUDPBytesCommand " + byteValues$) ba = CreateObject("roByteArray") byteString$ = StripLeadingSpaces(byteValues$) if len(byteString$) > 0 then commaPosition = -1 while commaPosition <> 0 commaPosition = instr(1, byteString$, ",") ba.push(val(byteString$)) byteString$ = mid(byteString$, commaPosition + 1) end while end if m.diagnostics.PrintDebug("Send UDP command " + ba.ToHexString()) m.udpSender.Send(ba) end sub Sub ExecuteSendProntoIRRemote(zoneHSM as object, command$ as string, parameters as object) irRemoteOutParameter = parameters["message"] irRemoteOut$ = irRemoteOutParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("Send Pronto IR Remote " + irRemoteOut$) if type(m.remote) <> "roIRRemote" then m.remote = CreateObject("roIRRemote") m.remote.SetPort(m.msgPort) end if if type(m.remote) = "roIRRemote" then m.remote.Send("PHC", irRemoteOut$) end if end sub Sub ExecuteSendIRRemoteCommand(zoneHSM as object, command$ as string, parameters as object) irRemoteOutParameter = parameters["message"] irRemoteOut$ = irRemoteOutParameter.GetCurrentParameterValue() if instr(1, irRemoteOut$, "b-") = 1 then irRemoteOut$ = mid(irRemoteOut$, 3) m.diagnostics.PrintDebug("Send Bose IR Remote " + irRemoteOut$) protocol$ = "Bose Sounddock" else m.diagnostics.PrintDebug("Send IR Remote " + irRemoteOut$) protocol$ = "NEC" end if if type(m.remote) <> "roIRRemote" then m.remote = CreateObject("roIRRemote") m.remote.SetPort(m.msgPort) end if if type(m.remote) = "roIRRemote" then irRemoteOut% = int(val(irRemoteOut$)) m.remote.Send(protocol$, irRemoteOut%) end if end sub Sub ExecuteSendBLC400OutputCommand(zoneHSM as object, command$ as string, parameters as object) controllerIndexParameter = parameters["controllerIndex"] controllerIndex$ = controllerIndexParameter.GetCurrentParameterValue() controllerIndex% = int(val(controllerIndex$)) if type(m.blcs[controllerIndex%]) = "roControlPort" then CHANNEL_CMD_INTENSITY% = &h1000 CHANNEL_CMD_BLINK% = &h1100 CHANNEL_CMD_BREATHE% = &h1200 CHANNEL_CMD_STROBE% = &h1300 CHANNEL_CMD_MARQUEE% = &h1400 ' blink mode enumeration BLINK_SPEED_SLOW% = &h20 BLINK_SPEED_MEDIUM% = &h21 BLINK_SPEED_FAST% = &h22 ' marquee sub commands MARQUEE_EXECUTE% = &h30 MARQUEE_ON_TIME% = &h31 MARQUEE_OFF_TIME% = &h32 MARQUEE_FADE_OUT% = &h33 MARQUEE_PLAYBACK% = &h34 MARQUEE_TRANSITION% = &h35 MARQUEE_INTENSITY% = &h36 ' marquee playback mode enumeration MARQUEE_PLAYBACK_LOOP% = &h40 MARQUEE_PLAYBACK_BOUNCE% = &h41 MARQUEE_PLAYBACK_ONCE% = &h42 MARQUEE_PLAYBACK_RANDOM% = &h43 ' marquee transition mode enumeration MARQUEE_TRANSITION_OFF% = &h50 MARQUEE_TRANSITION_FULL% = &h51 MARQUEE_TRANSITION_OVERLAP% = &h52 controlCmd = CreateObject("roArray", 4, false) effectParameter = parameters["effect"] effect$ = effectParameter.GetCurrentParameterValue() channelsParameter = parameters["channels"] channels$ = channelsParameter.GetCurrentParameterValue() channels% = int(val(channels$)) controlCmd[0] = channels% if effect$ = "intensity" then time% = GetIntegerParameterValue(parameters, "time", 0) intensity% = GetIntegerParameterValue(parameters, "intensity", 100) controlCmd[0] = CHANNEL_CMD_INTENSITY% or channels% controlCmd[1] = time% ' time in seconds for transition (zero for instantaneous) controlCmd[2] = intensity% ' target intensity controlCmd[3] = 0 ' unused m.diagnostics.PrintDebug("sendBLC400Output - intensity: time = " + stri(time%) + " intensity = " + stri(intensity%)) else if effect$ = "blink" then blinkRateParameter = parameters["blinkRate"] blinkRate$ = blinkRateParameter.GetCurrentParameterValue() if blinkRate$ = "fastblink" then blinkRate% = BLINK_SPEED_FAST% else if blinkRate$ = "mediumblink" then blinkRate% = BLINK_SPEED_MEDIUM% else blinkRate% = BLINK_SPEED_SLOW% end if controlCmd[0] = CHANNEL_CMD_BLINK% or channels% controlCmd[1] = blinkRate% ' blink mode controlCmd[2] = 100 ' intensity (0 = use current value) controlCmd[3] = 0 ' unused m.diagnostics.PrintDebug("sendBLC400Output - blink: blinkRate = " + blinkRate$) else if effect$ = "breathe" then time% = GetIntegerParameterValue(parameters, "time", 0) minimumIntensity% = GetIntegerParameterValue(parameters, "minimumIntensity", 0) maximumIntensity% = GetIntegerParameterValue(parameters, "maximumIntensity", 100) controlCmd[0] = CHANNEL_CMD_BREATHE% or channels% controlCmd[1] = time% ' time in seconds for change (zero for instantaneous) controlCmd[2] = minimumIntensity% ' min intensity (or rather starting intensity) controlCmd[3] = maximumIntensity% ' max intensity m.diagnostics.PrintDebug("sendBLC400Output - breathe: time = " + stri(time%) + " minimumIntensity = " + stri(minimumIntensity%) + " maximumIntensity = " + stri(maximumIntensity%)) else if effect$ = "strobe" then time% = GetIntegerParameterValue(parameters, "time", 0) intensity% = GetIntegerParameterValue(parameters, "intensity", 100) controlCmd[0] = CHANNEL_CMD_STROBE% or channels% controlCmd[1] = time% ' time in milliseconds for strobe controlCmd[2] = intensity% ' intensity (0 = use current value) controlCmd[3] = 0 ' unused m.diagnostics.PrintDebug("sendBLC400Output - strobe: time = " + stri(time%) + " intensity = " + stri(intensity%)) else if effect$ = "marquee" then lightOnTime% = GetIntegerParameterValue(parameters, "lightOnTime", 0) lightOffTime% = GetIntegerParameterValue(parameters, "lightOffTime", 0) transitionModeParameter = parameters["transitionMode"] transitionMode$ = transitionModeParameter.GetCurrentParameterValue() playbackModeParameter = parameters["playbackMode"] playbackMode$ = playbackModeParameter.GetCurrentParameterValue() if playbackMode$ = "loop" then playbackMode% = MARQUEE_PLAYBACK_LOOP% else if playbackMode$ = "backandforth" then playbackMode% = MARQUEE_PLAYBACK_BOUNCE% else if playbackMode$ = "playonce" then playbackMode% = MARQUEE_PLAYBACK_ONCE% else playbackMode% = MARQUEE_PLAYBACK_RANDOM% end if m.diagnostics.PrintDebug("sendBLC400Output - marquee: mode = " + playbackMode$) transitionMode% = MARQUEE_TRANSITION_OFF% if transitionMode$ = "hardonoff" then fadeOut% = 0 else fadeOut% = 1 if transitionMode$ = "smoothfulloverlap" then transitionMode% = MARQUEE_TRANSITION_FULL% else if transitionMode$ = "smoothpartialoverlap" transitionMode% = MARQUEE_TRANSITION_OVERLAP% end if end if controlCmd[0] = CHANNEL_CMD_MARQUEE% controlCmd[1] = MARQUEE_PLAYBACK% ' changing playback mode controlCmd[2] = playbackMode% ' playback mode controlCmd[3] = 0 ' unused m.blcs[controllerIndex%].SetOutputValues(controlCmd) controlCmd[0] = CHANNEL_CMD_MARQUEE% controlCmd[1] = MARQUEE_FADE_OUT% ' fadeOut controlCmd[2] = fadeOut% ' hard or soft controlCmd[3] = 0 ' unused m.blcs[controllerIndex%].SetOutputValues(controlCmd) controlCmd[0] = CHANNEL_CMD_MARQUEE% controlCmd[1] = MARQUEE_TRANSITION% controlCmd[2] = transitionMode% controlCmd[3] = 0 ' unused m.blcs[controllerIndex%].SetOutputValues(controlCmd) controlCmd[0] = CHANNEL_CMD_MARQUEE% controlCmd[1] = MARQUEE_ON_TIME% ' on time controlCmd[2] = lightOnTime% ' msec controlCmd[3] = 0 ' unused m.blcs[controllerIndex%].SetOutputValues(controlCmd) controlCmd[0] = CHANNEL_CMD_MARQUEE% controlCmd[1] = MARQUEE_OFF_TIME% ' off time controlCmd[2] = lightOffTime% ' msec controlCmd[3] = 0 ' unused m.blcs[controllerIndex%].SetOutputValues(controlCmd) controlCmd[0] = CHANNEL_CMD_MARQUEE% or channels% controlCmd[1] = MARQUEE_EXECUTE% ' marquee sub command controlCmd[2] = 0 ' unused controlCmd[3] = 0 ' unused end if m.blcs[controllerIndex%].SetOutputValues(controlCmd) end if end sub Sub ExecuteSendBPOutputCommand(zoneHSM as object, command$ as string, parameters as object) buttonPanelIndexParameter = parameters["buttonPanelIndex"] buttonPanelIndex$ = buttonPanelIndexParameter.GetCurrentParameterValue() buttonPanelIndex% = int(val(buttonPanelIndex$)) buttonNumberParameter = parameters["buttonNumber"] buttonNumber$ = buttonNumberParameter.GetCurrentParameterValue() actionParameter = parameters["action"] action$ = actionParameter.GetCurrentParameterValue() if type(m.bpOutput[buttonPanelIndex%]) = "roControlPort" then m.diagnostics.PrintDebug("Apply action " + action$ + " to BP button " + buttonNumber$) buttonNumber% = int(val(buttonNumber$)) if buttonNumber% = -1 then for i% = 0 to 10 if action$ = "on" then m.bpOutput[buttonPanelIndex%].SetOutputState(i%, 1) else if action$ = "off" then m.bpOutput[buttonPanelIndex%].SetOutputState(i%, 0) else if action$ = "fastBlink" then m.bpOutput[buttonPanelIndex%].SetOutputValue(i%, &h038e38c) else if action$ = "mediumBlink" then m.bpOutput[buttonPanelIndex%].SetOutputValue(i%, &h03f03e0) else if action$ = "slowBlink" then m.bpOutput[buttonPanelIndex%].SetOutputValue(i%, &h03ff800) end if next else if action$ = "on" then m.bpOutput[buttonPanelIndex%].SetOutputState(buttonNumber%, 1) else if action$ = "off" then m.bpOutput[buttonPanelIndex%].SetOutputState(buttonNumber%, 0) else if action$ = "fastBlink" then m.bpOutput[buttonPanelIndex%].SetOutputValue(buttonNumber%, &h038e38c) else if action$ = "mediumBlink" then m.bpOutput[buttonPanelIndex%].SetOutputValue(buttonNumber%, &h03f03e0) else if action$ = "slowBlink" then m.bpOutput[buttonPanelIndex%].SetOutputValue(buttonNumber%, &h03ff800) end if end if end if end sub Sub ExecuteSynchronizeCommand(zoneHSM as object, command$ as string, parameters as object) synchronizeKeywordParameter = parameters["message"] synchronizeKeyword$ = synchronizeKeywordParameter.GetCurrentParameterValue() if type(m.SyncManager) = "roSyncManager" then m.diagnostics.PrintDebug("Send synchronize command " + synchronizeKeyword$ + " using SyncManager.") syncManagerEvent = m.SyncManager.Synchronize(synchronizeKeyword$, 300) m.diagnostics.PrintDebug("@@@ Created syncManagerEvent with sync keyword: " + synchronizeKeyword$) zoneHSM.syncInfo = { } zoneHSM.syncInfo.SyncDomain = syncManagerEvent.GetDomain() zoneHSM.syncInfo.SyncId = syncManagerEvent.GetId() zoneHSM.syncInfo.SyncIsoTimestamp = syncManagerEvent.GetIsoTimestamp() else m.diagnostics.PrintDebug("Send synchronize command " + synchronizeKeyword$) m.udpSender.Send("pre-" + synchronizeKeyword$) if type(zoneHSM.preloadState) = "roAssociativeArray" and (zoneHSM.preloadedStateName$ <> zoneHSM.preloadState.name$) then ' currently only support preload / synchronizing with images and videos zoneHSM.preloadState.PreloadItem() end if sleep(300) ' m.udpSender.Send("ply-" + synchronizeKeyword$) if type(m.udpReceiver) = "roDatagramReceiver" then udpReceiverExists = true m.udpReceiver = 0 else udpReceiverExists = false end if m.WaitForSyncResponse(synchronizeKeyword$) if udpReceiverExists then m.udpReceiver = CreateObject("roDatagramReceiver", m.udpReceivePort) ' Set user data to distinguish between presentation udp messages and bootstrap udp messages m.udpReceiver.SetUserData("receiver") m.udpReceiver.SetPort(m.msgPort) end if end if end sub Sub ExecuteSendZoneMessageCommand(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("Execute sendZoneMessage command") zoneMessageParameter = parameters["message"] sendZoneMessageParameter$ = zoneMessageParameter.GetCurrentParameterValue() ' send ZoneMessage message zoneMessageCmd = { } zoneMessageCmd["EventType"] = "SEND_ZONE_MESSAGE" zoneMessageCmd["EventParameter"] = sendZoneMessageParameter$ m.msgPort.PostMessage(zoneMessageCmd) end sub Sub ExecuteSendPluginMessageCommand(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("Execute sendPluginMessage command") pluginName = parameters["pluginName"] pluginMessageParameter = parameters["message"] pluginName$ = pluginName.GetCurrentParameterValue() sendPluginMessageParameter$ = pluginMessageParameter.GetCurrentParameterValue() ' send ZoneMessage message pluginMessageCmd = { } pluginMessageCmd["EventType"] = "SEND_PLUGIN_MESSAGE" pluginMessageCmd["PluginName"] = pluginName$ pluginMessageCmd["PluginMessage"] = sendPluginMessageParameter$ m.msgPort.PostMessage(pluginMessageCmd) end sub Sub ExecuteResizeZoneCommand(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("Execute resizeZone command") zoneId$ = parameters["zoneId"].GetCurrentParameterValue() x% = int(val(parameters["x"].GetCurrentParameterValue())) y% = int(val(parameters["y"].GetCurrentParameterValue())) width% = int(val(parameters["width"].GetCurrentParameterValue())) height% = int(val(parameters["height"].GetCurrentParameterValue())) zone = m.GetZone(zoneId$) if type(zone) = "roAssociativeArray" then zone.rectangle = CreateScaledRectangle(x%, y%, width%, height%) if type(zone.videoPlayer) = "roVideoPlayer" then zone.videoPlayer.SetRectangle(zone.rectangle) end if if type(zone.mjpegvideoPlayer) = "roVideoPlayer" then zone.mjpegVideoPlayer.SetRectangle(zone.rectangle) end if if type(zone.imagePlayer) = "roImageWidget" then zone.imagePlayer.SetRectangle(zone.rectangle) end if if type(zone.displayedHtmlWidget) = "roHtmlWidget" then zone.displayedHtmlWidget.SetRectangle(zone.rectangle) end if if type(zone.canvasWidget) = "roCanvasWidget" then zone.canvasWidget.SetRectangle(zone.rectangle) end if if type(zone.widget) = "roHtmlWidget" or type(zone.widget) = "roTextWidget" then ok = zone.widget.SetRectangle(zone.rectangle) end if end if end sub Sub ExecuteHideZoneCommand(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("Execute hideZone command") zoneId$ = parameters["zoneId"].GetCurrentParameterValue() zoneHSM = m.GetZone(zoneId$) if type(zoneHSM) = "roAssociativeArray" then if not zoneHSM.isVisible then return end if if zoneHSM.type$ = "VideoOrImages" or zoneHSM.type$ = "Images" then if type(zoneHSM.videoPlayer) = "roVideoPlayer" then zoneHSM.videoPlayer.Hide() if type(zoneHSM.imagePlayer) <> "Invalid" then zoneHSM.imagePlayer.Hide() if type(zoneHSM.canvasWidget) <> "Invalid" then zoneHSM.canvasWidget.Hide() if type(zoneHSM.loadingHtmlWidget) <> "Invalid" then zoneHSM.loadingHtmlWidget.Hide() if type(zoneHSM.displayedHtmlWidget) <> "Invalid" then zoneHSM.displayedHtmlWidget.Hide() else if zoneHSM.type$ = "VideoOnly" then if type(zoneHSM.videoPlayer) = "roVideoPlayer" then zoneHSM.videoPlayer.Hide() else if zoneHSM.type$ = "Clock" then if type(zoneHSM.widget) <> "Invalid" then zoneHSM.widget.Hide() else if zoneHSM.type$ = "Ticker" then zoneHSM.widget.Hide() else if zoneHSM.type$ = "BackgroundImage" then end if zoneHSM.isVisible = false end if end sub Sub ExecuteShowZoneCommand(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("Execute showZone command") zoneId$ = parameters["zoneId"].GetCurrentParameterValue() zoneHSM = m.GetZone(zoneId$) if type(zoneHSM) = "roAssociativeArray" then if zoneHSM.isVisible then return end if if zoneHSM.type$ = "VideoOrImages" or zoneHSM.type$ = "Images" then if type(zoneHSM.videoPlayer) = "roVideoPlayer" then zoneHSM.videoPlayer.Show() if type(zoneHSM.imagePlayer) <> "Invalid" and not zoneHSM.imageHidden then zoneHSM.imagePlayer.Show() if type(zoneHSM.canvasWidget) <> "Invalid" and not zoneHSM.canvasHidden then zoneHSM.canvasWidget.Show() if type(zoneHSM.loadingHtmlWidget) <> "Invalid" and not zoneHSM.htmlHidden then zoneHSM.loadingHtmlWidget.Show() if type(zoneHSM.displayedHtmlWidget) <> "Invalid" and not zoneHSM.htmlHidden then zoneHSM.displayedHtmlWidget.Show() else if zoneHSM.type$ = "VideoOnly" then if type(zoneHSM.videoPlayer) = "roVideoPlayer" then zoneHSM.videoPlayer.Show() else if zoneHSM.type$ = "Clock" then zoneHSM.widget.Show() else if zoneHSM.type$ = "Ticker" then zoneHSM.widget.Show() else if zoneHSM.type$ = "BackgroundImage" then end if zoneHSM.isVisible = true end if end sub Sub ExecutePauseZonePlaybackCommand(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("Execute pauseZonePlayback command") zoneId$ = parameters["zoneId"].GetCurrentParameterValue() zoneHSM = m.GetZone(zoneId$) if type(zoneHSM) = "roAssociativeArray" then if type(zoneHSM.videoPlayer) = "roVideoPlayer" then zoneHSM.videoPlayer.Pause() endif if IsAudioPlayer(zoneHSM.audioPlayer) then zoneHSM.audioPlayer.Pause() endif endif end sub Sub ExecuteResumeZonePlaybackCommand(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("Execute resumeZonePlayback command") zoneId$ = parameters["zoneId"].GetCurrentParameterValue() zoneHSM = m.GetZone(zoneId$) if type(zoneHSM) = "roAssociativeArray" then if type(zoneHSM.videoPlayer) = "roVideoPlayer" then zoneHSM.videoPlayer.Resume() endif if IsAudioPlayer(zoneHSM.audioPlayer) then zoneHSM.audioPlayer.Resume() endif endif end sub Sub ExecuteInternalSynchronizeCommand(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("Execute internalSynchronize command") internalSyncParameter = parameters["message"] internalSyncParameter$ = internalSyncParameter.GetCurrentParameterValue() ' send InternalSyncPreload message internalSyncPreload = { } internalSyncPreload["EventType"] = "INTERNAL_SYNC_PRELOAD" internalSyncPreload["EventParameter"] = internalSyncParameter$ m.msgPort.PostMessage(internalSyncPreload) ' send InternalSyncMasterPreload message internalSyncMasterPreload = { } internalSyncMasterPreload["EventType"] = "INTERNAL_SYNC_MASTER_PRELOAD" internalSyncMasterPreload["EventParameter"] = internalSyncParameter$ m.msgPort.PostMessage(internalSyncMasterPreload) ' current state is zoneHSM.activeState activeState = zoneHSM.activeState if type(activeState) = "roAssociativeArray" then if type(activeState.internalSynchronizeEventsMaster) = "roAssociativeArray" then if type(activeState.internalSynchronizeEventsMaster[internalSyncParameter$]) = "roAssociativeArray" then transition = activeState.internalSynchronizeEventsMaster[internalSyncParameter$] nextState$ = transition.targetMediaState$ if nextState$ <> "" then zoneHSM.preloadState = zoneHSM.stateTable[nextState$] zoneHSM.preloadState.PreloadItem() end if end if end if end if end sub ' Send the CEC send hex string command through the specified video connector Sub ExecuteCecSendStringCommand(parameters as object) cecCommandParameter = parameters["cecString"] cecCommand$ = cecCommandParameter.GetCurrentParameterValue() videoConnector$ = getTextParameterFallbackToEmpty(parameters, "videoConnector") cecSubstituteSourceAddressParameter = parameters["cecSubstituteSourceAddress"] cecSubstituteSourceAddress$ = cecSubstituteSourceAddressParameter.GetCurrentParameterValue() if lcase(cecSubstituteSourceAddress$) = "true" then cecSubstituteSourceAddress = true else cecSubstituteSourceAddress = false end if m.diagnostics.PrintDebug("cecSendString " + cecCommand$ + " cecSubstituteSourceAddress " + cecSubstituteSourceAddress$ + " videoConnector " + videoConnector$) m.SendCecCommand(cecCommand$, cecSubstituteSourceAddress, videoConnector$) end sub ' Send the CEC philips set volume command through the specified video connector Sub ExecuteCecPhilipsSetVolumeCommand(parameters as object) volumeParameter = parameters["volume"] volume$ = volumeParameter.GetCurrentParameterValue() videoConnector$ = getTextParameterFallbackToEmpty(parameters, "videoConnector") m.diagnostics.PrintDebug("Set cec Philips volume to " + volume$ + " with connector " + videoConnector$) volume% = int(val(volume$)) m.CecPhilipsSetVolume(volume%, videoConnector$) end sub Sub ExecutePauseCommand(zoneHSM as object, command$ as string, parameters as object) pauseTimeParameter = parameters["pauseTime"] pauseTime$ = pauseTimeParameter.GetCurrentParameterValue() m.diagnostics.PrintDebug("Pause for " + pauseTime$ + " milliseconds") pauseTime% = int(val(pauseTime$)) sleep(pauseTime%) end sub Sub ExecuteSetVariableCommand(zoneHSM as object, command$ as string, parameters as object) variableNameParameter = parameters["variableName"] variableValueParameter = parameters["variableValue"] variableName$ = variableNameParameter.GetVariableName() variableValue$ = variableValueParameter.GetCurrentParameterValue() userVariable = m.GetUserVariable(variableName$) if type(userVariable) = "roAssociativeArray" then userVariable.SetCurrentValue(variableValue$, true) userVariablesChanged = { } userVariablesChanged["EventType"] = "USER_VARIABLES_UPDATED" m.msgPort.PostMessage(userVariablesChanged) ' Notify controlling devices to refresh m.SendUDPNotification("refresh") else m.diagnostics.PrintDebug("User variable " + variableName$ + " not found.") m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_USER_VARIABLE_NOT_FOUND, variableName$) end if end sub Sub ExecuteBeaconStartCommand(zoneHSM as object, command$ as string, parameters as object) nameParameter = parameters["beaconName"] name$ = nameParameter.GetCurrentParameterValue() m.btManager.StartBeacon(name$) end sub Sub ExecuteBeaconStopCommand(zoneHSM as object, command$ as string, parameters as object) nameParameter = parameters["beaconName"] name$ = nameParameter.GetCurrentParameterValue() m.btManager.StopBeacon(name$) end sub ' m is bsp Sub ExecuteCmd(zoneHSM as object, command$ as string, parameters as object) m.diagnostics.PrintDebug("ExecuteCmd " + command$) if command$ = "gpioOnCommand" then m.ExecuteGpioOnCommand(zoneHSM, command$, parameters) else if command$ = "gpioOffCommand" then m.ExecuteGpioOffCommand(zoneHSM, command$, parameters) else if command$ = "gpioSetStateCommand" then m.ExecuteGpioSetStateCommand(zoneHSM, command$, parameters) else if command$ = "sendWss" then m.ExecuteSendWssCommand(parameters) else if command$ = "sendBMap" then m.ExecuteSendBMapCommand(parameters) else if command$ = "sendBMapHex" then m.ExecuteSendBMapHexCommand(parameters) else if command$ = "setAllAudioOutputs" then m.ExecuteSetAllAudioOutputsCommand(parameters) else if command$ = "setAudioMode" then m.SetAudioMode(parameters) else if command$ = "muteAudioOutputs" then m.MuteAudioOutputs(true, parameters) else if command$ = "unmuteAudioOutputs" then m.MuteAudioOutputs(false, parameters) else if command$ = "setConnectorVolume" then m.SetConnectorVolume(parameters) else if command$ = "incrementConnectorVolume" then m.ChangeConnectorVolume(1, parameters) else if command$ = "decrementConnectorVolume" then m.ChangeConnectorVolume( - 1, parameters) else if command$ = "setZoneVolume" then m.SetZoneVolume(parameters) else if command$ = "incrementZoneVolume" then m.ChangeZoneVolume(1, parameters) else if command$ = "decrementZoneVolume" then m.ChangeZoneVolume( - 1, parameters) else if command$ = "setZoneChannelVolume" then m.SetZoneChannelVolume(parameters) else if command$ = "incrementZoneChannelVolume" then m.ChangeZoneChannelVolume(1, parameters) else if command$ = "decrementZoneChannelVolume" then m.ChangeZoneChannelVolume( - 1, parameters) else if command$ = "serialSendStringCommand" or command$ = "sendSerialStringCommand" or command$ = "serialSendString" then m.ExecuteSerialSendStringCommand(zoneHSM, command$, parameters) else if command$ = "sendSerialBlockCommand" then m.ExecuteSendSerialBlockCommand(zoneHSM, command$, parameters) else if command$ = "sendSerialByteCommand" then m.ExecuteSendSerialByteCommand(zoneHSM, command$, parameters) else if command$ = "sendSerialBytesCommand" then m.ExecuteSendSerialBytesCommand(zoneHSM, command$, parameters) else if command$ = "sendUDPCommand" then m.ExecuteSendUDPCommand(zoneHSM, command$, parameters) else if command$ = "sendUDPBytesCommand" then m.ExecuteSendUDPBytesCommand(zoneHSM, command$, parameters) else if command$ = "sendProntoIRRemote" then m.ExecuteSendProntoIRRemote(zoneHSM, command$, parameters) else if command$ = "sendIRRemote" then m.ExecuteSendIRRemoteCommand(zoneHSM, command$, parameters) else if command$ = "sendBLC400Output" then m.ExecuteSendBLC400OutputCommand(zoneHSM, command$, parameters) else if command$ = "sendBPOutput" then m.ExecuteSendBPOutputCommand(zoneHSM, command$, parameters) else if command$ = "synchronize" then m.ExecuteSynchronizeCommand(zoneHSM, command$, parameters) else if command$ = "sendZoneMessage" then m.ExecuteSendZoneMessageCommand(zoneHSM, command$, parameters) else if command$ = "sendPluginMessage" then m.ExecuteSendPluginMessageCommand(zoneHSM, command$, parameters) else if command$ = "resizeZone" then m.ExecuteResizeZoneCommand(zoneHSM, command$, parameters) else if command$ = "hideZone" then m.ExecuteHideZoneCommand(zoneHSM, command$, parameters) else if command$ = "showZone" then m.ExecuteShowZoneCommand(zoneHSM, command$, parameters) else if command$ = "pauseZonePlayback" then m.ExecutePauseZonePlaybackCommand(zoneHSM, command$, parameters) else if command$ = "resumeZonePlayback" then m.ExecuteResumeZonePlaybackCommand(zoneHSM, command$, parameters) else if command$ = "internalSynchronize" then m.ExecuteInternalSynchronizeCommand(zoneHSM, command$, parameters) else if command$ = "reboot" then RebootSystem() else if command$ = "cecDisplayOn" then m.CecDisplayOn(parameters) else if command$ = "cecDisplayOff" then m.CecDisplayOff(parameters) else if command$ = "cecSetSourceToBrightSign" then m.CecSetSourceToBrightSign(parameters) else if command$ = "cecSendString" then m.ExecuteCecSendStringCommand(parameters) else if command$ = "cecPhilipsSetVolume" then m.ExecuteCecPhilipsSetVolumeCommand(parameters) else if command$ = "pauseVideo" then m.PauseVideo(zoneHSM) else if command$ = "resumeVideo" then m.ResumeVideo(zoneHSM) else if command$ = "enablePowerSaveMode" then m.SetPowerSaveMode(true) else if command$ = "disablePowerSaveMode" then m.SetPowerSaveMode(false) else if command$ = "pause" then m.ExecutePauseCommand(zoneHSM, command$, parameters) else if command$ = "setVariable" then m.ExecuteSetVariableCommand(zoneHSM, command$, parameters) else if command$ = "incrementVariable" then m.ChangeUserVariableValue(parameters, 1) else if command$ = "decrementVariable" then m.ChangeUserVariableValue(parameters, - 1) else if command$ = "resetVariable" then m.ResetVariable(parameters) else if command$ = "resetVariables" then m.ResetVariables() else if command$ = "generateSessionGuid" then m.GenerateSessionGuid() else if command$ = "clearSessionGuid" then m.ClearSessionGuid() else if command$ = "configureAudioResources" then zoneHSM.ConfigureAudioResources() else if command$ = "updateDataFeed" then m.UpdateDataFeed(parameters) else if command$ = "beaconStart" then m.ExecuteBeaconStartCommand(zoneHSM, command$, parameters) else if command$ = "beaconStop" then m.ExecuteBeaconStopCommand(zoneHSM, command$, parameters) end if end sub Sub ResetVariable(parameters as object) variableNameParameter = parameters["variableName"] variableName$ = variableNameParameter.GetVariableName() userVariable = m.GetUserVariable(variableName$) if type(userVariable) = "roAssociativeArray" then userVariable.Reset(true) else m.diagnostics.PrintDebug("User variable " + variableName$ + " not found.") m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_USER_VARIABLE_NOT_FOUND, variableName$) end if end sub Sub ChangeUserVariableValue(parameters as object, delta% as integer) variableNameParameter = parameters["variableName"] variableName$ = variableNameParameter.GetVariableName() userVariable = m.GetUserVariable(variableName$) if type(userVariable) = "roAssociativeArray" then currentValue% = val(userVariable.GetCurrentValue()) currentValue% = currentValue% + delta% userVariable.SetCurrentValue(StripLeadingSpaces(stri(currentValue%)), true) else m.diagnostics.PrintDebug("User variable " + variableName$ + " not found.") m.logging.WriteDiagnosticLogEntry(m.diagnosticCodes.EVENT_USER_VARIABLE_NOT_FOUND, variableName$) end if end sub Function STTopEventHandler(event as object, stateData as object) as object stateData.nextState = invalid return "IGNORED" end function Function GetPoolFilePath(assetPoolFiles as object, fileName$ as string) as string if type(assetPoolFiles) = "roAssetPoolFiles" then return assetPoolFiles.GetPoolFilePath(fileName$) else return fileName$ end if end function 'endregion 'region Logging REM ******************************************************* REM ******************************************************* REM *************** ******************** REM *************** LOGGING OBJECT ******************** REM *************** ******************** REM ******************************************************* REM ******************************************************* REM REM construct a new logging BrightScript object REM Function newLogging() as object logging = { } logging.bsp = m logging.msgPort = m.msgPort logging.systemTime = m.systemTime logging.diagnostics = m.diagnostics logging.SetSystemInfo = SetSystemInfo 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.WriteStateLogEntry = WriteStateLogEntry logging.WriteDiagnosticLogEntry = WriteDiagnosticLogEntry logging.WriteDiagnosticLogEntryForForceLogUpload = WriteDiagnosticLogEntryForForceLogUpload logging.WriteVariableLogEntry = WriteVariableLogEntry logging.PushLogFile = PushLogFile logging.CutoverLogFile = CutoverLogFile logging.HandleTimerEvent = HandleLoggingTimerEvent logging.PushLogFilesOnBoot = PushLogFilesOnBoot logging.OpenOrCreateCurrentLog = OpenOrCreateCurrentLog logging.DeleteExpiredFiles = DeleteExpiredFiles logging.DeleteOlderFiles = DeleteOlderFiles logging.DeleteLogFiles = DeleteLogFiles logging.DeleteAllLogFiles = DeleteAllLogFiles logging.GetLogFiles = GetLogFiles logging.CopyAllLogFiles = CopyAllLogFiles logging.CopyLogFiles = CopyLogFiles logging.FlushLogFile = FlushLogFile logging.UpdateLogCounter = UpdateLogCounter logging.PrepareAddVariableDataToLog = PrepareAddVariableDataToLog logging.AddVariableDataToLog = AddVariableDataToLog logging.CleanupPostAddVariableDataToLog = CleanupPostAddVariableDataToLog 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.stateLoggingEnabled = false logging.variableLoggingEnabled = false logging.uploadLogFilesAtBoot = false logging.uploadLogFilesAtSpecificTime = false logging.inErrorState = false logging.uploadLogFilesTime% = 0 logging.uploadLogFilesInterval% = invalid logging.useDate = logging.systemTime.IsValid() return logging end function Function UpdateLogCounter(logCounter$ as string, maxValue% as integer, numDigits% as integer, writeToRegistry as boolean) as string logCounter% = val(logCounter$) logCounter% = logCounter% + 1 if logCounter% > maxValue% then logCounter% = 0 end if logCounter$ = StripLeadingSpaces(stri(logCounter%)) while len(logCounter$) < numDigits% logCounter$ = "0" + logCounter$ end while if writeToRegistry then WriteRegistrySetting("lc", logCounter$) GetGlobalAA().registrySettings.logCounter$ = logCounter$ else WriteAsciiFile("logCounter.txt", logCounter$) end if return logCounter$ end function Function CreateLogFile() as object if not m.useDate then ' don't use date for file name, use log counter logCounter$ = ReadAsciiFile("logCounter.txt") if logCounter$ = "" then logCounter$ = "000000" end if ' this pattern is used in PushLogFile to check serial number localFileName$ = "BrightSignLog." + m.deviceUniqueID$ + "-" + logCounter$ + ".log" fileNameLogCounter$ = logCounter$ logCounter$ = m.UpdateLogCounter(logCounter$, 999999, 6, false) else ' use date for file name logCounter$ = GetGlobalAA().registrySettings.logCounter$ dtLocal = m.systemTime.GetLocalDateTime() year$ = Right(stri(dtLocal.GetYear()), 2) month$ = StripLeadingSpaces(stri(dtLocal.GetMonth())) if len(month$) = 1 then month$ = "0" + month$ end if day$ = StripLeadingSpaces(stri(dtLocal.GetDay())) if len(day$) = 1 then day$ = "0" + day$ end if dateString$ = year$ + month$ + day$ logDate$ = GetGlobalAA().registrySettings.logDate$ if logDate$ = "" or logCounter$ = "" then logCounter$ = "000" else if logDate$ <> dateString$ then logCounter$ = "000" end if logDate$ = dateString$ ' this pattern is used in PushLogFile to check serial number localFileName$ = "BrightSign" + "Log." + m.deviceUniqueID$ + "-" + dateString$ + logCounter$ + ".log" WriteRegistrySetting("ld", logDate$) GetGlobalAA().registrySettings.logDate$ = logDate$ logCounter$ = m.UpdateLogCounter(logCounter$, 999, 3, true) end if fileName$ = "currentLog/" + localFileName$ logFile = CreateObject("roCreateFile", fileName$) m.diagnostics.PrintDebug("Create new log file " + localFileName$) t$ = chr(9) ' version header$ = "BrightSignLogVersion" + t$ + "4" logFile.SendLine(header$) ' serial number header$ = "SerialNumber" + t$ + m.deviceUniqueID$ logFile.SendLine(header$) ' log counter if not m.useDate then counterInHeader$ = "LogCounter" + t$ + fileNameLogCounter$ logFile.SendLine(counterInHeader$) end if ' group id if type(m.networking) = "roAssociativeArray" then if type(m.networking.currentSync) = "roSyncSpec" then header$ = "Account" + t$ + GetActiveSyncSpecSettings().account logFile.SendLine(header$) header$ = "Group" + t$ + GetActiveSettings().group logFile.SendLine(header$) end if end if ' 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$) end if ' 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$ end if 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$) end if next end sub Sub MoveCurrentLog() listOfPendingLogFiles = MatchFiles("/currentLog", "*") if listOfPendingLogFiles.count() > 0 then openedDB = m.PrepareAddVariableDataToLog() endif for each file in listOfPendingLogFiles sourceFilePath$ = "currentLog/" + file m.AddVariableDataToLog(sourceFilePath$) destinationFilePath$ = "logs/" + file CopyFile(sourceFilePath$, destinationFilePath$) DeleteFile(sourceFilePath$) next if listOfPendingLogFiles.count() > 0 then m.CleanupPostAddVariableDataToLog(openedDB) endif end sub Function PrepareAddVariableDataToLog() as boolean openedDB = false if m.variableLoggingEnabled then openedDB = false if not m.bsp.variablesDBExists then m.bsp.ReadVariablesDB("") openedDB = true end if endif return openedDB end function Sub CleanupPostAddVariableDataToLog(openedDB as boolean) if openedDB then m.bsp.userVariablesDB = invalid end if end sub Sub AddVariableDataToLog(filePath$ as string) if m.variableLoggingEnabled then if m.bsp.variablesDBExists then logFile = CreateObject("roAppendFile", filePath$) if type(logFile) <> "roAppendFile" then return end if timestamp$ = m.systemTime.GetLocalDateTime().GetString() sectionNames = m.bsp.GetDBSectionNames() for each sectionName in sectionNames m.WriteVariableLogEntry(logFile, timestamp$, "Section", sectionName, "", "") sectionId% = m.bsp.GetDBSectionId(sectionName) if sectionId% > 0 then categoryNames = m.bsp.GetDBCategoryNames(sectionName) for each categoryName in categoryNames m.WriteVariableLogEntry(logFile, timestamp$, "Category", categoryName, "", "") userVariablesList = m.bsp.GetUserVariablesGivenCategory(sectionName, false, categoryName, false) for each userVariable in userVariablesList m.WriteVariableLogEntry(logFile, timestamp$, "Variable", userVariable.name$, userVariable.currentValue$, userVariable.defaultValue$) next next end if next logFile.Flush() end if end if end sub Sub WriteVariableLogEntry(logFile as object, timeStamp$ as string, entityType$ as string, name$ as string, currentValue$ as string, defaultValue$ as string) if not m.variableLoggingEnabled then return if type(logFile) <> "roAppendFile" then return t$ = chr(9) logFile.SendLine("L=u" + t$ + "T=" + timestamp$ + t$ + "E=" + entityType$ + t$ + "N=" + name$ + t$ + "C=" + currentValue$ + t$ + "D=" + defaultValue$) end sub Sub InitializeLogging(playbackLoggingEnabled as boolean, eventLoggingEnabled as boolean, stateLoggingEnabled as boolean, diagnosticLoggingEnabled as boolean, variableLoggingEnabled as boolean, uploadLogFilesAtBoot as boolean, uploadLogFilesAtSpecificTime as boolean, uploadLogFilesTime% as integer) m.loggingEnabled = playbackLoggingEnabled or eventLoggingEnabled or stateLoggingEnabled or diagnosticLoggingEnabled or variableLoggingEnabled if m.loggingEnabled then CreateDirectory("logs") CreateDirectory("currentLog") CreateDirectory("archivedLogs") CreateDirectory("failedLogs") end if m.DeleteExpiredFiles() m.playbackLoggingEnabled = playbackLoggingEnabled m.eventLoggingEnabled = eventLoggingEnabled m.stateLoggingEnabled = stateLoggingEnabled m.diagnosticLoggingEnabled = diagnosticLoggingEnabled m.variableLoggingEnabled = variableLoggingEnabled m.uploadLogFilesAtBoot = uploadLogFilesAtBoot m.uploadLogFilesAtSpecificTime = uploadLogFilesAtSpecificTime m.uploadLogFilesTime% = uploadLogFilesTime% m.uploadLogsEnabled = uploadLogFilesAtBoot or uploadLogFilesAtSpecificTime if m.uploadLogFilesAtBoot then m.PushLogFilesOnBoot() end if m.MoveExpiredCurrentLog() if m.loggingEnabled then m.OpenOrCreateCurrentLog() m.InitializeCutoverTimer() end sub Sub ReinitializeLogging(playbackLoggingEnabled as boolean, eventLoggingEnabled as boolean, stateLoggingEnabled as boolean, diagnosticLoggingEnabled as boolean, variableLoggingEnabled as boolean, uploadLogFilesAtBoot as boolean, uploadLogFilesAtSpecificTime as boolean, uploadLogFilesTime% as integer) if playbackLoggingEnabled = m.playbackLoggingEnabled and eventLoggingEnabled = m.eventLoggingEnabled and stateLoggingEnabled = m.stateLoggingEnabled and diagnosticLoggingEnabled = m.diagnosticLoggingEnabled and variableLoggingEnabled = m.variableLoggingEnabled and uploadLogFilesAtBoot = m.uploadLogFilesAtBoot and uploadLogFilesAtSpecificTime = m.uploadLogFilesAtSpecificTime and uploadLogFilesTime% = m.uploadLogFilesTime% then return m.loggingEnabled = playbackLoggingEnabled or eventLoggingEnabled or stateLoggingEnabled or diagnosticLoggingEnabled or variableLoggingEnabled if m.loggingEnabled then CreateDirectory("logs") CreateDirectory("currentLog") CreateDirectory("archivedLogs") CreateDirectory("failedLogs") end if if type(m.cutoverTimer) = "roTimer" then m.cutoverTimer.Stop() m.cutoverTimer = invalid end if m.playbackLoggingEnabled = playbackLoggingEnabled m.eventLoggingEnabled = eventLoggingEnabled m.stateLoggingEnabled = stateLoggingEnabled m.diagnosticLoggingEnabled = diagnosticLoggingEnabled m.variableLoggingEnabled = variableLoggingEnabled m.uploadLogFilesAtBoot = uploadLogFilesAtBoot m.uploadLogFilesAtSpecificTime = uploadLogFilesAtSpecificTime m.uploadLogFilesTime% = uploadLogFilesTime% m.uploadLogsEnabled = uploadLogFilesAtBoot or uploadLogFilesAtSpecificTime if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" and m.loggingEnabled then m.OpenOrCreateCurrentLog() end if m.InitializeCutoverTimer() end sub Sub InitializeCutoverTimer() if type(m.cutoverTimer) = "roTimer" then m.cutoverTimer.stop() end if m.cutoverTimer = CreateObject("roTimer") m.cutoverTimer.SetPort(m.msgPort) if type(m.uploadLogFilesInterval%) = "Integer" or type(m.uploadLogFilesInterval%) = "roInt" then m.cutoverTimer.SetElapsed(m.uploadLogFilesInterval% * 60, 0) else if m.uploadLogFilesAtSpecificTime then hour% = m.uploadLogFilesTime% / 60 minute% = m.uploadLogFilesTime% - (hour% * 60) else hour% = 0 minute% = 0 end if ' randomize upload log time +- 30 mins ' Rnd(61) returns random integer in [1, 61], so randomDiff% is in [-30, 30] randomDiff% = Rnd(61) - 31 ' minute% is in [0, 59], so newMinute% is in [-30, 89] newMinute% = minute% + randomDiff% if newMinute% <0 then if hour% = 0 then randomHour% = 23 else randomHour% = hour% - 1 end if randomMinute% = newMinute% + 60 else if newMinute% >59 then if hour% = 23 then randomHour% = 0 else randomHour% = hour% + 1 end if randomMinute% = newMinute% - 60 else randomMinute% = newMinute% randomHour% = hour% end if m.cutoverTimer.SetDate( - 1, - 1, - 1) m.cutoverTimer.SetTime(randomHour%, randomMinute%, 0) end if m.cutoverTimer.Start() end sub Function CopyAllLogFiles(storagePath$ as string) as boolean if type(m.logFile) = "roCreateFile" or type(m.logFile) = "roAppendFile" then m.logFile.Flush() end if ok = m.CopyLogFiles(storagePath$, "currentLog") if not ok then return ok end if ok = m.CopyLogFiles(storagePath$, "logs") if not ok then return ok end if ok = m.CopyLogFiles(storagePath$, "failedLogs") if not ok then return ok end if ok = m.CopyLogFiles(storagePath$, "archivedLogs") return ok end function Function CopyLogFiles(storagePath$ as string, folderName$ as string) listOfLogFiles = MatchFiles("/" + folderName$, "*") for each file in listOfLogFiles sourceFilePath$ = "/" + folderName$ + "/" + file destinationFilePath$ = storagePath$ + file ok = CopyFile(sourceFilePath$, destinationFilePath$) if not ok then return ok end if next return true end function Sub DeleteAllLogFiles() ' close the current log file before deleting if type(m.logFile) = "roCreateFile" or type(m.logFile) = "roAppendFile" then m.logFile.Flush() m.logFile = invalid end if m.DeleteLogFiles("currentLog") m.DeleteLogFiles("logs") m.DeleteLogFiles("failedLogs") m.DeleteLogFiles("archivedLogs") end sub Sub DeleteLogFiles(folderName$ as string) listOfLogFiles = MatchFiles("/" + folderName$, "*") for each file in listOfLogFiles fullFilePath$ = "/" + folderName$ + "/" + file DeleteFile(fullFilePath$) next end sub Sub DeleteExpiredFiles() if m.useDate then ' delete any files that are more than 30 days old dtExpired = m.systemTime.GetLocalDateTime() dtExpired.SubtractSeconds(60 * 60 * 24 * 30) ' look in the following folders ' logs ' failedLogs ' archivedLogs m.DeleteOlderFiles("logs", dtExpired) m.DeleteOlderFiles("failedLogs", dtExpired) m.DeleteOlderFiles("archivedLogs", dtExpired) else MAX_FILES_TO_KEEP = 60 ' get a list of all log files logFiles = CreateObject("roArray", 1, true) m.GetLogFiles("logs", logFiles) m.GetLogFiles("failedLogs", logFiles) m.GetLogFiles("archivedLogs", logFiles) ' sort them in ascending order sortedIndices = CreateObject("roArray", 1, true) SortItems(logFiles, sortedIndices) ' if the count is > than the number to keep, delete the first n in the list while sortedIndices.Count() > MAX_FILES_TO_KEEP fullFilePath$ = logFiles[sortedIndices[0]].fullFilePath$ m.diagnostics.PrintDebug("Delete log file " + fullFilePath$) DeleteFile(fullFilePath$) sortedIndices.shift() end while end if end sub ' sorted indices is an array that can grow and has no values on entry ' items is an array of associative arrays Sub SortItems(logFiles as object, sortedIndices as object) ' initialize array with indices. for i% = 0 to logFiles.Count() - 1 sortedIndices[i%] = i% next numItemsToSort% = logFiles.Count() for i% = numItemsToSort% - 1 to 1 step -1 for j% = 0 to i% - 1 index0% = sortedIndices[j%] logCounter0% = logFiles[index0%].counter% index1% = sortedIndices[j% + 1] logCounter1% = logFiles[index1%].counter% if logCounter0% > logCounter1% then k% = sortedIndices[j%] sortedIndices[j%] = sortedIndices[j% + 1] sortedIndices[j% + 1] = k% end if next next end sub Sub GetLogFiles(folderName$ as string, logFiles as object) listOfLogFiles = MatchFiles("/" + folderName$, "*") for each file in listOfLogFiles logFile = { } logFile.counter% = int(val(left(right(file, 7), 3))) logFile.fullFilePath$ = "/" + folderName$ + "/" + file logFiles.push(logFile) next 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$) end if 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 WriteStateLogEntry(stateMachine as object, stateName$ as string, stateType$ as string) if not m.stateLoggingEnabled then return if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" then return timestamp$ = m.systemTime.GetLocalDateTime().GetString() t$ = chr(9) if type(stateMachine.lastStateName$) = "roString" then lastStateName$ = stateMachine.lastStateName$ lastEventType$ = stateMachine.lastEventType$ lastEventData$ = stateMachine.lastEventData$ else lastStateName$ = "" lastEventType$ = "" lastEventData$ = "" end if m.logFile.SendLine("L=s" + t$ + "S=" + stateName$ + t$ + "T=" + timestamp$ + t$ + "Y=" + stateType$ + t$ + "LS=" + lastStateName$ + t$ + "LE=" + lastEventType$ + t$ + "LD=" + lastEventData$) m.logFile.AsyncFlush() end sub Sub WriteEventLogEntry(stateMachine as object, stateName$ as string, eventType$ as string, eventData$ as string, eventActedOn$ as string) if not (m.eventLoggingEnabled or m.stateLoggingEnabled) then return if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" then return timestamp$ = m.systemTime.GetLocalDateTime().GetString() if eventActedOn$ = "1" then stateMachine.lastStateName$ = stateName$ stateMachine.lastEventType$ = eventType$ stateMachine.lastEventData$ = eventData$ end if if m.eventLoggingEnabled then t$ = chr(9) logStr$ = "L=e" + t$ + "S=" + stateName$ + t$ + "T=" + timestamp$ + t$ + "E=" + eventType$ + t$ + "D=" + eventData$ + t$ + "A=" + eventActedOn$ if isString(m.bsp.sysInfo.sessionGuid$) and m.bsp.sysInfo.sessionGuid$ <> "" then logStr$ = logStr$ + t$ + ("I=" + m.bsp.sysInfo.sessionGuid$) end if m.logFile.SendLine(logStr$) m.logFile.AsyncFlush() end if 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 ' If log file size is larger than 5MB, rotate file if m.logFile.CurrentPosition() >= 5242880 then m.CutoverLogFile(false) if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" then return end if 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 ' This function is only called in the case of force log upload Sub WriteDiagnosticLogEntryForForceLogUpload(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.CutoverLogFile(true) end sub Sub PushLogFile(forceUpload as boolean) if type(m.networking) <> "roAssociativeArray" then return 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 ' check if serial number in log file matches device serial number ' this check is to prevent when SD card from one working player gets inserted to another new player start = file.instr(".") + 1 length = file.instr("-") - start logSerial = file.mid(start, length) fullFilePath$ = m.uploadLogFailedFolder + "/" + file if logSerial = m.deviceUniqueID$ then target$ = m.uploadLogFolder + "/" + file ok = MoveFile(fullFilePath$, target$) else ok = DeleteFile(fullFilePath$) end if next m.networking.UploadLogFiles() end sub Sub PushLogFilesOnBoot() m.MoveCurrentLog() m.PushLogFile(false) end sub Sub HandleLoggingTimerEvent() m.CutoverLogFile(false) m.cutoverTimer.Start() end sub Sub CutoverLogFile(forceUpload as boolean) if type(m.logFile) <> "roCreateFile" and type(m.logFile) <> "roAppendFile" or m.inErrorState then return m.logFile.Flush() m.MoveCurrentLog() m.logFile = m.CreateLogFile() if forceUpload or m.uploadLogFilesAtSpecificTime then m.PushLogFile(forceUpload) end if m.DeleteExpiredFiles() 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 end if next m.logFile = m.CreateLogFile() end sub 'endregion ' Transform invalid value to "invalid" for purpose of avoiding runtime errors when printing variable Function helper_ValidateInvalidPrint(varToValidate as object) as object if varToValidate = invalid then return "invalid" else return varToValidate end if end function 'region Hierarchical State Machine ' ************************************************* ' ' Hierarchical State Machine Implementation ' ' ************************************************* Function newHSM() as object HSM = { } HSM.Initialize = HSMInitialize HSM.Constructor = HSMConstructor HSM.Dispatch = HSMDispatch HSM.IsIn = HSMIsIn HSM.InitialPseudostateHandler = invalid HSM.ConstructorHandler = invalid HSM.newHState = newHState HSM.topState = invalid HSM.activeState = invalid return HSM end function Sub HSMConstructor() if type(m.ConstructorHandler) = invalid then stop m.ConstructorHandler() end sub Sub HSMInitialize() ' there is definitely some confusion here about the usage of both activeState and m.activeState stateData = { } ' empty event used to get super states emptyEvent = { } emptyEvent["EventType"] = "EMPTY_SIGNAL" ' entry event entryEvent = { } entryEvent["EventType"] = "ENTRY_SIGNAL" ' init event initEvent = { } initEvent["EventType"] = "INIT_SIGNAL" ' execute initial transition m.activeState = m.InitialPseudoStateHandler() ' if there is no activeState, the playlist is empty if type(m.activeState) <> "roAssociativeArray" return activeState = m.activeState ' start at the top state if type(m.topState) <> "roAssociativeArray" then stop sourceState = m.topState while true entryStates = CreateObject("roArray", 4, true) entryStateIndex% = 0 entryStates[0] = activeState ' target of the initial transition status$ = m.activeState.HStateEventHandler(emptyEvent, stateData) ' send an empty event to get the super state activeState = stateData.nextState m.activeState = stateData.nextState while (activeState.id$ <> sourceState.id$) ' walk up the tree until the current source state is hit entryStateIndex% = entryStateIndex% + 1 entryStates[entryStateIndex%] = activeState status$ = m.activeState.HStateEventHandler(emptyEvent, stateData) activeState = stateData.nextState m.activeState = stateData.nextState end while ' activeState = entryStates[0] ' restore the target of the initial transition while (entryStateIndex% >= 0) ' retrace the entry path in reverse (desired) order entryState = entryStates[entryStateIndex%] status$ = entryState.HStateEventHandler(entryEvent, stateData) entryStateIndex% = entryStateIndex% - 1 end while sourceState = entryStates[0] ' new source state is the current state status$ = sourceState.HStateEventHandler(initEvent, stateData) if status$ <> "TRANSITION" then m.activeState = sourceState return end if activeState = stateData.nextState m.activeState = stateData.nextState end while end sub Sub HSMDispatch(event as object) ' if there is no activeState, the playlist is empty if type(m.activeState) <> "roAssociativeArray" return stateData = { } ' empty event used to get super states emptyEvent = { } emptyEvent["EventType"] = "EMPTY_SIGNAL" ' entry event entryEvent = { } entryEvent["EventType"] = "ENTRY_SIGNAL" ' exit event exitEvent = { } exitEvent["EventType"] = "EXIT_SIGNAL" ' init event initEvent = { } initEvent["EventType"] = "INIT_SIGNAL" t = m.activeState ' save the current state status$ = "SUPER" while (status$ = "SUPER") ' process the event hierarchically s = m.activeState status$ = s.HStateEventHandler(event, stateData) m.activeState = stateData.nextState end while if (status$ = "TRANSITION") path = CreateObject("roArray", 4, true) path[0] = m.activeState ' save the target of the transition path[1] = t ' save the current state while (t.id$ <> s.id$) ' exit from the current state to the transition s status$ = t.HStateEventHandler(exitEvent, stateData) if status$ = "HANDLED" then status$ = t.HStateEventHandler(emptyEvent, stateData) end if t = stateData.nextState end while t = path[0] ' target of the transition ' s is the source of the transition if (s.id$ = t.id$) then ' check source == target (transition to self) status$ = s.HStateEventHandler(exitEvent, stateData) ' exit the source ip = 0 else status$ = t.HStateEventHandler(emptyEvent, stateData) ' superstate of target t = stateData.nextState if (s.id$ = t.id$) then ' check source == target->super ip = 0 ' enter the target else status$ = s.HStateEventHandler(emptyEvent, stateData) ' superstate of source if (stateData.nextState.id$ = t.id$) then ' check source->super == target->super status$ = s.HStateEventHandler(exitEvent, stateData) ' exit the source ip = 0 ' enter the target else if (stateData.nextState.id$ = path[0].id$) then ' check source->super == target status$ = s.HStateEventHandler(exitEvent, stateData) ' exit the source else ' check rest of source == target->super->super and store the entry path along the way iq = 0 ' indicate LCA not found ip = 1 ' enter target and its superstate path[1] = t ' save the superstate of the target t = stateData.nextState ' save source->super ' get target->super->super status$ = path[1].HStateEventHandler(emptyEvent, stateData) while (status$ = "SUPER") ip = ip + 1 path[ip] = stateData.nextState ' store the entry path if (stateData.nextState.id$ = s.id$) then ' is it the source? iq = 1 ' indicate that LCA found ip = ip - 1 ' do not enter the source status$ = "HANDLED" ' terminate the loop else ' it is not the source; keep going up status$ = stateData.nextState.HStateEventHandler(emptyEvent, stateData) end if end while if (iq = 0) then ' LCA not found yet status$ = s.HStateEventHandler(exitEvent, stateData) ' exit the source ' check the rest of source->super == target->super->super... iq = ip status = "IGNORED" ' indicate LCA not found while (iq >= 0) if (t.id$ = path[iq].id$) then ' is this the LCA? status = "HANDLED" ' indicate LCA found ip = iq - 1 ' do not enter LCA iq = -1 ' terminate the loop else iq = iq - 1 ' try lower superstate of target end if end while if (status <> "HANDLED") then ' LCA not found yet? ' check each source->super->... for each target->super... status = "IGNORED" ' keep looping while (status <> "HANDLED") status$ = t.HStateEventHandler(exitEvent, stateData) if (status$ = "HANDLED") then status$ = t.HStateEventHandler(emptyEvent, stateData) end if t = stateData.nextState ' set to super of t iq = ip while (iq > 0) if (t.id$ = path[iq].id$) then ' is this the LCA? ip = iq - 1 ' do not enter LCA iq = -1 ' break inner status = "HANDLED" ' break outer else iq = iq - 1 end if end while end while end if end if end if end if end if end if ' retrace the entry path in reverse (desired) order... while (ip >= 0) status$ = path[ip].HStateEventHandler(entryEvent, stateData) ' enter path[ip] ip = ip - 1 end while t = path[0] ' stick the target into register */ m.activeState = t ' update the current state */ ' drill into the target hierarchy... status$ = t.HStateEventHandler(initEvent, stateData) m.activeState = stateData.nextState while (status$ = "TRANSITION") ip = 0 path[0] = m.activeState status$ = m.activeState.HStateEventHandler(emptyEvent, stateData) ' find superstate m.activeState = stateData.nextState while (m.activeState.id$ <> t.id$) ip = ip + 1 path[ip] = m.activeState status$ = m.activeState.HStateEventHandler(emptyEvent, stateData) ' find superstate m.activeState = stateData.nextState end while m.activeState = path[0] while (ip >= 0) status$ = path[ip].HStateEventHandler(entryEvent, stateData) ip = ip - 1 end while t = path[0] status$ = t.HStateEventHandler(initEvent, stateData) end while end if m.activeState = t ' set the new state or restore the current state end sub Function HSMIsIn() as boolean return false end function Function newHState(bsp as object, id$ as string) as object HState = { } HState.HStateEventHandler = invalid ' filled in by HState instance HState.stateMachine = m HState.bsp = bsp HState.superState = invalid ' filled in by HState instance HState.id$ = id$ return HState end function 'endregion 'region Diagnostics 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 = { } diagnostics.debug = sysFlags.debugOn diagnostics.autorunVersion$ = "unknown" diagnostics.customAutorunVersion$ = "unknown" diagnostics.firmwareVersion$ = "unknown" diagnostics.systemTime = CreateObject("roSystemTime") diagnostics.systemLogDebug = sysFlags.systemLogDebugOn if diagnostics.systemLogDebug then diagnostics.systemLog = CreateObject("roSystemLog") end if diagnostics.UpdateDebugOn = UpdateDebugOn diagnostics.UpdateSystemLogDebugOn = UpdateSystemLogDebugOn diagnostics.PrintDebug = PrintDebug diagnostics.PrintTimestamp = PrintTimestamp diagnostics.SetSystemInfo = SetSystemInfo diagnostics.DiagnoseAndRecoverWifiNetwork = DiagnoseAndRecoverWifiNetwork diagnostics.minusSixFailureCount = 0 diagnostics.minusTwentyEightFailureCount = 0 diagnostics.minusFiftySixFailureCount = 0 diagnostics.lastWifiReconnectTimestampInSeconds = 0 return diagnostics end function Sub UpdateDebugOn(debugOn as boolean) m.debug = debugOn end sub Sub UpdateSystemLogDebugOn(systemLogDebug as boolean) m.systemLogDebug = systemLogDebug if systemLogDebug and type(m.systemLog) <> "roSystemLog" then m.systemLog = CreateObject("roSystemLog") end if end sub Sub PrintDebug(debugStr$ as string) if type(m) <> "roAssociativeArray" then stop if m.debug then print debugStr$ end if if m.systemLogDebug then m.systemLog.SendLine(debugStr$) end if return end sub Sub PrintTimestamp() eventDateTime = m.systemTime.GetLocalDateTime() if m.debug then print eventDateTime.GetString() if m.systemLogDebug then m.systemLog.SendLine(eventDateTime.GetString()) end if 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.enableLogDeletion = sysInfo.enableLogDeletion m.diagnosticCodes = diagnosticCodes return end sub Function GetHexColor(colorAttrs as object) as string ba = CreateObject("roByteArray") ba[0] = colorAttrs["a"] alpha$ = ba.ToHexString() ba[0] = colorAttrs["r"] red$ = ba.ToHexString() ba[0] = colorAttrs["g"] green$ = ba.ToHexString() ba[0] = colorAttrs["b"] blue$ = ba.ToHexString() return alpha$ + red$ + green$ + blue$ end function Function ByteArraysMatch(baInput as object, baSpec as object) as boolean if baSpec.Count() > baInput.Count() then return false end if numBytesToMatch% = baSpec.Count() numBytesInInput% = baInput.Count() startByteInInput% = numBytesInInput% - numBytesToMatch% for i% = 0 to baSpec.Count() - 1 if baInput[startByteInInput% + i%] <> baSpec[i%] then return false end if next return true end function Function StripLeadingSpaces(inputString$ as string) as string while true if left(inputString$, 1) <> " " then return inputString$ inputString$ = right(inputString$, len(inputString$) - 1) end while return inputString$ 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 REM ******************************************************* REM ******************************************************* REM *************** ******************** REM *************** DIAGNOSTIC CODES ******************** REM *************** ******************** REM ******************************************************* REM ******************************************************* Function newDiagnosticCodes() as object diagnosticCodes = { } 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_ASSETPOOL_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" diagnosticCodes.EVENT_USER_VARIABLE_NOT_FOUND = "1021" diagnosticCodes.EVENT_MEDIA_COUNTER_VARIABLE_NOT_FOUND = "1022" diagnosticCodes.EVENT_START_PRESENTATION = "1023" diagnosticCodes.EVENT_GPS_LOCATION = "1024" diagnosticCodes.EVENT_GPS_NOT_LOCKED = "1025" diagnosticCodes.EVENT_RETRIEVE_USER_VARIABLE_FEED = "1026" diagnosticCodes.EVENT_RETRIEVE_LIVE_TEXT_FEED = "1027" diagnosticCodes.EVENT_USER_VARIABLE_FEED_DOWNLOAD_FAILURE = "1028" diagnosticCodes.EVENT_LIVE_TEXT_FEED_DOWNLOAD_FAILURE = "1029" diagnosticCodes.EVENT_UNASSIGNED_LOCAL_PLAYLIST = "1030" diagnosticCodes.EVENT_UNASSIGNED_LOCAL_PLAYLIST_NO_NAVIGATION = "1031" diagnosticCodes.EVENT_REALIZE_FAILURE = "1032" diagnosticCodes.EVENT_LIVE_TEXT_PLUGIN_FAILURE = "1033" diagnosticCodes.EVENT_INVALID_DATE_TIME_SPEC = "1034" diagnosticCodes.EVENT_HTML5_LOAD_ERROR = "1035" diagnosticCodes.EVENT_USB_UPDATE_SECURITY_ERROR = "1036" diagnosticCodes.EVENT_SCRIPT_PLUGIN_FAILURE = "1041" diagnosticCodes.EVENT_DISK_ERROR = "1042" diagnosticCodes.EVENT_LIVE_MRSS_PLUGIN_FAILURE = "1043" diagnosticCodes.EVENT_EMPTY_MEDIA_PLAYLIST = "1044" diagnosticCodes.EVENT_CUSTOM_USER_AGENT_FAILURE = "1045" diagnosticCodes.EVENT_VARIABLE_REFERENCE_FAILURE = "1046" diagnosticCodes.EVENT_BLC400_STATUS = "1100" diagnosticCodes.EVENT_CONTINUE_LIVE_DATA_FEED_CONTENT_DOWNLOAD = "1200" diagnosticCodes.EVENT_RESTART_LIVE_DATA_FEED_CONTENT_DOWNLOAD = "1201" diagnosticCodes.EVENT_START_LIVE_DATA_FEED_CONTENT_DOWNLOAD = "1202" diagnosticCodes.EVENT_ASSETPOOL_UNPROTECT_FAILURE = "1203" diagnosticCodes.EVENT_PLAYBACK_FAILURE = "1204" diagnosticCodes.EVENT_START_MRSS_FEED_CONTENT_DOWNLOAD = "1205" diagnosticCodes.EVENT_UNABLE_TO_CREATE_ASSET_POOL = "1206" diagnosticCodes.EVENT_DELETE_USER_VARIABLES_DB = "1207" diagnosticCodes.EVENT_SCREENSHOT_ERROR = "1208" diagnosticCodes.EVENT_SCREENSHOT_UPLOAD_ERROR = "1209" diagnosticCodes.EVENT_SCREENSHOT_UPLOADED_AND_QUEUED = "1210" diagnosticCodes.EVENT_SCREENSHOT_QUEUE_ERROR = "1211" diagnosticCodes.EVENT_SET_SNAPSHOT_CONFIGURATION = "1215" diagnosticCodes.EVENT_STREAM_END = "1216" diagnosticCodes.EVENT_SET_VIDEO_MODE = "1217" diagnosticCodes.EVENT_SNAPSHOT_PUT_TO_SERVER_ERROR = "1218" diagnosticCodes.EVENT_CHECK_LIVE_TEXT_FEED_HEAD = "1219" diagnosticCodes.EVENT_SCREENSHOT_UPLOADED = "1220" diagnosticCodes.EVENT_BEACON_START = "1300" diagnosticCodes.EVENT_BEACON_START_FAILED = "1301" diagnosticCodes.EVENT_BEACON_START_LIMIT_EXCEEDED = "1302" diagnosticCodes.EVENT_BTLE_START_FAILED = "1303" diagnosticCodes.EVENT_CONTROL_PORT_DISCONNECTED = "1304" diagnosticCodes.EVENT_BMAP_DISCONNECTED = "1305" return diagnosticCodes end function 'endregion 'region GPS Functions REM ================================================== REM GPS Functions REM ================================================== ' Parse the NMEA GPRMC format and return the data in an object - http://www.gpsinformation.org/dale/nmea.htm ' The returned object contains the following fields ' .valid - boolean - is the sentence is correctly formed, has the correct checksum and has the correct GPRMC header ' .fixTime - contains the string from the sentence that is the time the sample was taken - no processing is done on this ' .fixActive - boolean - does the latitude and longitude contain real data ' .latitude - float - signed degrees of the latitude ' .longitude - float - signed degrees of the longitude Sub ParseGPSdataGPRMCformat(NMEAsentence as string) as object gpsData = { } starLoc = instr(1, NMEAsentence, "*") if starLoc = 0 then gpsData.valid = false else if starLoc = len(NMEAsentence) - 2 then CalcChecksum = CalcChecksum (mid(NMEAsentence, 2, len(NMEAsentence) - 4)) ba = CreateObject("roByteArray") ba.fromhexstring(mid(NMEAsentence, len(NMEAsentence) - 1, 2)) CalcChecksum = ba[0] if (CalcChecksum <> CalcChecksum) then gpsData.valid = false else ' Strip off the beginning $ sign and the * + checksum strippedSentence = mid(NMEAsentence, 2, len(NMEAsentence) - 4) ' Get the identifier field = getNextGPSfield(strippedSentence, 1) gpsData.type = field.fieldString ' Make sure this is the right data format if (gpsData.type <> "GPRMC") then gpsData.valid = false else gpsData.valid = true ' Get the fix time field = getNextGPSfield(strippedSentence, field.nextFieldStart) gpsData.fixTime = field.fieldString ' Get the status of the fix: A=Active, V=Void time, convert to fixActive = true for A, false for V field = getNextGPSfield(strippedSentence, field.nextFieldStart) if (field.fieldString <> "A") then gpsData.fixActive = false gpsData.latitude = 0 gpsData.longitude = 0 else gpsData.fixActive = true ' Get the Latitude field = getNextGPSfield(strippedSentence, field.nextFieldStart) latDegrees = val(left(field.fieldString, 2)) latMinutes = val(mid(field.fieldString, 3)) latDegrees = latDegrees + (latMinutes / 60) ' Get the Latitude Direction field = getNextGPSfield(strippedSentence, field.nextFieldStart) ' Adjust the sign of the angle based on the direction gpsData.latitude = ConvertNSEWtoQuadrant(field.fieldString, latDegrees) ' Get the Longitude field = getNextGPSfield(strippedSentence, field.nextFieldStart) longDegrees = val(left(field.fieldString, 3)) longMinutes = val(mid(field.fieldString, 4)) longDegrees = longDegrees + (longMinutes / 60) ' Get the Longitude Direction field = getNextGPSfield(strippedSentence, field.nextFieldStart) ' Adjust the sign of the angle based on the direction gpsData.longitude = ConvertNSEWtoQuadrant(field.fieldString, longDegrees) end if end if end if end if return gpsData end sub ' Parse and return the next NMEA field from the sentence ' returns an object with two members: ' .fieldString - contains the contents of the field, if nothing is in the field - returns "" ' .nextFieldStart - indicates the location in the string where the next field should start Sub getNextGPSfield (NMEAsentence as string, startingIndex as integer) as object gpsField = { } ' Look for the next field seperator as a comma (this is the case except for the checksum which is a *) fieldEndLoc = instr(startingIndex, NMEAsentence, ",") if fieldEndLoc <> 0 then if fieldEndLoc > startingIndex then gpsField.fieldString = mid(NMEAsentence, startingIndex, fieldEndLoc - startingIndex) else gpsField.fieldString = "" end if gpsField.nextFieldStart = fieldEndLoc + 1 else stringLen = len(NMEAsentence) if (stringLen >= startingIndex) then gpsField.fieldString = mid(NMEAsentence, startingIndex, stringLen - startingIndex + 1) else gpsField.fieldString = "" end if gpsField.nextFieldStart = stringLen + 1 end if return (gpsField) end sub ' Calculate the great circle distance of two gps points - points must be in radians Sub CalcGPSDistance(lat1 as float, lon1 as float, lat2 as float, lon2 as float) as float radiusOfEarthInFeet# = 3963.1 * 5280.0 ' Convert coodinate 1 to Cartesian coordinates x1# = radiusOfEarthInFeet# * cos(lon1) * sin(lat1) y1# = radiusOfEarthInFeet# * sin(lon1) * sin(lat1) z1# = radiusOfEarthInFeet# * sin(lat1) ' Convert coodinate 2 to Cartesian coordinates x2# = radiusOfEarthInFeet# * cos(lon2) * sin(lat2) y2# = radiusOfEarthInFeet# * sin(lon2) * sin(lat2) z2# = radiusOfEarthInFeet# * sin(lat2) ' Calc the distance based on Euclidean distance distance = sqr((x1# - x2#) * (x1# - x2#) + (y1# - y2#) * (y1# - y2#) + (z1# - z2#) * (z1# - z2#)) return (distance) end sub ' Calculate the checksum based on the NMEA stardard - http://www.gpsinformation.org/dale/nmea.htm ' the checksum is an XOR of all characters between the $ and * in the sentence Sub CalcChecksum (theString as string) as integer checksum = 0 theStringLen = len (theString) if (theStringLen >= 2) then a = asc(mid(theString, 1, 1)) b = asc(mid(thestring, 2, 1)) ' XOR the two first two characters in the string checksum = &HFF and ((a or b) and (not(a and b))) else if (theStringLen = 1) then ' If only one character is in the string, it is the checksum checksum = asc(mid(theString, 1, 1)) end if if (theStringLen >= 3) then for i = 3 to theStringLen a = checksum b = asc(mid(thestring, i, 1)) ' XOR the current checksum with the next character checksum = &HFF and ((a or b) and (not(a and b))) next end if return (checksum) end sub Sub ConvertDecimalDegtoRad(deg as float) as float pi = 3.14159265358979 radians = deg * (pi / 180) return (radians) end sub Sub ConvertNSEWtoQuadrant(direction as string, angle as float) as float if (direction = "W") or (direction = "w") or (direction = "S") or (direction = "s") then angle = angle * -1 end if return (angle) end sub 'endregion 'region SIGNCHANNEL / MEDIARSS helpers REM ******************************************************* REM ******************************************************* REM *************** *************** REM *************** SIGNCHANNEL / MEDIARSS *************** REM *************** *************** REM ******************************************************* REM ******************************************************* Function isImage(item as object) as boolean REM Default is the item is an image rv = TRUE if item.type = "video/mpeg" or item.type = "video/mp4" or item.type = "video/quicktime" or item.type = "video/x-matroska" or item.medium = "video" or item.type = "audio/mpeg" or item.medium = "audio" or item.type = "text/html" or item.type = "application/widget" or item.medium = "document" then rv = false end if return rv end function Function isAudio(item as object) as boolean REM Default is the item is an image rv = false if item.type = "audio/mpeg" or item.medium = "audio" then rv = true end if return rv end function Function isHtml(item as object) as boolean REM Default is the item is an image rv = false if item.type = "text/html" or item.type = "application/widget" or item.medium = "document" then rv = true end if return rv 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 Function helper_GetFileSize(contentElement as object) as integer size = contentElement.GetAttributes()["fileSize"] if size = invalid then return 0 end if fileSize = Val(size) return fileSize end function Function helper_GetProbeData(contentElement as object) as string probe = contentElement.GetAttributes()["probe"] if probe = invalid then return "" end if return probe end function 'endregion 'region Miscellaneous functions Function FileExists(filePath$ as string) as boolean file = CreateObject("roReadFile", filePath$) if not type(file) = "roReadFile" return false end if file = invalid return true end function 'endregion 'region MRSSDataFeed methods ' check for existence of the feed file associated with this feed ' if it exists, setup asset collection / assetPoolFiles objects for the feed (independent of whether or not the assets are actually on the card) Sub ReadMRSSContent() feedFileName$ = "feed_cache/" + m.id$ + ".xml" m.isMRSSFeed = m.FeedIsMRSS(feedFileName$) if not m.isMRSSFeed and m.parser$ = "" then return end if m.bsp.diagnostics.PrintDebug("Read existing content for feed " + m.id$ + ".") file = CreateObject("roReadFile", feedFileName$) if type(file) <> "roReadFile" then return end if file = invalid ' parse the feed, building an asset collection and a list of file items m.assetCollection = CreateObject("roAssetCollection") m.ParseMRSSFeed(feedFileName$) for each item in m.feed.items ' Do not download content of type 'text/html' - this is accessed directly if item.type = invalid or item.type <> "text/html" then asset = { } asset.link = item.url ' SignChannel sizes appear to be inaccurate ' if item.size > 0 then ' asset.size = item.size ' endif asset.name = item.url if IsNonEmptyString(item.guid) then asset.change_hint = item.guid else if IsString(item.url) then asset.change_hint = item.url end if m.assetCollection.AddAsset(asset) end if next if not m.bsp.feedPool.ProtectAssets("display-" + m.id$, m.assetCollection) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.logging.FlushLogFile() m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + m.bsp.feedPool.GetFailureReason()) stop end if m.assetPoolFiles = CreateObject("roAssetPoolFiles", m.bsp.feedPool, m.assetCollection) end sub Function FeedIsMRSS(fileName$ as string) xml = ReadAsciiFile(fileName$) if len(xml) = 0 then return false end if feedXML = CreateObject("roXMLElement") if not feedXML.Parse(xml) then return false end if if feedXML.HasAttribute("xmlns:media") then attrs = feedXML.GetAttributes() if attrs["xmlns:media"] = "http://search.yahoo.com/mrss/" then return true end if end if return false end function Sub DownloadMRSSContent() m.bsp.diagnostics.PrintDebug("DownloadMRSSContent") if type(m.assetFetcher) = "roAssetFetcher" then return end if fileNameOnCard$ = "feed_cache/" + m.id$ + ".xml" ' write the mrss feed to the card CopyFile(m.rssFileName$, fileNameOnCard$) m.bsp.diagnostics.PrintDebug("Download new content for feed " + m.id$ + ".") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_START_MRSS_FEED_CONTENT_DOWNLOAD, m.id$) ' parse the feed, building an asset collection and a list of file items m.assetCollection = CreateObject("roAssetCollection") m.ParseMRSSFeed(m.rssFileName$) m.feedContentFilesToDownload = { } for each item in m.feed.items ' Do not download content of type 'text/html' - this is accessed directly if item.type = invalid or item.type <> "text/html" then asset = { } asset.link = item.url ' if item.size > 0 then ' asset.size = item.size ' endif asset.name = item.url if IsNonEmptyString(item.guid) then asset.change_hint = item.guid else if IsString(item.url) then asset.change_hint = item.url end if m.assetCollection.AddAsset(asset) end if ' track feed content downloads fileToDownload = { } fileToDownload.name = item.title fileToDownload.size = item.size fileToDownload.hash = item.guid fileToDownload.currentFilePercentage$ = "" fileToDownload.status$ = "" if type(asset) = "roAssociativeArray" and type(asset.link) = "roString" then if type(m.assetPoolFiles) = "roAssetPoolFiles" then filePath = m.assetPoolFiles.GetPoolFilePath(asset.link) if filePath <> "" then fileToDownload.currentFilePercentage$ = "100" fileToDownload.status$ = "ok" end if end if end if if IsString(fileToDownload.hash) then m.feedContentFilesToDownload.AddReplace(fileToDownload.hash, fileToDownload) end if next if type(m.bsp.networkingHSM) = "roAssociativeArray" then m.bsp.networkingHSM.UploadDeviceDownloadProgressFileList() m.bsp.networkingHSM.FileListPendingUpload = false end if if not m.bsp.feedPool.ProtectAssets("download-" + m.id$, m.assetCollection) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.logging.FlushLogFile() m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + m.bsp.feedPool.GetFailureReason()) stop end if m.bsp.feedPool.ReserveMegabytes(50) m.assetFetcher = CreateObject("roAssetFetcher", m.bsp.feedPool) m.assetFetcher.SetPort(m.bsp.msgPort) m.assetFetcher.AddHeader("User-Agent", m.bsp.userAgent$) m.assetFetcher.SetMinimumTransferRate(1000, 60) m.assetFetcher.SetFileProgressIntervalSeconds(5) m.assetFetcher.SetUserData(m.id$) aa = GetBinding("mediaFeedsDownloadEnabled", m.bsp.mrssDataFeedsBindingPriorityIndex) binding = aa.network_interface m.bsp.mrssDataFeedsBindingPriorityIndex = aa.priorityIndex m.bsp.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for assetFetcher is (DownloadMRSSContent) ", binding)) ok = m.assetFetcher.BindToInterface(binding) if not ok then stop if not m.assetFetcher.AsyncDownload(m.assetCollection) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_IMMEDIATE_FAILURE, m.assetFetcher.GetFailureReason()) m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### AsyncDownload failed: " + m.assetFetcher.GetFailureReason()) m.assetFetcher = invalid end if ' tell the states to switch over to the new spec immediately m.assetPoolFiles = CreateObject("roAssetPoolFiles", m.bsp.feedPool, m.assetCollection) mrssSpecUpdatedEvent = { } mrssSpecUpdatedEvent["EventType"] = "MRSS_SPEC_UPDATED" mrssSpecUpdatedEvent["LiveDataFeed"] = m m.bsp.msgPort.PostMessage(mrssSpecUpdatedEvent) end sub Function newMRSSFeed(liveDataFeed as object) as object feed = { } feed.liveDataFeed = liveDataFeed feed.ttlSeconds = -1 feed.PopulateFeedItems = mrssFeed_PopulateFeedItems feed.ParseFeedByPlugin = mrssFeed_ParseFeedByPlugin feed.SetTTLMinutes = mrssFeed_SetTTLMinutes feed.SetTTLSeconds = mrssFeed_SetTTLSeconds feed.ContentExists = mrssFeed_ContentExists feed.AllContentExists = mrssFeed_AllContentExists return feed end function Sub ParseMRSSFeed(filePath$ as string) m.feed = newMRSSFeed(m) if m.parser$ <> "" then m.feed.ParseFeedByPlugin(filePath$) else m.feed.PopulateFeedItems(filePath$) end if ' if the ttl specified in the feed < the update interval, set the update interval to the specified ttl if m.feed.ttlSeconds > 0 and m.feed.ttlSeconds < m.updateInterval% then m.updateInterval% = m.feed.ttlSeconds end if if m.feed.displayOn <> invalid then m.displayOn = m.feed.displayOn end if end sub Function mrssFeed_ContentExists(assetPoolFiles as object) as boolean for each item in m.items file$ = item.url filePath$ = GetPoolFilePath(assetPoolFiles, file$) if filePath$ <> "" then return true end if next return false end function Function mrssFeed_AllContentExists(assetPoolFiles as object) as boolean for each item in m.items ' skip checkng for HTML items - bug 24245 if item.type = invalid or item.type <> "text/html" then file$ = item.url filePath$ = GetPoolFilePath(assetPoolFiles, file$) if filePath$ = "" then return false end if end if next return true end function Sub mrssFeed_PopulateFeedItems(filePath$ as string) m.items = [] xml = ReadAsciiFile(filePath$) if len(xml) = 0 then return end if ' check for encryption if getGlobalAA().bsp.contentEncrypted then isEncrypted = true else isEncrypted = false end if mrssFeedXML = CreateObject("roXMLElement") if not mrssFeedXML.Parse(xml) then stop end if for each elt in mrssFeedXML.GetBody().Peek().GetBody() name = elt.GetName() if name = "ttl" then m.SetTTLMinutes(elt.GetBody()) else if name = "frameuserinfo:playtime" then m.playtime = Val(elt.GetBody()) else if lcase(name) = "title" then m.title = elt.GetBody() else if name = "item" then item = newMRSSItem(elt) if (item <> invalid) then item.isEncrypted = isEncrypted m.items.Push(item) end if end if next end sub Sub mrssFeed_SetTTLMinutes(ttl as string) if ttl = invalid or Val(ttl) <= 0 then m.ttlSeconds = -1 else if Val(ttl) < 2 then m.ttlSeconds = 120 else m.ttlSeconds = Val(ttl) * 60 end if ' the ttl is the lower of the ttl specified in the feed and the update rate of the live data if type(m.liveDataFeed) = "roAssociativeArray" and type(m.liveDataFeed.updateInterval%) = "roInt" and m.liveDataFeed.updateInterval% < m.ttlSeconds then m.ttlSeconds = m.liveDataFeed.updateInterval% end if end sub Sub mrssFeed_SetTTLSeconds(ttlSeconds as string) if ttlSeconds <> invalid then secs = Val(ttlSeconds) if secs < 30 then m.ttlSeconds = 30 else m.ttlSeconds = secs end if ' the ttl is the lower of the ttl specified in the feed and the update rate of the live data if type(m.liveDataFeed) = "roAssociativeArray" and type(m.liveDataFeed.updateInterval%) = "roInt" and m.liveDataFeed.updateInterval% < m.ttlSeconds then m.ttlSeconds = m.liveDataFeed.updateInterval% end if end if end sub Function newCustomContentMRSSItem( xml as Object ) As Object item = { url:"no_url", title:"no_title", medium:"no_medium", type: "no_type", guid: "" } contentPresent = false for each elt in xml.GetBody() name = elt.GetName() if name = "title" then item.title = elt.GetBody() else if name = "description" then item.url = elt.GetBody() else if name = "medium" then item.medium = elt.GetBody() else if name = "type" then item.type = elt.GetBody() endif next ' set medium from either type of medium if item.medium = "no_medium" then if item.type <> "no_type" then item.medium = getMediumFromMimeType(item.type) endif endif return item End function Function newMRSSItem(xml as object) as object item = { durationSeconds: 60, url: "no_url", category: "no_category", thumbnail: "no_thumbnail", title: "no_title", displayStart: 0, medium: "no_medium", size: 0, isEncrypted: GetGlobalAA().bsp.contentEncrypted } contentPresent = false for each elt in xml.GetBody() name = elt.GetName() if name = "guid" then item.guid = elt.GetBody() else if lcase(name) = "title" then item.title = elt.GetBody() else if name = "description" then item.description = elt.GetBody() else if name = "media:content" then item.url = elt.GetAttributes()["url"] item.type = elt.GetAttributes()["type"] item.duration = helper_GetDuration(elt) item.size = helper_GetFileSize(elt) item.medium = elt.GetAttributes()["medium"] contentPresent = true item.probeData = helper_GetProbeData(elt) else if name = "media:thumbnail" then item.thumbnail = elt.GetAttributes()["url"] else if name = "category" then item.category = elt.GetBody() else if name = "media:group" then for each eltmg in elt.GetBody() name = eltmg.GetName() if name = "media:content" then item.url = eltmg.GetAttributes()["url"] item.type = eltmg.GetAttributes()["type"] item.duration = helper_GetDuration(eltmg) item.size = helper_GetFileSize(eltmg) item.medium = eltmg.GetAttributes()["medium"] contentPresent = true item.probeData = helper_GetProbeData(eltmg) else if name = "media:thumbnail" then item.thumbnail = eltmg.GetAttributes()["url"] end if next ' custom fields ' ignore the following elements in a feed: link, ? else if name <> "link" then if type(item.mrssCustomFields) <> "roAssociativeArray" then item.mrssCustomFields = { } end if if elt.GetBody() = invalid then value = "" else value = elt.GetBody() end if item.mrssCustomFields.AddReplace(name, value) end if next ' make item.guid the hash of the guid if IsString(item.guid) then hashGen = CreateObject("roHashGenerator", "SHA1") item.guid = hashGen.hash(item.guid).ToHexString() hashGen = invalid end if if (contentPresent) then return item else return invalid end if end function Sub mrssFeed_ParseFeedByPlugin(filePath$ as string) items = CreateObject("roArray", 1, true) metadata = { } m.items = [] ERR_NORMAL_END = &hFC retVal = eval(m.liveDataFeed.parser$ + "(filePath$, items, metadata)") if retVal <> ERR_NORMAL_END then ' log the failure bsp = m.liveDataFeed.bsp bsp.diagnostics.PrintDebug("Failure invoking Eval to parse live MRSS data feed: return value = " + stri(retVal) + ", parser is " + m.liveDataFeed.parser$) bsp.logging.WriteDiagnosticLogEntry(bsp.diagnosticCodes.EVENT_LIVE_MRSS_PLUGIN_FAILURE, stri(retVal) + chr(9) + m.liveDataFeed.parser$) else ' Use the item array to build the item array for the feed, making sure it has ' all required elements for each item in items ' Skip any item that does not have a url if item.url <> invalid and IsString(item.url) then mrssItem = { } mrssItem.url = item.url if IsString(item.title) then mrssItem.title = item.title else mrssItem.title = "" end if if IsString(item.description) then mrssItem.description = item.description else mrssItem.description = "" end if if IsString(item.duration) then mrssItem.duration = Val(item.duration) else mrssItem.duration = 15 end if if IsString(item.type) then mrssItem.type = item.type else mrssItem.type = "" end if if IsString(item.medium) then mrssItem.medium = item.medium else mrssItem.medium = "no_medium" end if if IsString(item.size) then mrssItem.size = Val(item.size) else mrssItem.size = 0 end if if IsString(item.probeData) then mrssItem.probeData = item.probeData else mrssItem.probeData = "" end if ' make mrssItem.guid the hash of the guid from the feed if IsString(item.guid) then guid$ = item.guid hashGen = CreateObject("roHashGenerator", "SHA1") mrssItem.guid = hashGen.hash(guid$).ToHexString() hashGen = invalid end if ' if plugin specified custom fields, just copy them in if type(item.mrssCustomFields) = "roAssociativeArray" then mrssItem.mrssCustomFields = item.mrssCustomFields end if m.items.Push(mrssItem) end if next 'Populate feed metadata 'If the parser gives us an update interval in seconds, use that, otherwise look for interval in minutes (like standard MRSS) if metadata.ttlSeconds <> invalid and IsString(metadata.ttlSeconds) then m.SetTTLSeconds(metadata.ttlSeconds) else if metadata.ttl <> invalid and IsString(metadata.ttl) then m.SetTTLMinutes(metadata.ttl) end if if metadata.title <> invalid and IsString(metadata.title) then m.title = metadata.title end if if metadata.playtime <> invalid and IsString(metadata.playtime) then m.playtime = metadata.playtime end if if metadata.displayOn <> invalid and IsString(metadata.displayOn) then m.displayOn = metadata.displayOn end if end if end sub 'endregion 'region LiveDataFeed methods Function ParseSimpleRSSFeed(filePath$ as string) as boolean success = true m.articles = CreateObject("roArray", 1, true) m.articleTitles = CreateObject("roArray", 1, true) m.articlesByTitle = CreateObject("roAssociativeArray") m.articleHashTypes = CreateObject("roArray", 1, true) m.articleHashes = CreateObject("roArray", 1, true) if m.parser$ <> "" then m.ParseRSSWithParserPlugin( filePath$) else if type(m.isJSON) = "roBoolean" and m.isJSON then success = m.ParseJSONRSS(filePath$) else parser = CreateObject("roRssParser") success = parser.ParseFile(filePath$) if success then article = parser.GetNextArticle() while type(article) = "roRssArticle" title = article.GetTitle() description = article.GetDescription() m.articles.Push(description) m.articleTitles.Push(title) m.articlesByTitle.AddReplace(title, description) article = parser.GetNextArticle() endwhile endif endif endif return success end function Function ContentDataFeedsIdentical(articlesFeed1 as object, articlesFeed2 as object, compareArticleTitles as boolean, articleTitlesFeed1 as object, articleTitlesFeed2 as object) as boolean if articlesFeed1.Count() = articlesFeed2.Count() then for i% = 0 to articlesFeed1.Count() - 1 if articlesFeed1[i%] <> articlesFeed2[i%] then return false else if compareArticleTitles and articleTitlesFeed1[i%] <> articleTitlesFeed2[i%] then return false end if next return true end if return false end function Sub ReadFeedContent() if m.usage$ = "content" then m.ReadLiveFeedContent() else if m.usage$ = "mrss" or m.usage$ = "mrsswith4k" then m.ReadMRSSContent() end if end sub Sub ReadLiveFeedContent() filePath$ = "feed_cache/" + m.id$ + ".xml" ok = true if m.isDynamicPlaylist or m.isLiveMediaFeed then m.ParseMRSSFeed(filePath$) m.ConvertMRSSFormatToContent() else ok = m.ParseCustomContentFormat(filePath$) end if if ok then m.itemUrls = m.articles m.fileUrls = m.articles if type(m.articleMediaTypes) = "roArray" then m.fileTypes = m.articleMediaTypes end if m.fileKeys = CreateObject("roArray", m.articles.Count(), true) if m.articleTitles.Count() > 0 then m.fileKeys = m.articleTitles else if type(m.articlesByTitle) = "roAssociativeArray" then ' the following algorithm has poor performance- improve in the future by building and using a dictionary for each key in m.articlesByTitle ' find the corresponding url by linearly searching through m.articles index% = 0 url = m.articlesByTitle[key] for each articleUrl in m.articles if articleUrl = url then m.fileKeys[index%] = key end if index% = index% + 1 next next end if ' build data structures so that script can check if all content exists on the card m.assetCollection = CreateObject("roAssetCollection") index% = 0 for each url in m.fileUrls asset = { } asset.link = url asset.name = url if type(m.assets) = "roArray" then asset.hash = m.assets[index%].hash end if m.assetCollection.AddAsset(asset) index% = index% + 1 next ' verify that all specified files are actually on the card m.assetPoolFiles = CreateObject("roAssetPoolFiles", m.bsp.feedPool, m.assetCollection) for each url in m.fileUrls filePath$ = m.assetPoolFiles.GetPoolFilePath(url) if filePath$ = "" then m.assetPoolFiles = invalid m.itemUrls = invalid m.fileKeys = invalid m.fileUrls = invalid return end if next ' protect these assets if not m.bsp.feedPool.ProtectAssets("current-" + m.id$, m.assetCollection) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.logging.FlushLogFile() m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + m.bsp.feedPool.GetFailureReason()) stop end if ' post message indicating load complete contentDataFeedLoaded = { } contentDataFeedLoaded["EventType"] = "CONTENT_DATA_FEED_LOADED" contentDataFeedLoaded["Name"] = m.id$ m.bsp.msgPort.PostMessage(contentDataFeedLoaded) end if end sub Sub DownloadLiveFeedContent() m.bsp.diagnostics.PrintDebug("DownloadLiveFeedContent") if type(m.parser$) = "roString" and m.parser$ <> "" then compareArticleTitles = false else compareArticleTitles = true end if if type(m.assetFetcher) = "roAssetFetcher" then ' fetch active, see if feed has changed if ContentDataFeedsIdentical(m.articlesDownloading, m.articles, compareArticleTitles, m.articleTitlesDownloading, m.articleTitles) then m.bsp.diagnostics.PrintDebug("### live data feed asset fetch active and there are no changes to the feed spec so we'll let it continue") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_CONTINUE_LIVE_DATA_FEED_CONTENT_DOWNLOAD, "") return end if ' feed has changed, cancel download and start new download m.bsp.diagnostics.PrintDebug("### asset fetch active but feed has changed - cancel current download") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_RESTART_LIVE_DATA_FEED_CONTENT_DOWNLOAD, "") m.assetFetcher.AsyncCancel() m.assetFetcher = invalid end if ' it is only necessary to download content if ... ' there is no current content OR ' current keys are different from keys specified in new feed OR ' current content is different from content specified in new feed if type(m.assetPoolFiles) = "roAssetPoolFiles" and type(m.fileUrls) = "roArray" and m.fileUrls.Count() > 0 then ' there is current content; check to see if it matches the new feed ' compare to existing keys and Urls if ContentDataFeedsIdentical(m.itemUrls, m.articles, compareArticleTitles, m.fileKeys, m.articleTitles) then m.bsp.diagnostics.PrintDebug("No change in content feed " + m.id$ + ". No need to download content.") ' post message indicating no need to download content contentDataFeedUnchanged = { } contentDataFeedUnchanged["EventType"] = "CONTENT_DATA_FEED_UNCHANGED" contentDataFeedUnchanged["Name"] = m.id$ m.bsp.msgPort.PostMessage(contentDataFeedUnchanged) return end if end if ' write the rss feed to the card CopyFile(m.rssFileName$, "feed_cache/" + m.id$ + ".xml") m.bsp.diagnostics.PrintDebug("Download new content for feed " + m.id$ + ".") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_START_LIVE_DATA_FEED_CONTENT_DOWNLOAD, m.id$) ' parse the feed, building an asset collection and a list of file items m.assetCollection = CreateObject("roAssetCollection") index% = 0 for each url in m.articles asset = { } asset.link = url asset.name = url ' Code commented out until fix for bug 17733 / 29442 is fully implemented for usageType = "content" ' asset.change_hint = url if type(m.articleHashTypes) = "roArray" and m.articleHashTypes.Count() > index% and type(m.articleHashTypes[index%]) = "roString" and type(m.articleHashes) = "roArray" and m.articleHashes.Count() > index% and type(m.articleHashes[index%]) = "roString" then asset.hash = m.articleHashTypes[index%] + ":" + m.articleHashes[index%] end if m.assetCollection.AddAsset(asset) ' Code commented out until fix for bug 17733 / 29442 is fully implemented for usageType = "content": the issue is that there is no m.feed / m.feed.items for this type of feed ' ' track feed content downloads ' fileToDownload = {} ' fileToDownload.name = url ' fileToDownload.size = 0 ' fileToDownload.hash = url ' fileToDownload.currentFilePercentage$ = "" ' fileToDownload.status$ = "" ' if type(asset) = "roAssociativeArray" and type(asset.link) = "roString" then ' if type(m.assetPoolFiles) = "roAssetPoolFiles" then ' filePath = m.assetPoolFiles.GetPoolFilePath(asset.link) ' if filePath <> "" then ' fileToDownload.currentFilePercentage$ = "100" ' fileToDownload.status$ = "ok" ' endif ' endif ' endif ' m.feedContentFilesToDownload.AddReplace(fileToDownload.hash, fileToDownload) index% = index% + 1 next if not m.bsp.feedPool.ProtectAssets("new-" + m.id$, m.assetCollection) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.logging.FlushLogFile() m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + m.bsp.feedPool.GetFailureReason()) stop end if m.bsp.feedPool.ReserveMegabytes(50) m.assetFetcher = CreateObject("roAssetFetcher", m.bsp.feedPool) m.assetFetcher.SetPort(m.bsp.msgPort) m.assetFetcher.AddHeader("User-Agent", m.bsp.userAgent$) m.assetFetcher.SetMinimumTransferRate(1000, 60) m.assetFetcher.SetFileProgressIntervalSeconds(5) m.assetFetcher.SetUserData(m.id$) aa = GetBinding("mediaFeedsDownloadEnabled", m.bsp.mrssDataFeedsBindingPriorityIndex) binding = aa.network_interface m.bsp.mrssDataFeedsBindingPriorityIndex = aa.priorityIndex m.bsp.diagnostics.PrintDebug(GetBindingDiagnostic("### Binding for Download Live Feed Content is ", binding)) ok = m.assetFetcher.BindToInterface(binding) if not ok then stop m.articlesDownloading = m.articles m.articleTitlesDownloading = m.articleTitles if not m.assetFetcher.AsyncDownload(m.assetCollection) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_IMMEDIATE_FAILURE, m.assetFetcher.GetFailureReason()) m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### AsyncDownload failed: " + m.assetFetcher.GetFailureReason()) m.assetFetcher = invalid end if end sub Sub RestartLiveDataFeedDownloadTimer(timespan% as integer) if timespan% > 0 then ' set a timer to update live data feed if type(m.timer) = "roTimer" then m.timer.Stop() else m.timer = CreateObject("roTimer") m.timer.SetPort(m.bsp.msgPort) end if m.timer.SetElapsed(timespan%, 0) m.timer.Start() m.bsp.liveDataFeedsByTimer.AddReplace(stri(m.timer.GetIdentity()), m) end if end sub Sub HandleLiveDataFeedContentDownloadAssetFetcherProgressEvent(event) m.bsp.diagnostics.PrintDebug("### HandleLiveDataFeedContentDownloadAssetFetcherProgressEvent") m.bsp.diagnostics.PrintDebug("### File download progress " + event.GetFileName() + str(event.GetCurrentFilePercentage())) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_FILE_DOWNLOAD_PROGRESS, event.GetFileName() + chr(9) + str(event.GetCurrentFilePercentage())) fileIndex% = event.GetFileIndex() assetList = m.assetCollection.GetAssetList() asset = assetList[fileIndex%] hash = asset.change_hint ' Hash is invalid until fix for bug 17733 / 29442 is fully implemented for usageType = "content": the issue is that there is no m.feed / m.feed.items for this type of feed if type(hash) <> "Invalid" then fileItem = m.feedContentFilesToDownload.Lookup(hash) if type(m.bsp.networkingHSM) = "roAssociativeArray" then m.bsp.networkingHSM.AddDeviceDownloadProgressItem(fileItem, str(event.GetCurrentFilePercentage()), "ok") end if end if m.bsp.diagnostics.PrintDebug("----------------------------- HandleLiveDataFeedContentDownloadAssetFetcherProgressEvent: " + str(event.GetCurrentFilePercentage())) end sub Sub HandleLiveDataFeedContentDownloadAssetFetcherEvent(event) POOL_EVENT_FILE_DOWNLOADED = 1 POOL_EVENT_FILE_FAILED = -1 POOL_EVENT_ALL_DOWNLOADED = 2 POOL_EVENT_ALL_FAILED = -2 m.bsp.diagnostics.PrintTimestamp() m.bsp.diagnostics.PrintDebug("### LiveDataFeedContentDownloadAssetFetcherEvent") if (event.GetEvent() = POOL_EVENT_FILE_DOWNLOADED) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_FILE_DOWNLOAD_COMPLETE, event.GetName()) m.bsp.diagnostics.PrintDebug("### File downloaded " + event.GetName()) ' track download traffic for dynamic playlists if m.isDynamicPlaylist and type(m.bsp.networkingHSM) = "roAssociativeArray" and type(m.assetPoolFiles) = "roAssetPoolFiles" then fileName$ = event.GetName() filePath$ = m.assetPoolFiles.GetPoolFilePath(fileName$) if filePath$ <> "" then checkFile = CreateObject("roReadFile", filePath$) if (checkFile <> invalid) then checkFile.SeekToEnd() size = checkFile.CurrentPosition() checkFile = invalid m.bsp.networkingHSM.UploadMRSSTrafficDownload(size) end if end if end if else if (event.GetEvent() = POOL_EVENT_FILE_FAILED) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_FILE_DOWNLOAD_FAILURE, event.GetName() + chr(9) + event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### File failed " + event.GetName() + ": " + event.GetFailureReason()) ' log this error to the download progress handler fileIndex% = event.GetFileIndex() assetList = m.assetCollection.GetAssetList() asset = assetList[fileIndex%] if IsString(asset.change_hint) then hash = asset.change_hint fileItem = m.feedContentFilesToDownload.Lookup(hash) if type(fileItem) = "roAssociativeArray" and type(m.bsp.networkingHSM) = "roAssociativeArray" then m.bsp.networkingHSM.AddDeviceDownloadProgressItem(fileItem, "-1", event.GetFailureReason()) end if end if ' count number of failure and cancel if there are too many?? else if (event.GetEvent() = POOL_EVENT_ALL_FAILED) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_SYNCSPEC_DOWNLOAD_FAILURE, event.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### Download failed: " + event.GetFailureReason()) m.assetFetcher = invalid m.lastDownloadedFailed = true if m.bsp.mrssDataFeedsNumRetries% >= m.bsp.mrssMaxRetries% then globalAA = GetGlobalAA() if not globalAA.networkInterfacePriorityLists.DoesExist("mediaFeedsDownloadEnabled") then ' TEDTODO stop endif networkInterfacePriorityList = globalAA.networkInterfacePriorityLists.Lookup("mediaFeedsDownloadEnabled") m.bsp.mrssDataFeedsBindingPriorityIndex = m.bsp.mrssDataFeedsBindingPriorityIndex + 1 if m.bsp.mrssDataFeedsBindingPriorityIndex >= networkInterfacePriorityList.count() then ' all network interfaces failed m.bsp.diagnostics.PrintDebug("### mrss data feed content download failed on all network interfaces") m.bsp.mrssDataFeedsBindingPriorityIndex = 0 else ' try next network interface m.bsp.diagnostics.PrintDebug("### mrss data feed content download failed. Try next network interface") endif else m.bsp.mrssDataFeedsNumRetries% = m.bsp.mrssDataFeedsNumRetries% + 1 m.bsp.diagnostics.PrintDebug("### retry mrss data feed content download") endif m.bsp.RemoveFailedFeedFromQueue() m.RestartLiveDataFeedDownloadTimer(30) else if (event.GetEvent() = POOL_EVENT_ALL_DOWNLOADED) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_DOWNLOAD_COMPLETE, "") m.bsp.diagnostics.PrintDebug("### All files downloaded") ' send up the list of files downloaded m.feedContentFilesToDownload = { } ' m.feed is invalid for feeds used for Media Lists (usageType$ = "content'): progress updates not supported yet if type(m.feed) = "roAssociativeArray" and type(m.feed.items) = "roArray" then for each item in m.feed.items fileToDownload = { } fileToDownload.name = item.title fileToDownload.size = item.size fileToDownload.hash = item.guid fileToDownload.currentFilePercentage$ = "100" fileToDownload.status$ = "ok" if IsString(fileToDownload.hash) then m.feedContentFilesToDownload.AddReplace(fileToDownload.hash, fileToDownload) end if next end if if type(m.bsp.networkingHSM) = "roAssociativeArray" then m.bsp.networkingHSM.UploadDeviceDownloadProgressFileList() m.bsp.networkingHSM.FileListPendingUpload = false end if m.assetFetcher = invalid if m.usage$ = "content" then ' unprotect old assets, keep protection on new (now current) assets if not m.bsp.feedPool.ProtectAssets("current-" + m.id$, m.assetCollection) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.logging.FlushLogFile() m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + m.bsp.feedPool.GetFailureReason()) stop end if if not m.bsp.feedPool.UnprotectAssets("new-" + m.id$) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### UnprotectFiles failed: " + m.bsp.feedPool.GetFailureReason()) end if ' get asset pool m.assetPoolFiles = CreateObject("roAssetPoolFiles", m.bsp.feedPool, m.assetCollection) ' copy result of previous parse m.itemUrls = [] m.fileKeys = [] m.fileUrls = [] m.fileTypes = CreateObject("roArray", 1, true) ' for MediaList states titlesByUrl = { } for each title in m.articlesByTitle url = m.articlesByTitle.Lookup(title) titlesByUrl.AddReplace(url, title) next index% = 0 for each itemUrl in m.articles m.itemUrls.push(itemUrl) if type(m.articleMediaTypes) = "roArray" and m.articleMediaTypes.Count() > index% then m.fileTypes[index%] = m.articleMediaTypes[index%] end if ' get corresponding title title = titlesByUrl.Lookup(itemUrl) m.fileKeys.push(title) m.fileUrls.push(itemUrl) index% = index% + 1 next m.bsp.mrssDataFeedsBindingPriorityIndex = 0 m.bsp.mrssDataFeedsNumRetries% = 0 ' post message indicating load complete contentDataFeedLoaded = { } contentDataFeedLoaded["EventType"] = "CONTENT_DATA_FEED_LOADED" contentDataFeedLoaded["Name"] = m.id$ m.bsp.msgPort.PostMessage(contentDataFeedLoaded) else ' unprotect old assets, keep protection on new (now current) assets if not m.bsp.feedPool.ProtectAssets("display-" + m.id$, m.assetCollection) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.logging.FlushLogFile() m.bsp.diagnostics.PrintDebug("### ProtectFiles failed: " + m.bsp.feedPool.GetFailureReason()) stop end if if not m.bsp.feedPool.UnprotectAssets("download-" + m.id$) then m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_ASSETPOOL_PROTECT_FAILURE, m.bsp.feedPool.GetFailureReason()) m.bsp.diagnostics.PrintDebug("### UnprotectFiles failed: " + m.bsp.feedPool.GetFailureReason()) end if m.bsp.mrssDataFeedsBindingPriorityIndex = 0 m.bsp.mrssDataFeedsNumRetries% = 0 ' post message indicating load complete mrssDataFeedLoaded = { } mrssDataFeedLoaded["EventType"] = "MRSS_DATA_FEED_LOADED" mrssDataFeedLoaded["Name"] = m.id$ m.bsp.msgPort.PostMessage(mrssDataFeedLoaded) end if end if end sub Sub ConvertMRSSFormatToContent() ' convert to format required for content feed m.articles = CreateObject("roArray", 1, true) m.articleTitles = CreateObject("roArray", 1, true) m.articlesByTitle = { } m.articleMediaTypes = CreateObject("roArray", 1, true) for each item in m.feed.items m.articles.push(item.url) m.articleTitles.push(item.title) m.articlesByTitle.AddReplace(item.title, item.url) m.articleMediaTypes.push(item.medium) next end sub Function ParseRSSWithParserPlugin( filePath$ as String) as Boolean success = true userVariables = m.bsp.currentUserVariables ERR_NORMAL_END = &hFC ' try plugin using new interface first feedItems = [] retVal = Eval(m.parser$ + "(filePath$, feedItems, m.bsp)") if retVal = ERR_NORMAL_END then ' success using updated interface. extract data and convert to structures used by other parts of autorun index% = 0 for each feedItem in feedItems m.articleTitles[index%] = feedItem.key m.articles[index%] = feedItem.url m.articlesByTitle.AddReplace(feedItem.key, feedItem.url) m.articleHashTypes[index%] = feedItem.hashType m.articleHashes[index%] = feedItem.hash index% = index% + 1 next else ' failure using new interface: try old interface retVal = Eval(m.parser$ + "(filePath$, m.articles, m.articlesByTitle, userVariables)") if retVal <> ERR_NORMAL_END then ' log the failure m.bsp.diagnostics.PrintDebug("Failure invoking Eval to parse live text data feed: return value = " + stri(retVal) + ", parser is " + m.parser$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_LIVE_TEXT_PLUGIN_FAILURE, stri(retVal) + chr(9) + m.parser$) success = false endif endif return success End Function Function ParseJSONRSS(filePath$) as Boolean success = true jsonString=ReadAsciiFile(filePath$) json = ParseJSON(jsonString) numItemsAdded% = 0 for each jsonItem in json if type(m.isTwitterFeed) = "roBoolean" and m.isTwitterFeed then text$ = jsonItem.full_text else text$ = jsonItem.text endif m.articles.Push(text$) m.articleTitles.Push(text$) m.articlesByTitle.AddReplace(text$, text$) numItemsAdded% = numItemsAdded% + 1 if m.restrictNumberOfItems and (numItemsAdded% >= m.numberOfItemsToDisplay%) then exit for endif next if numItemsAdded% = 0 then success = false endif return success End Function Function ParseCustomContentFormat( filePath$ as String) success = true m.articles = CreateObject("roArray", 1, true) m.articleTitles = CreateObject("roArray", 1, true) m.articlesByTitle = CreateObject("roAssociativeArray") m.articleHashTypes = CreateObject("roArray", 1, true) m.articleHashes = CreateObject("roArray", 1, true) if m.parser$ <> "" then m.ParseRSSWithParserPlugin( filePath$) else if type(m.isJSON) = "roBoolean" and m.isJSON then success = m.ParseJSONRSS(filePath$) else xml = ReadAsciiFile( filePath$ ) if len(xml) = 0 then return false endif mrssFeedXML = CreateObject("roXMLElement") if not mrssFeedXML.Parse(xml) stop items = [] for each elt in mrssFeedXML.GetBody().Peek().GetBody() name = elt.GetName() if name = "item" then item = newCustomContentMRSSItem(elt) items.Push( item ) end if next ' convert to format required for content feed m.articles = CreateObject("roArray", 1, true) m.articleTitles = CreateObject("roArray", 1, true) m.articlesByTitle = CreateObject("roAssociativeArray") m.articleMediaTypes = CreateObject("roArray", 1, true) for each item in items m.articles.push(item.url) m.articleTitles.push(item.title) m.articlesByTitle.AddReplace(item.title, item.url) m.articleMediaTypes.push(item.medium) next endif endif return success End Function Function IsFeatureSupported(featureName$ as string, fwVersion$ as string, featureMinRevs as object) as boolean featureExists = featureMinRevs.DoesExist(featureName$) if featureExists then featureMinFWRev = featureMinRevs[featureName$] featureMinFWRevVSFWVersion% = CompareFirmwareVersions(featureMinFWRev, fwVersion$) if featureMinFWRevVSFWVersion% <= 0 then return true end if end if return false end function Function CompareFirmwareVersions(a$ as string, b$ as string) as integer start_a% = 0 start_b% = 0 while true if start_a% >= len(a$) then if start_b% >= len(b$) then return 0 else return -1 end if else if start_b% >= len(b$) then return 1 end if aChar$ = mid(a$, start_a% + 1, 1) a_digit = IsDigit(aChar$) bChar$ = mid(b$, start_b% + 1, 1) b_digit = IsDigit(bChar$) if a_digit and b_digit then ' Now we need to find the end of each of the sequences of digits. aa = { } aa.index = start_a% a_number% = ReadDigits(a$, aa) start_a% = aa.index bb = { } bb.index = start_b% b_number% = ReadDigits(b$, bb) start_b% = bb.index if a_number% < b_number% then return -1 else if a_number% > b_number% then return 1 end if else if a_digit then ' The first string has a digit but the second one has a ' non-digit so it must be greater. return 1 else if b_digit then return -1 else aChar$ = mid(a$, start_a% + 1, 1) bChar$ = mid(b$, start_b% + 1, 1) if asc(aChar$) < asc(bChar$) then return -1 else if asc(aChar$) > asc(bChar$) return 1 end if ' Otherwise we've dealt with this character start_a% = start_a% + 1 start_b% = start_b% + 1 end if end while end function Function IsDigit(a$ as string) as boolean if asc(a$) >= 48 and asc(a$) <= 57 then return true end if return false end function Function ReadDigits(s$ as string, aa as object) as integer value% = 0 index% = aa.index sChar$ = mid(s$, index% + 1, 1) while index% < len(s$) and IsDigit(sChar$) new_value% = value% * 10 + asc(sChar$) - asc("0") index% = index% + 1 value% = new_value% if len(s$) > index% then sChar$ = mid(s$, index% + 1, 1) end if end while aa.index = index% return value% end function 'endregion 'region Bluetooth/Beacons Function newBtManager() as object ' m is BSP btm = { bsp: m, btActive: false, beaconsSupported: false } btm.newBeacon = newBeacon btm.ParsePresentationBeacons = ParsePresentationBeacons btm.ResetPresentationBeacons = ResetPresentationBeacons btm.UpdatePersistentBeacons = UpdatePersistentBeacons btm.AddPersistentBeacon = AddPersistentBeacon btm.StartBeacon = StartBeacon btm.StopBeacon = StopBeacon btm.SetBtAdvertising = SetBtAdvertising btm.StartBtleClient = StartBtleClient btm.StopBtleClient = StopBtleClient btm.SetBtleStatus = SetBtleStatus btm.HandleEvent = btManager_HandleEvent btm.persistentBeacons = { } btm.presentationBeacons = { } btm.btleSupported = false btm.btleClientManager = invalid btm.btleClientServiceData = invalid btm.btleStatus% = 0 btm.btManager = CreateObject("roBtManager") if type(btm.btManager) = "roBtManager" then btm.btManager.SetPort(m.msgPort) btm.UpdatePersistentBeacons() endif if m.btleSupported then btm.btleSupported = true end if return btm end function Function btManager_HandleEvent(event as object) btEventType = event.GetEvent() if btEventType = "add-adapter" then m.bsp.diagnostics.PrintDebug("-- Bluetooth adapter added") m.btActive = true m.SetBtAdvertising() else if btEventType = "remove-adapter" then m.bsp.diagnostics.PrintDebug("-- Bluetooth adapter removed") m.btActive = false end if end function Function newBeacon(beaconXml as object, persistent as boolean) as object beacon = invalid beaconName$ = beaconXml.name.GetText() beaconType$ = beaconXml.type.GetText() beaconData = invalid if beaconType$ = "IBeacon" then beaconId = beaconXml.beaconId.GetText() ' BeaconId must be specified if IsString(beaconId) and Len(beaconId) > 0 then beaconId = LCase(beaconId) major% = Val(beaconXml.data1.GetText()) minor% = Val(beaconXml.data2.GetText()) txp% = Val(beaconXml.txlevel.GetText()) beaconData = { mode: "beacon", beacon_uuid: beaconId, beacon_major: major%, beacon_minor: minor%, beacon_level: txp%, persistent: persistent } end if else if beaconType$ = "EddystoneUrl" then url = beaconXml.beaconId.GetText() if IsString(url) then txp% = Val(beaconXml.txlevel.GetText()) beaconData = { mode: "eddystone-url", url: url, tx_power: txp%, persistent: persistent } end if else if beaconType$ = "EddystoneUid" then txp% = Val(beaconXml.txlevel.GetText()) beaconData = GetEddystoneUidBeaconData(beaconXml.beaconId.GetText(), beaconXml.data1.GetText(), txp%, persistent) end if if beaconData <> invalid then beacon = { name: beaconName$, type: beaconType$, data: beaconData, activate: true, isActive: false } if not persistent and beacon.autostart <> invalid then autostart = beaconXml.autostart.GetText() if not IsString(autostart) or LCase(autostart.Left(1)) <> "t" then beacon.activate = false end if end if end if return beacon end function Function ParsePresentationBeacons(beaconsXml as object) as boolean beacons = { } ' BACONTODO' '' if type(beaconsXml) = "roXMLList" and beaconsXml.Count() >= 1 then '' for each beaconXML in beaconsXml '' beacon = m.newBeacon(beaconXML, false) '' if beacon <> invalid then '' beacons.AddReplace(beacon.name, beacon) '' endif '' next '' endif m.presentationBeacons = beacons return not m.presentationBeacons.IsEmpty() end function Sub ResetPresentationBeacons() if not m.presentationBeacons.IsEmpty() then m.presentationBeacons = { } m.SetBtAdvertising() end if end sub Function GetEddystoneUidBeaconData(namespace as string, instance as string, txLevel% as integer, persistent as boolean) as object beaconData = invalid if Len(namespace) > 0 and Len(instance) > 0 then if LCase(namespace.Left(2)) = "0x" then namespace = namespace.mid(2) end if ns = CreateObject("roByteArray") ns.FromHexString(namespace) ' namespace string must have exactly ten bytes pad = 10 - ns.Count() for i = 1 to pad ns.Unshift(0) next if LCase(instance.Left(2)) = "0x" then instance = instance.mid(2) end if in = CreateObject("roByteArray") in.FromHexString(instance) ' instance string must have exactly six bytes pad = 6 - in.Count() for i = 1 to pad in.Unshift(0) next beaconData = { mode: "eddystone-uid", namespace: ns, instance: in, tx_power: txLevel%, persistent: persistent } end if return beaconData end function Sub UpdatePersistentBeacons() wasEmpty = m.persistentBeacons.IsEmpty() m.persistentBeacons = { } m.AddPersistentBeacon(GetGlobalAA().registrySettings.beacon1) m.AddPersistentBeacon(GetGlobalAA().registrySettings.beacon2) if not (m.persistentBeacons.IsEmpty() and wasEmpty) then ' Restart beacons to handle any persistent beacon changes m.SetBtAdvertising() end if end sub Function AddPersistentBeacon(beaconJson as string) as boolean success = false if IsString(beaconJson) and Len(beaconJson) > 0 then beaconInputData = ParseJson(beaconJson) if type(beaconInputData) = "roAssociativeArray" then beaconName$ = beaconInputData.Name beaconType% = beaconInputData.Type beaconType$ = "" beaconData = invalid if beaconType% = 0 then beaconId = LCase(beaconInputData.BeaconId) if Len(beaconId) > 0 then beaconType$ = "IBeacon" major% = Val(beaconInputData.Data1) minor% = Val(beaconInputData.Data2) txp% = beaconInputData.TxLevel beaconData = { mode: "beacon", beacon_uuid: beaconId, beacon_major: major%, beacon_minor: minor%, beacon_level: txp%, persistent: true } end if else if beaconType% = 1 then url = beaconInputData.BeaconId if Len(url) > 0 then beaconType$ = "EddystoneUrl" txp% = beaconInputData.TxLevel beaconData = { mode: "eddystone-url", url: url, tx_power: txp%, persistent: true } end if else if beaconType% = 2 then beaconType$ = "EddystoneUid" beaconData = GetEddystoneUidBeaconData(beaconInputData.BeaconId, beaconInputData.Data1, beaconInputData.TxLevel, true) end if if beaconData <> invalid then beacon = { name: beaconName$, type: beaconType$, data: beaconData, activate: true, isActive: false } m.persistentBeacons.AddReplace(beacon.name, beacon) end if end if end if return success end function Function StartBeacon(name as string) as boolean success = false beacon = m.presentationBeacons[name] if beacon <> invalid then beacon.activate = true success = m.SetBtAdvertising() end if return success end function Function StopBeacon(name as string) as boolean success = false beacon = m.presentationBeacons[name] if beacon <> invalid then beacon.activate = false success = m.SetBtAdvertising() end if return success end function Function StartBtleClient(clientParams as object, appId as string, txPower as integer) as boolean success = false if m.btleSupported then if m.btManager.GetAdapterList().Count() > 0 then m.bsp.diagnostics.PrintDebug("---- Starting BTLE Client manager") m.btleClientManager = CreateObject("roBtClientManager") m.btleClientManager.SetPort(m.bsp.msgPort) success = m.btleClientManager.Start(clientParams) if not success then m.bsp.diagnostics.PrintDebug("-- Start BTLE Client manager failed: " + m.btleClientManager.GetFailureReason()) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BTLE_START_FAILED, "Start BTLE Client manager failed: " + m.btleClientManager.GetFailureReason()) else ' remember service ID m.btleServiceId = clientParams.service_uuid ' set up service data array m.btleClientServiceData = CreateObject("roByteArray") m.btleClientServiceData.FromHexString(appId) if m.btleClientServiceData.Count() < 4 then while m.btleClientServiceData.Count() < 4 m.btleClientServiceData.Unshift(0) end while else if m.btleClientServiceData.Count() > 4 then while m.btleClientServiceData.Count() > 4 m.btleClientServiceData.Pop() end while end if m.btleClientServiceData.push(txPower) m.btleClientServiceData.push(0) ' restart advertising to set connectable flag for all beacons success = m.SetBtAdvertising() end if else m.bsp.diagnostics.PrintDebug("-- Start BTLE Client manager failed - there is no bluetooth adapter") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BTLE_START_FAILED, "Start BTLE Client manager failed - there is no bluetooth adapter") end if end if return success end function Function StopBtleClient(clientParams as object) as boolean success = false if m.btleClientManager then m.bsp.diagnostics.PrintDebug("---- Stopping BTLE Client manager") m.btleClientManager.Stop() m.btleClientManager = invalid m.btleClientServiceData = invalid m.btleServiceId = invalid ' restart advertising to reset connectable flag for all beacons success = m.SetBtAdvertising() end if return success end function Sub SetBtleStatus(status% as integer) newStatus% = status% and 255 if newStatus% <> m.btleStatus% then m.btleStatus% = newStatus% m.SetBtAdvertising() end if end sub Function SetBtAdvertising() success = false if m.beaconsSupported then ' We must have active bluetooth hardware if m.btManager.GetAdapterList().Count() > 0 then ' If a btClientManager is active, we need to set 'connectable' flag in all beacons isConnectable = false sd = invalid if m.btleClientManager <> invalid and m.btleServiceId <> invalid then isConnectable = true sd = { } sd.uuid = m.BtleServiceId sd.data = m.btleClientServiceData sd.data.SetEntry(5, m.btleStatus%) end if ' Get array of beacon data that should be active now beaconDataArray = [] for each beaconName in m.persistentBeacons beacon = m.persistentBeacons[beaconName] data = beacon.data data.connectable = isConnectable if sd <> invalid then sdlist = [sd] data.service_data = sdlist end if beaconDataArray.Push(data) next for each beaconName in m.presentationBeacons beacon = m.presentationBeacons[beaconName] if beacon.activate then if beaconDataArray.Count() < 5 then data = beacon.data data.connectable = isConnectable if sd <> invalid then sdlist = [sd] data.service_data = sdlist end if beaconDataArray.Push(data) else ' There is a limit of 5 beacons msg$ = "-- SetBtAdvertising - attempted to set more than 5 beacons - some beacons will not be started" m.bsp.diagnostics.PrintDebug(msg$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BEACON_START_LIMIT_EXCEEDED, msg$) exit for end if end if next ' If we have the BTLE client manager running, and there are no beacons defined, just advertise the client service if beaconDataArray.Count() = 0 and sd <> invalid then sdlist = [sd] data = { mode: "custom", connectable: true, service_data: sdlist } beaconDataArray.Push(data) end if for each beaconName in m.persistentBeacons m.persistentBeacons[beaconName].isActive = false next for each beaconName in m.presentationBeacons m.presentationBeacons[beaconName].isActive = false next if beaconDataArray.Count() > 0 then beaconMsgSpec$ = stri(beaconDataArray.Count()) + " beacons" if isConnectable then beaconMsgSpec$ = beaconMsgSpec$ + " (connectable, status =" + stri(m.btleStatus%) + ")" end if success = m.btManager.StartAdvertising(beaconDataArray) if success then m.bsp.diagnostics.PrintDebug("-- Set Bluetooth Advertising for" + beaconMsgSpec$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BEACON_START, "Set Bluetooth Advertising for" + beaconMsgSpec$) for each beaconName in m.persistentBeacons m.persistentBeacons[beaconName].isActive = true next for each beaconName in m.presentationBeacons beacon = m.presentationBeacons[beaconName] if beacon.activate then ' TODO - we shouldn't set active flag for beacons over the limit of 5 beacon.isActive = true end if next else beaconMsgSpec$ = beaconMsgSpec$ + ", reason: " + m.btManager.GetFailureReason() m.bsp.diagnostics.PrintDebug("-- Set Bluetooth Advertising failed for" + beaconMsgSpec$) m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BEACON_START_FAILED, "Set Bluetooth Advertising failed for" + beaconMsgSpec$) end if else success = m.btManager.StopAdvertising() m.bsp.diagnostics.PrintDebug("-- Stop all Bluetooth Advertising") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BEACON_START, "Stop all Bluetooth Advertising") end if else if not (m.persistentBeacons.IsEmpty() and m.presentationBeacons.IsEmpty()) then m.bsp.diagnostics.PrintDebug("-- Set Bluetooth Advertising failed - there is no bluetooth adapter") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_BEACON_START_FAILED, "Set Bluetooth Advertising failed - there is no bluetooth adapter") end if end if return success end function Function GetAutoscheduleFilePath(BSP as object) return GetPoolFilePath(BSP.assetPoolFiles, "autoschedule.json") end function Function GetAutoschedule(autoscheduleFilePath$) return m.jsonAutoschedule(autoscheduleFilePath$) end function Function GetAutoplayFileName(presentationName$) return "autoplay-" + presentationName$ + ".json" end function Function GetAutoplay(autoplayPath$) autoplayContainer = ParseJSON(ReadAsciiFile(autoplayPath$)) ' high level check for validity of autoplay file if type(autoplayContainer) <> "roAssociativeArray" or type(autoplayContainer.BrightAuthor) <> "roAssociativeArray" then print "Invalid autoplay file - name not BrightAuthor" stop end if autoplay = autoplayContainer.BrightAuthor if type(autoplay.version) <> "Integer" then print "Invalid JSON file - version not found" : stop return autoplay end function Function GetAutoplayVersion(autoplay) as integer version% = autoplay.version return version% end function Function GetBaconVersion(autoplay) as string if IsString(autoplay.BrightAuthorConnectedVersion) then return autoplay.BrightAuthorConnectedVersion end if return "unknown" end function Function ParseAutoplay(BrightAuthor as object, bsp as object) autoplay = { } meta = { } autoplay.meta = meta jsonParseAutoplay(BrightAuthor, meta, bsp) return autoplay end function Function ParseZones(bsp as object, BrightAuthor as object, Sign as object) return jsonParseZones(BrightAuthor, Sign) end function Function GetColor(colorAttrs as object) as integer alpha = colorAttrs["a"] red% = colorAttrs["r"] green% = colorAttrs["g"] blue% = colorAttrs["b"] color_spec% = (alpha * 256 * 256 * 256) + (red% * 256 * 256) + (green% * 256) + blue% return color_spec% end function ' region Schedule parsing' Sub GetStartingPresentation(schedule as object) ' get starting presentation schedule.GetActiveScheduledEvent = GetActiveScheduledEvent schedule.GetNextScheduledEventTime = GetNextScheduledEventTime schedule.activeScheduledEvent = schedule.GetActiveScheduledEvent(schedule.scheduledInterruptions) if type(schedule.activeScheduledEvent) <> "roAssociativeArray" then schedule.activeScheduledEvent = schedule.GetActiveScheduledEvent(schedule.scheduledEvents) end if if type(schedule.activeScheduledEvent) <> "roAssociativeArray" then schedule.nextScheduledEventTime = schedule.GetNextScheduledEventTime(schedule.allScheduledEvents) else ' determine when this scheduled event will end schedule.activeScheduledEventEndDateTime = invalid if schedule.activeScheduledEvent.interruption then schedule.activeScheduledEventEndDateTime = CopyDateTime(schedule.activeScheduledEvent.dateTime) schedule.activeScheduledEventEndDateTime.AddSeconds(schedule.activeScheduledEvent.duration% * 60) else endDateTime = invalid if not schedule.activeScheduledEvent.allDayEveryDay then endDateTime = CopyDateTime(schedule.activeScheduledEvent.dateTime) endDateTime.AddSeconds(schedule.activeScheduledEvent.duration% * 60) end if nextInterruptionStartTime = schedule.GetNextScheduledEventTime(schedule.scheduledInterruptions) if endDateTime = invalid then if nextInterruptionStartTime <> invalid then schedule.activeScheduledEventEndDateTime = nextInterruptionStartTime end if else if nextInterruptionStartTime = invalid then schedule.activeScheduledEventEndDateTime = endDateTime else if endDateTime.GetString() < nextInterruptionStartTime.GetString() then schedule.activeScheduledEventEndDateTime = endDateTime else schedule.activeScheduledEventEndDateTime = nextInterruptionStartTime end if end if end if end if end if end sub Function jsonAutoschedule(jsonFileName$ as string) autoScheduleJSON = ParseJSON(ReadAsciiFile(jsonFileName$)) schedule = jsonNewSchedule(autoScheduleJSON) if type(schedule.activeScheduledEvent) = "roAssociativeArray" then presentation$ = schedule.activeScheduledEvent.presentationName$ m.activePresentation$ = presentation$ autoplayFileName$ = "autoplay-" + presentation$ + ".json" ' find the autoplay file in the pool folder assetCollection = GetActiveSyncSpec().GetAssets("download") apf = CreateObject("roAssetPoolFiles", m.assetPool, assetCollection) autoplayPoolFile$ = apf.GetPoolFilePath(autoplayFileName$) if autoplayPoolFile$ = "" then stop schedule.autoplayPoolFile$ = autoplayPoolFile$ apf = invalid end if return schedule end function Function jsonNewSchedule(autoScheduleJSON as object) as object scheduledPresentations = autoScheduleJSON.scheduledPresentations numScheduledPresentations% = scheduledPresentations.Count() schedule = { } schedule.allScheduledEvents = CreateObject("roArray", numScheduledPresentations%, true) schedule.scheduledEvents = CreateObject("roArray", numScheduledPresentations%, true) schedule.scheduledInterruptions = CreateObject("roArray", 1, true) for each scheduledPresentation in scheduledPresentations scheduledPresentationBS = jsonNewScheduledEvent(scheduledPresentation) schedule.allScheduledEvents.push(scheduledPresentationBS) if scheduledPresentationBS.interruption then schedule.scheduledInterruptions.push(scheduledPresentationBS) else schedule.scheduledEvents.push(scheduledPresentationBS) end if next GetStartingPresentation(schedule) return schedule end function Function jsonNewScheduledEvent(scheduledEventJSON as object) as object scheduledEventBS = { } if type(scheduledEventJSON.presentationToSchedule) = "roAssociativeArray" then scheduledEventBS.presentationName$ = scheduledEventJSON.presentationToSchedule.name end if dateTime$ = scheduledEventJSON.dateTime scheduledEventBS.dateTime = FixDateTime(dateTime$) scheduledEventBS.duration% = scheduledEventJSON.duration if scheduledEventJSON.allDayEveryDay then scheduledEventBS.allDayEveryDay = true else scheduledEventBS.allDayEveryDay = false end if scheduledEventBS.recurrence = scheduledEventJSON.recurrence scheduledEventBS.recurrencePattern$ = scheduledEventJSON.recurrencePattern scheduledEventBS.recurrencePatternDaily$ = scheduledEventJSON.recurrencePatternDaily scheduledEventBS.recurrencePatternDaysOfWeek% = scheduledEventJSON.recurrencePatternDaysOfWeek ' TODO - bug that it's not written I think if not scheduledEventJSON.recurrenceStartDate = invalid then dateTime$ = scheduledEventJSON.recurrenceStartDate scheduledEventBS.recurrenceStartDate = FixDateTime(dateTime$) end if ' recurrence enabled but no valid start date, play all day every day if scheduledEventBS.recurrence and scheduledEventBS.recurrenceStartDate = invalid then validDateTime$ = helper_ValidateInvalidPrint(dateTime$) m.bsp.diagnostics.PrintDebug("Recurrence start date is missing or unparseable '" + validDateTime$ + "'. Scheduled event will run all day every day and not recur.") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_INVALID_DATE_TIME_SPEC, validDateTime$) scheduledEventBS.recurrence = false scheduledEventBS.allDayEveryDay = true end if ' date time invalid, all day every day, do not recur if scheduledEventBS.dateTime = invalid then validDateTime$ = helper_ValidateInvalidPrint(dateTime$) m.bsp.diagnostics.PrintDebug("Date Time is missing or unparseable '" + validDateTime$ + "'. Scheduled event will run all day every day.") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_INVALID_DATE_TIME_SPEC, validDateTime$) scheduledEventBS.allDayEveryDay = true scheduledEventBS.recurrence = false end if scheduledEventBS.recurrenceGoesForever = scheduledEventJSON.recurrenceGoesForever dateTime$ = scheduledEventJSON.recurrenceEndDate recurrenceEndDate = FixDateTime(dateTime$) if recurrenceEndDate <> invalid then recurrenceEndDate.AddSeconds(60 * 60 * 24) ' adjust the recurrence end date to refer to the beginning of the next day scheduledEventBS.recurrenceEndDate = recurrenceEndDate else ' no valid end date, assume the recurrence goes forever validDateTime$ = helper_ValidateInvalidPrint(dateTime$) m.bsp.diagnostics.PrintDebug("Could not parse recurrence end date '" + validDateTime$ + "'. Ignored.") m.bsp.logging.WriteDiagnosticLogEntry(m.bsp.diagnosticCodes.EVENT_INVALID_DATE_TIME_SPEC, validDateTime$) scheduledEventBS.recurrenceGoesForever = true end if ' TODO - bug that it's not initialized I think scheduledEventBS.interruption = false if not scheduledEventJSON.interruption = invalid then scheduledEventBS.interruption = scheduledEventJSON.interruption end if return scheduledEventBS end function Sub jsonParseAutoplay(BrightAuthor as object, meta as object, bsp as object) if IsBoolean(BrightAuthor.meta.disableSettingsHandler) then meta.enableSettingsHandler = not BrightAuthor.meta.disableSettingsHandler else meta.enableSettingsHandler = true endif meta.publishedModel = BrightAuthor.meta.model meta.name = BrightAuthor.meta.name meta.videoMode = BrightAuthor.meta.videoMode meta.forceResolution = BrightAuthor.meta.forceResolution meta.tenBitColorEnabled = BrightAuthor.meta.tenBitColorEnabled meta.dolbyVisionEnabled = BrightAuthor.meta.dolbyVisionEnabled meta.fullResGraphicsEnabled = BrightAuthor.meta.fullResGraphicsEnabled meta.stretchedVideoWall = [] meta.audioAutoLevel = BrightAuthor.meta.audioAutoLevel meta.videoConnector = BrightAuthor.meta.videoConnector meta.monitorOrientation = lcase(BrightAuthor.meta.monitorOrientation) meta.deviceWebPageDisplay = BrightAuthor.meta.deviceWebPageDisplay meta.customDeviceWebPage = BrightAuthor.meta.customDeviceWebPage meta.alphabetizeVariableNames = BrightAuthor.meta.alphabetizeVariableNames meta.htmlEnableJavascriptConsole = BrightAuthor.meta.htmlEnableJavascriptConsole meta.htmlEnableChromiumVideoPlayback = BrightAuthor.meta.htmlEnableChromiumVideoPlayback meta.backgroundScreenColor = GetColor(BrightAuthor.meta.backgroundScreenColor) meta.dontChangePresentationUntilMediaEndEventReceived = BrightAuthor.meta.delayScheduleChangeUntilMediaEndEvent meta.delayScheduleChangeUntilMediaEndEvent = BrightAuthor.meta.delayScheduleChangeUntilMediaEndEvent meta.languageKey = BrightAuthor.meta.languageKey 'BACONTODO - check case' meta.bp900AConfigureAutomatically = BrightAuthor.meta.buttonPanels["bp900a"].configureAutomatically meta.bp900BConfigureAutomatically = BrightAuthor.meta.buttonPanels["bp900b"].configureAutomatically meta.bp900CConfigureAutomatically = BrightAuthor.meta.buttonPanels["bp900c"].configureAutomatically meta.bp900DConfigureAutomatically = BrightAuthor.meta.buttonPanels["bp900d"].configureAutomatically meta.bp200AConfigureAutomatically = BrightAuthor.meta.buttonPanels["bp200a"].configureAutomatically meta.bp200BConfigureAutomatically = BrightAuthor.meta.buttonPanels["bp200b"].configureAutomatically meta.bp200CConfigureAutomatically = BrightAuthor.meta.buttonPanels["bp200c"].configureAutomatically meta.bp200DConfigureAutomatically = BrightAuthor.meta.buttonPanels["bp200d"].configureAutomatically meta.bp900AConfiguration% = BrightAuthor.meta.buttonPanels["bp900a"].configuration meta.bp900BConfiguration% = BrightAuthor.meta.buttonPanels["bp900b"].configuration meta.bp900CConfiguration% = BrightAuthor.meta.buttonPanels["bp900c"].configuration meta.bp900DConfiguration% = BrightAuthor.meta.buttonPanels["bp900d"].configuration meta.bp200AConfiguration% = BrightAuthor.meta.buttonPanels["bp200a"].configuration meta.bp200BConfiguration% = BrightAuthor.meta.buttonPanels["bp200b"].configuration meta.bp200CConfiguration% = BrightAuthor.meta.buttonPanels["bp200c"].configuration meta.bp200DConfiguration% = BrightAuthor.meta.buttonPanels["bp200d"].configuration ' remote configuration meta.irRemoteControl = BrightAuthor.meta.irRemote.irRemoteControl meta.irInConfiguration = BrightAuthor.meta.irRemote.irInConfiguration meta.irOutConfiguration = BrightAuthor.meta.irRemote.irOutConfiguration ' serial ports' meta.serialPortConfigurations = CreateObject("roArray", 1, true) for each serialPortConfigurationSpec in BrightAuthor.meta.serialPortConfigurations serialPortConfiguration = { } serialPortConfiguration.serialPortSpeed% = serialPortConfigurationSpec.baudRate serialPortConfiguration.protocol$ = serialPortConfigurationSpec.protocol serialPortConfiguration.sendEol$ = GetEolFromSpec(serialPortConfigurationSpec.sendEol) serialPortConfiguration.receiveEol$ = GetEolFromSpec(serialPortConfigurationSpec.receiveEol) serialPortConfiguration.invertSignals = serialPortConfigurationSpec.invertSignals dataBits$ = stri(serialPortConfigurationSpec.dataBits) parity$ = serialPortConfigurationSpec.parity stopBits$ = stri(serialPortConfigurationSpec.stopBits) serialPortConfiguration.serialPortMode = StripLeadingSpaces(dataBits$) + StripLeadingSpaces(parity$) + StripLeadingSpaces(stopBits$) port% = val(serialPortConfigurationSpec.port) serialPortConfiguration.port = port% connectedDevice$ = serialPortConfigurationSpec.connectedDevice if connectedDevice$ = "GPS" then serialPortConfiguration.gps = true else serialPortConfiguration.gps = false end if meta.serialPortConfigurations[port%] = serialPortConfiguration next ' parse parser plugins meta.parserPlugins = CreateObject("roArray", 1, true) for each parserPluginSpec in BrightAuthor.meta.parserPlugins parserPlugin = jsonParseParserPlugin(parserPluginSpec) meta.parserPlugins.push(parserPlugin) next ' first pass parse of user variables meta.userVariableSpecs = [] userVariablesJson = BrightAuthor.meta.userVariables for each userVariableJson in userVariablesJson userVariableSpec = { } userVariableSpec.name = userVariableJson.name userVariableSpec.defaultValue = userVariableJson.defaultValue userVariableSpec.access = userVariableJSON.access userVariableSpec.systemVariable$ = userVariableJson.systemVariable ' record networked information - parse in 2nd pass userVariableSpec.url = "" userVariableSpec.liveDataFeedId = userVariableJson.liveDataFeedId url$ = userVariableJson.url if url$ <> "" then userVariableSpec.url$ = url$ end if videoConnector$ = getVarFromObj(userVariableJson, "data.videoConnector", "String", "") if videoConnector$ <> "" userVariableSpec.videoConnector$ = videoConnector$ meta.userVariableSpecs.push(userVariableSpec) next ' parse live data feeds meta.liveDataFeedDescriptions = [] liveDataFeedsJson = BrightAuthor.meta.liveDataFeeds for each liveDataFeedJson in liveDataFeedsJson liveDataFeedDescription = jsonParseLiveDataFeed(liveDataFeedJson) meta.liveDataFeedDescriptions.push(liveDataFeedDescription) next meta.resetVariablesOnPresentationStart = BrightAuthor.meta.resetVariablesOnPresentationStart ' parse Node apps meta.nodeAppDescriptions = [] for each nodeAppJson in BrightAuthor.meta.nodeApps nodeAppDescription = jsonParseNodeApp(bsp, nodeAppJson) meta.nodeAppDescriptions.push(nodeAppDescription) next ' parse HTML sites meta.htmlSiteDescriptions = [] for each htmlSiteJson in BrightAuthor.meta.htmlSites htmlSiteDescription = jsonParseHtmlSite(bsp, htmlSiteJson) meta.htmlSiteDescriptions.push(htmlSiteDescription) next ' parse presentation identifiers meta.presentationIdentifiers = [] if type(BrightAuthor.meta.presentationIdentifiers) = "roArray" then for each presentationIdentifier in BrightAuthor.meta.presentationIdentifiers meta.presentationIdentifiers.push(presentationIdentifier) next end if ' parse beacons meta.beacons = [] ' get list of additional files to publish meta.additionalPublishedFiles = [] if type(BrightAuthor.meta.auxiliaryFiles) = "roArray" then for each auxiliaryFile in BrightAuthor.meta.auxiliaryFiles meta.additionalPublishedFiles.push(auxiliaryFile) next end if meta.boseProducts = BrightAuthor.meta.partnerProducts meta.wssDeviceSpec = BrightAuthor.meta.wssDeviceSpec meta.bmapSpecAssetName = BrightAuthor.meta.bmapSpecAssetName meta.udpReceivePort = BrightAuthor.meta.udpReceiverPort meta.udpSendPort = BrightAuthor.meta.udpDestinationPort meta.udpAddressType = BrightAuthor.meta.udpDestinationAddressType 'BACONTODO - check case' if meta.udpAddressType = "" then meta.udpAddressType = "IPAddress" meta.udpAddress = BrightAuthor.meta.udpDestinationAddress meta.enableEnhancedSynchronization = false meta.deviceIsSyncMaster = false meta.ptpDomain$ = "0" if type(BrightAuthor.meta.enableEnhancedSynchronization) = "roAssociativeArray" and BrightAuthor.meta.enableEnhancedSynchronization.deviceIsSyncMaster <> invalid then meta.enableEnhancedSynchronization = true meta.deviceIsSyncMaster = BrightAuthor.meta.enableEnhancedSynchronization.deviceIsSyncMaster meta.ptpDomain$ = StripLeadingSpaces(stri(BrightAuthor.meta.enableEnhancedSynchronization.ptpDomain)) else end if meta.flipCoordinates = BrightAuthor.meta.flipCoordinates meta.touchCursorDisplayMode = lcase(BrightAuthor.meta.touchCursorDisplayMode) meta.gpio = CreateObject("roArray", 8, true) for i% = 0 to 7 meta.gpio[i%] = BrightAuthor.meta.gpio[i%] next meta.audioConfiguration = BrightAuthor.meta.audioConfiguration audioSignPropertyMap = BrightAuthor.meta.audioSignPropertyMap meta.audio1MinVolume = audioSignPropertyMap["analog1"].min meta.audio1MaxVolume = audioSignPropertyMap["analog1"].max meta.hdmiMinVolume = audioSignPropertyMap["hdmi"].min meta.hdmiMaxVolume = audioSignPropertyMap["hdmi"].max meta.hdmi1MinVolume = getVarFromObj(audioSignPropertyMap, "hdmi1.min", "Integer", 0) meta.hdmi1MaxVolume = getVarFromObj(audioSignPropertyMap, "hdmi1.max", "Integer", 100) meta.hdmi2MinVolume = getVarFromObj(audioSignPropertyMap, "hdmi2.min", "Integer", 0) meta.hdmi2MaxVolume = getVarFromObj(audioSignPropertyMap, "hdmi2.max", "Integer", 100) meta.hdmi3MinVolume = getVarFromObj(audioSignPropertyMap, "hdmi3.min", "Integer", 0) meta.hdmi3MaxVolume = getVarFromObj(audioSignPropertyMap, "hdmi3.max", "Integer", 100) meta.hdmi4MinVolume = getVarFromObj(audioSignPropertyMap, "hdmi4.min", "Integer", 0) meta.hdmi4MaxVolume = getVarFromObj(audioSignPropertyMap, "hdmi4.max", "Integer", 100) meta.spdifMinVolume = audioSignPropertyMap["spdif"].min meta.spdifMaxVolume = audioSignPropertyMap["spdif"].max meta.usbTypeAMinVolume = audioSignPropertyMap["usbTypeA"].min meta.usbTypeAMaxVolume = audioSignPropertyMap["usbTypeA"].max meta.usbTypeCMinVolume = audioSignPropertyMap["usbTypeC"].min meta.usbTypeCMaxVolume = audioSignPropertyMap["usbTypeC"].max meta.usb700_1MinVolume = audioSignPropertyMap["usb700_1"].min meta.usb700_1MaxVolume = audioSignPropertyMap["usb700_1"].max meta.usb700_2MinVolume = audioSignPropertyMap["usb700_2"].min meta.usb700_2MaxVolume = audioSignPropertyMap["usb700_2"].max meta.usb700_3MinVolume = audioSignPropertyMap["usb700_3"].min meta.usb700_3MaxVolume = audioSignPropertyMap["usb700_3"].max meta.usb700_4MinVolume = audioSignPropertyMap["usb700_4"].min meta.usb700_4MaxVolume = audioSignPropertyMap["usb700_4"].max meta.usb700_5MinVolume = audioSignPropertyMap["usb700_5"].min meta.usb700_5MaxVolume = audioSignPropertyMap["usb700_5"].max meta.usb700_6MinVolume = audioSignPropertyMap["usb700_6"].min meta.usb700_6MaxVolume = audioSignPropertyMap["usb700_6"].max meta.usb700_7MinVolume = audioSignPropertyMap["usb700_7"].min meta.usb700_7MaxVolume = audioSignPropertyMap["usb700_7"].max meta.usb_1MinVolume = audioSignPropertyMap["usb_1"].min meta.usb_1MaxVolume = audioSignPropertyMap["usb_1"].max meta.usb_2MinVolume = audioSignPropertyMap["usb_2"].min meta.usb_2MaxVolume = audioSignPropertyMap["usb_2"].max meta.usb_3MinVolume = audioSignPropertyMap["usb_3"].min meta.usb_3MaxVolume = audioSignPropertyMap["usb_3"].max meta.usb_4MinVolume = audioSignPropertyMap["usb_4"].min meta.usb_4MaxVolume = audioSignPropertyMap["usb_4"].max meta.usb_5MinVolume = audioSignPropertyMap["usb_5"].min meta.usb_5MaxVolume = audioSignPropertyMap["usb_5"].max meta.usb_6MinVolume = audioSignPropertyMap["usb_6"].min meta.usb_6MaxVolume = audioSignPropertyMap["usb_6"].max meta.inactivityTimeout = BrightAuthor.meta.inactivityTimeout meta.inactivityTime = BrightAuthor.meta.inactivityTime meta.graphicsZOrder = BrightAuthor.meta.graphicsZOrder 'BACONTODO - check case' meta.isMosaic = false end sub Function jsonParseZones(BrightAuthor as object, Sign as object) zoneList = BrightAuthor.zones numZones% = zoneList.Count() zoneDescriptions = CreateObject("roArray", numZones%, true) for each zoneSpec in zoneList zoneDescription = jsonParseZoneSpec(zoneSpec, Sign) zoneDescriptions.push(zoneDescription) next return zoneDescriptions end function ' BACONTODO add support for AudioOnly' Function jsonParseZoneSpec(zoneSpec as object, sign as object) zoneDescription = { } '' common zone parameters zoneDescription.name$ = zoneSpec.name zoneDescription.originalWidth% = zoneSpec.absolutePosition.width zoneDescription.originalHeight% = zoneSpec.absolutePosition.height zoneDescription.x = zoneSpec.absolutePosition.x zoneDescription.y = zoneSpec.absolutePosition.y zoneDescription.width = zoneSpec.absolutePosition.width zoneDescription.height = zoneSpec.absolutePosition.height zoneDescription.type$ = zoneSpec.type zoneDescription.id$ = zoneSpec.id ' VideoOrImagesZone' if zoneDescription.type$ = "VideoOrImages" then zoneDescription.imageMode% = GetImageModeValue(zoneSpec.zoneSpecificParameters.imageMode) zoneDescription.numImageItems% = 0 end if ' Ticker ' if lcase(zoneDescription.type$) = "ticker" then zoneDescription.numberOfLines% = zoneSpec.zoneSpecificParameters.textWidget.numberOfLines zoneDescription.delay% = zoneSpec.zoneSpecificParameters.textWidget.delay zoneDescription.rotation% = 0 rotation$ = zoneSpec.zoneSpecificParameters.textWidget.rotation if rotation$ = "90" then zoneDescription.rotation% = 3 else if rotation$ = "180" then zoneDescription.rotation% = 2 else if rotation$ = "270" then zoneDescription.rotation% = 1 end if alignment$ = lcase(zoneSpec.zoneSpecificParameters.textWidget.alignment) if alignment$ = "center" then zoneDescription.alignment% = 1 else if alignment$ = "right" then zoneDescription.alignment% = 2 else zoneDescription.alignment% = 0 end if scrollingMethod$ = lcase(zoneSpec.zoneSpecificParameters.textWidget.scrollingMethod) zoneDescription.scrollingMethod% = 0 if scrollingMethod$ = "statictext" then zoneDescription.scrollingMethod% = 1 else if scrollingMethod$ = "scrolling" then zoneDescription.scrollingMethod% = 3 end if zoneDescription.scrollSpeed% = zoneSpec.zoneSpecificParameters.scrollSpeed widget = zoneSpec.zoneSpecificParameters.widget foregroundTextColor = widget.foregroundTextColor backgroundTextColor = widget.backgroundTextColor zoneDescription.foregroundTextColor% = GetColor(foregroundTextColor) zoneDescription.backgroundTextColor% = GetColor(backgroundTextColor) zoneDescription.font$ = widget.font if type(widget.backgroundBitmap) = "roAssociativeArray" then zoneDescription.backgroundBitmapFile$ = widget.backgroundBitmap.file zoneDescription.stretch = widget.backgroundBitmap.stretch else zoneDescription.backgroundBitmapFile$ = "" zoneDescription.stretch = false end if safeTextRegion = widget.safeTextRegion if type(safeTextRegion) = "roAssociativeArray" then zoneDescription.safeTextRegionX% = safeTextRegion.x zoneDescription.safeTextRegionY% = safeTextRegion.y zoneDescription.safeTextRegionWidth% = safeTextRegion.width zoneDescription.safeTextRegionHeight% = safeTextRegion.height end if end if ' Clock ' if lcase(zoneDescription.type$) = "clock" then rotation = zoneSpec.zoneSpecificParameters.rotation if rotation <> invalid and IsNonEmptyString(rotation) then zoneDescription.AddReplace("rotation", rotation) end if clockFormat = zoneSpec.zoneSpecificParameters.clockFormat if clockFormat <> invalid and IsNonEmptyString(clockFormat) then zoneDescription.AddReplace("clockFormat", clockFormat) end if widget = zoneSpec.zoneSpecificParameters.widget foregroundTextColor = widget.foregroundTextColor if type(foregroundTextColor) = "roAssociativeArray" then zoneDescription.AddReplace("foregroundTextColor", foregroundTextColor) end if backgroundTextColor = widget.backgroundTextColor if type(backgroundTextColor) = "roAssociativeArray" then zoneDescription.AddReplace("backgroundTextColor", backgroundTextColor) end if backgroundBitmapFileName = widget.backgroundBitmapFileName if backgroundBitmapFileName <> invalid and IsNonEmptyString(backgroundBitmapFileName) then filePath = GetPoolFilePath(m.bsp.assetPoolFiles, backgroundBitmapFileName) zoneDescription.AddReplace("backgroundBitmapFilePath", backgroundBitmapFileName) zoneDescription.AddReplace("stretchBitmapFile", widget.stretchBitmapFile) end if fontFileName = widget.fontFileName if fontFileName <> invalid and IsNonEmptyString(fontFileName) then filePath = GetPoolFilePath(m.bsp.assetPoolFiles, fontFileName) zoneDescription.AddReplace("fontFilePath", fontFileName) end if safeTextRegion = widget.safeTextRegion if type(safeTextRegion) = "roAssociativeArray" then zoneDescription.AddReplace("safeTextRegion", safeTextRegion) end if end if ' Video parameters' if zoneDescription.type$ = "VideoOrImages" or zoneDescription.type$ = "VideoOnly" then zoneDescription.viewMode% = GetViewModeValue(zoneSpec.zoneSpecificParameters.viewMode) zoneDescription.initialVideoVolume% = zoneSpec.zoneSpecificParameters.videoVolume zoneDescription.zOrderFront = zoneSpec.zoneSpecificParameters.zOrderFront end if ' Audio parameters' if zoneDescription.type$ = "VideoOrImages" or zoneDescription.type$ = "VideoOnly" or zoneDescription.type$ = "AudioOnly" or zoneDescription.type$ = "EnhancedAudio" then zoneDescription.audioMixMode$ = zoneSpec.zoneSpecificParameters.audioMixMode zoneDescription.analogOutput$ = zoneSpec.zoneSpecificParameters.analogOutput zoneDescription.hdmiOutput$ = zoneSpec.zoneSpecificParameters.hdmiOutput zoneDescription.hdmi1Output$ = getVarFromObj(zoneSpec, "zoneSpecificParameters.hdmi1Output", "String", "") zoneDescription.hdmi2Output$ = getVarFromObj(zoneSpec, "zoneSpecificParameters.hdmi2Output", "String", "") zoneDescription.hdmi3Output$ = getVarFromObj(zoneSpec, "zoneSpecificParameters.hdmi3Output", "String", "") zoneDescription.hdmi4Output$ = getVarFromObj(zoneSpec, "zoneSpecificParameters.hdmi4Output", "String", "") zoneDescription.spdifOutput$ = zoneSpec.zoneSpecificParameters.spdifOutput zoneDescription.usbOutputA$ = zoneSpec.zoneSpecificParameters.usbOutputA zoneDescription.usbOutputB$ = zoneSpec.zoneSpecificParameters.usbOutputB zoneDescription.usbOutputTypeA$ = zoneSpec.zoneSpecificParameters.usbOutputTypeA zoneDescription.usbOutputTypeC$ = zoneSpec.zoneSpecificParameters.usbOutputTypeC zoneDescription.usbOutput700_1$ = zoneSpec.zoneSpecificParameters.usbOutput700_1 zoneDescription.usbOutput700_2$ = zoneSpec.zoneSpecificParameters.usbOutput700_2 zoneDescription.usbOutput700_3$ = zoneSpec.zoneSpecificParameters.usbOutput700_3 zoneDescription.usbOutput700_4$ = zoneSpec.zoneSpecificParameters.usbOutput700_4 zoneDescription.usbOutput700_5$ = zoneSpec.zoneSpecificParameters.usbOutput700_5 zoneDescription.usbOutput700_6$ = zoneSpec.zoneSpecificParameters.usbOutput700_6 zoneDescription.usbOutput700_7$ = zoneSpec.zoneSpecificParameters.usbOutput700_7 zoneDescription.usbOutput_1$ = zoneSpec.zoneSpecificParameters.usbOutput_1 zoneDescription.usbOutput_2$ = zoneSpec.zoneSpecificParameters.usbOutput_2 zoneDescription.usbOutput_3$ = zoneSpec.zoneSpecificParameters.usbOutput_3 zoneDescription.usbOutput_4$ = zoneSpec.zoneSpecificParameters.usbOutput_4 zoneDescription.usbOutput_5$ = zoneSpec.zoneSpecificParameters.usbOutput_5 zoneDescription.usbOutput_6$ = zoneSpec.zoneSpecificParameters.usbOutput_6 zoneDescription.minimumVolume% = zoneSpec.zoneSpecificParameters.minimumVolume zoneDescription.maximumVolume% = zoneSpec.zoneSpecificParameters.maximumVolume zoneDescription.initialAudioVolume% = zoneSpec.zoneSpecificParameters.audioVolume ' get if support multi screen to decide if we should use hdmi or hdmi-1 through hdmi-4 zoneDescription.hasMultiScreenOutputs = HasMultiScreenOutputs(sign) end if ' EnhancedAudioZone parameters if zoneDescription.type$ = "EnhancedAudio" then zoneDescription.fadeLength = zoneSpec.zoneSpecificParameters.fadeLength end if ' ImagesZone - BACONTODO - this seems inconsistent with xml version if zoneDescription.type$ = "Images" then zoneDescription.numImageItems% = 0 zoneDescription.imageMode% = GetImageModeValue(zoneSpec.zoneSpecificParameters.imageMode) end if ' BACONTODO - zone type independent? zoneDescription.playlist = jsonParsePlaylistSpec(zoneDescription, zoneSpec.playlist) return zoneDescription end function Function jsonParseNodeApp(bsp as object, nodeAppJson as object) as object nodeAppDescription = { } nodeAppDescription.name$ = nodeAppJson.name nodeAppDescription.prefix$ = nodeAppJson.prefix nodeAppDescription.filePath$ = nodeAppJson.fileName return nodeAppDescription end function Function jsonParseHtmlSite(bsp as object, htmlSiteJson as object) as object htmlSiteDescription = { } htmlSiteDescription.name$ = htmlSiteJson.name htmlSiteDescription.enableNode = htmlSiteJson.enableNode htmlSiteDescription.queryString = jsonParseParameterValue(htmlSiteJson.queryString) htmlSiteDescription.contentIsLocal = htmlSiteJson.contentIsLocal if htmlSiteDescription.contentIsLocal then htmlSiteDescription.prefix$ = htmlSiteJson.prefix htmlSiteDescription.filePath$ = htmlSiteJson.fileName else htmlSiteDescription.url = jsonParseParameterValue(htmlSiteJson.url) end if return htmlSiteDescription end function Function jsonParsePlaylistSpec(zoneDescription as object, playlistSpec as object) playlistDescription = { } playlistDescription.name = playlistSpec.name playlistDescription.type = playlistSpec.type playlistDescription.initialMediaStateName = playlistSpec.initialMediaStateName ' playlistSpec.states playlistDescription.stateDescriptions = [] for each stateSpec in playlistSpec.states stateDescription = jsonParseState(stateSpec) playlistDescription.stateDescriptions.push(stateDescription) next ' playlistSpec.transitions playlistDescription.transitionDescriptions = [] for each transitionSpec in playlistSpec.transitions transitionDescription = jsonParseTransition(transitionSpec) playlistDescription.transitionDescriptions.push(transitionDescription) next return playlistDescription end function Function jsonParseStreamItem(streamItemSpec as object) streamItemSpec.url = jsonParseParameterValue(streamItemSpec.url) return streamItemSpec end function Function jsonParseState(stateSpec as object) stateDescription = { } stateDescription.name = stateSpec.name if type(stateSpec.imageItem) = "roAssociativeArray" then stateDescription.imageItem = stateSpec.imageItem if type(stateSpec.imageItem.slideTransition) = "roString" then stateDescription.imageItem.slideTransition% = GetSlideTransitionValue(stateSpec.imageItem.slideTransition) else stateDescription.imageItem.slideTransition% = 0 end if stateDescription.type = "image" else if type(stateSpec.videoItem) = "roAssociativeArray" then stateDescription.videoItem = stateSpec.videoItem stateDescription.type = "video" else if type(stateSpec.audioItem) = "roAssociativeArray" then stateDescription.audioItem = stateSpec.audioItem stateDescription.type = "audio" else if type(stateSpec.html5Item) = "roAssociativeArray" then stateDescription.html5Item = stateSpec.html5Item stateDescription.html5Item.name$ = stateSpec.name stateDescription.html5Item.htmlSiteName$ = stateSpec.html5Item.htmlSiteName stateDescription.type = "html5" else if type(stateSpec.rssDataFeedPlaylistItem) = "roAssociativeArray" then stateDescription.rssDataFeedPlaylistItem = jsonParseRSSItem(stateSpec.rssDataFeedPlaylistItem) stateDescription.type = "rss" else if type(stateSpec.liveVideoItem) = "roAssociativeArray" then stateDescription.liveVideoItem = stateSpec.liveVideoItem stateDescription.type = "liveVideo" else if type(stateSpec.videoStreamItem) = "roAssociativeArray" then stateDescription.videoStreamItem = jsonParseStreamItem(stateSpec.videoStreamItem) stateDescription.type = "videoStream" else if type(stateSpec.audioStreamItem) = "roAssociativeArray" then stateDescription.audioStreamItem = jsonParseStreamItem(stateSpec.audioStreamItem) stateDescription.type = "audioStream" else if type(stateSpec.mjpegStreamItem) = "roAssociativeArray" then stateDescription.mjpegStreamItem = jsonParseStreamItem(stateSpec.mjpegStreamItem) stateDescription.type = "mjpegStream" else if type(stateSpec.mrssDataFeedItem) = "roAssociativeArray" then stateDescription.mrssDataFeedPlaylistItem = stateSpec.mrssDataFeedItem stateDescription.mrssDataFeedPlaylistItem.slideTransition% = 0 ' TODO stateDescription.type = "mrssDataFeed" else if type(stateSpec.backgroundImageItem) = "roAssociativeArray" then stateDescription.backgroundImageItem = stateSpec.backgroundImageItem stateDescription.type = "backgroundImage" else if type(stateSpec.textItem) = "roAssociativeArray" then stateDescription.textItem = stateSpec.textItem stateDescription.type = "textItem" else if type(stateSpec.mediaListItem) = "roAssociativeArray" then stateDescription.mediaListItem = stateSpec.mediaListItem stateDescription.type = "mediaList" else if type(stateSpec.playFileItem) = "roAssociativeArray" then stateDescription.playFileItem = stateSpec.playFileItem stateDescription.type = "playFile" else if type(stateSpec.templatePlaylistItem) = "roAssociativeArray" then stateDescription.templatePlaylistItem = stateSpec.templatePlaylistItem stateDescription.type = "template" else if type(stateSpec.twitterItem) = "roAssociativeArray" then stateDescription.twitterItem = stateSpec.twitterItem stateDescription.type = "twitter" else if type(stateSpec.eventHandlerItem) = "roAssociativeArray" then stateDescription.eventHandlerItem = stateSpec.eventHandlerItem stateDescription.type = "eventHandler" else if type(stateSpec.superStateItem) = "roAssociativeArray" then stateDescription.superStateItem = stateSpec.superStateItem stateDescription.type = "superState" else if type(stateSpec.userVariableInTickerItem) = "roAssociativeArray" then stateDescription.userVariableInTickerItem = stateSpec.userVariableInTickerItem stateDescription.type = "userVariableInTickerItem" end if stateDescription.brightSignCmd = stateSpec.entryCommands stateDescription.brightSignExitCommands = stateSpec.exitCommands return stateDescription end function Function jsonParseRSSItem(playlistItemJson as object) as object rssPlaylistItemDescription = { } rssPlaylistItemDescription.liveDataFeedId$ = playlistItemJson.liveDataFeedId return rssPlaylistItemDescription end function Function getStringFromJsonParameter(parameter as object) as string if parameter = invalid then return "" end if return parameter end function Function jsonParseTransition(transitionSpec as object) transitionDescription = { } transitionDescription.sourceMediaState = getStringFromJsonParameter(transitionSpec.sourceMediaState) transitionDescription.targetMediaState = getStringFromJsonParameter(transitionSpec.targetMediaState) transitionDescription.commands = transitionSpec.commands transitionDescription.assignInputToUserVariable = transitionSpec.assignInputToUserVariable if transitionSpec.assignInputToUserVariable then transitionDescription.variableToAssignFromInput$ = transitionSpec.variableToAssignFromInput else transitionDescription.variableToAssignFromInput$ = "" end if transitionDescription.assignWildcardToUserVariable = transitionSpec.assignWildcardToUserVariable if transitionSpec.assignWildcardToUserVariable then transitionDescription.variableToAssign$ = transitionSpec.variableToAssignFromWildcard else transitionDescription.variableToAssign$ = "" end if targetAction = GetTargetActionFromEventAction(transitionSpec.eventAction) transitionDescription.targetMediaStateIsPreviousState = targetAction.targetMediaStateIsPreviousState transitionDescription.remainOnCurrentStateActions = targetAction.remainOnCurrentStateActions transitionDescription.conditionalTransitions = transitionSpec.conditionalTransitions transitionDescription.userEvent = transitionSpec.userEvent eventName = transitionSpec.userEvent.name eventData = transitionSpec.userEvent.data if eventName = "rectangularTouchEvent" then eventData.x = eventData.region.x eventData.y = eventData.region.y eventData.width = eventData.region.width eventData.height = eventData.region.height else if eventName = "bp900AUserEvent" or eventName = "bp900BUserEvent" or eventName = "bp900CUserEvent" or eventName = "bp200AUserEvent" or eventName = "bp200BUserEvent" or eventName = "bp200CUserEvent" then bpIndex$ = eventData.bpIndex if bpIndex$ = "a" then eventData.buttonPanelIndex% = 0 else if bpIndex$ = "b" then eventData.buttonPanelIndex% = 1 else if bpIndex$ = "c" then eventData.buttonPanelIndex% = 2 else eventData.buttonPanelIndex% = 3 end if eventData.buttonNumber$ = StripLeadingSpaces(stri(eventData.buttonNumber)) if type(eventData.pressContinuous) = "roAssociativeArray" then eventData.configuration$ = "pressContinuous" eventData.initialHoldoff$ = StripLeadingSpaces(stri(eventData.pressContinuous.initialHoldoff)) eventData.repeatInterval$ = StripLeadingSpaces(stri(eventData.pressContinuous.repeatInterval)) else eventData.configuration$ = "press" end if else if eventName = "gpioUserEvent" then eventData.buttonDirection$ = lcase(eventData.buttonDirection) eventData.buttonNumber$ = StripLeadingSpaces(stri(eventData.buttonNumber)) if type(eventData.pressContinuous) = "roAssociativeArray" then eventData.configuration$ = "pressContinuous" eventData.initialHoldoff$ = StripLeadingSpaces(stri(eventData.pressContinuous.initialHoldoff)) eventData.repeatInterval$ = StripLeadingSpaces(stri(eventData.pressContinuous.repeatInterval)) else eventData.configuration$ = "press" end if end if return transitionDescription end function Function jsonParseParameterValue(parameterValueJson as object) as object parameterValueDescription = { } parameterValueItems = [] for each parameterValueItemSpec in parameterValueJson parameterValueItem = { } parameterValueItem.type = parameterValueItemSpec.type parameterValueItem.value = parameterValueItemSpec.value parameterValueItems.push(parameterValueItemSpec) next parameterValueDescription.parameterValueItems = parameterValueItems return parameterValueDescription end function Function newParameterValue(bsp as object, parameterValueDescription as object) as object parameterValue = { } parameterValue.GetCurrentParameterValue = GetCurrentParameterValue parameterValue.GetVariableName = GetVariableName parameterValue.parameterValueItems = [] if type(parameterValueDescription) = "roAssociativeArray" then parameterValueItems = parameterValueDescription.parameterValueItems if parameterValueItems.count() = 0 then parameterValue.parameterValueItems.push(newParameterValueItemText("")) else for each parameterValueItem in parameterValueItems if lcase(parameterValueItem.type) = "text" then parameterValue.parameterValueItems.push(newParameterValueItemText(parameterValueItem.value)) else if lcase(parameterValueItem.type) = "uservariable" then parameterValue.parameterValueItems.push(jsonNewParameterValueItemUserVariable(bsp, parameterValueItem)) end if next end if end if return parameterValue end function Function jsonNewParameterValueItemUserVariable(bsp as object, parameterValueItemJsonUserVariable) as object parameterValueItem = { } parameterValueItem.GetCurrentValue = GetCurrentUserVariableParameterValue parameterValueItem.type$ = "userVariable" userVariableName$ = parameterValueItemJsonUserVariable.value parameterValueItem.userVariable = bsp.GetUserVariable(userVariableName$) if type(parameterValueItem.userVariable) <> "roAssociativeArray" then bsp.diagnostics.PrintDebug("User variable " + userVariableName$ + " not found.") bsp.logging.WriteDiagnosticLogEntry(bsp.diagnosticCodes.EVENT_USER_VARIABLE_NOT_FOUND, userVariableName$) end if return parameterValueItem end function Function jsonGetTotalSpaceRequired(filesToCopy as object, deletionCandidates as object) as object filesToPublish$ = ReadAsciiFile("filesToPublish.json") if filesToPublish$ = "" then stop ' create the list of files that need to be copied. this is the list of files in filesToPublish that are not in listOfPoolFiles filesToPublish = ParseJson(filesToPublish$) ' determine total space required totalSpaceRequired! = 0 for each file in filesToPublish.file fullFileName$ = file.fullFileName o = deletionCandidates.Lookup(fullFileName$) if not IsString(o) then ' file is not already on the card fileItem = { } fileItem.fileName$ = file.fileName fileItem.filePath$ = file.filePath fileItem.hashValue$ = file.hashValue fileItem.fileSize$ = file.fileSize filesToCopy.AddReplace(fullFileName$, fileItem) ' files that need to be copied to the card fileSize% = val(fileItem.fileSize$) totalSpaceRequired! = totalSpaceRequired! + fileSize% end if next filesToPublish = invalid return totalSpaceRequired! end function Sub jsonGetFilesToDelete(deletionCandidates as object, oldLocationDeletionCandidates as object) stop ' not implemented yet filesToPublish$ = ReadAsciiFile("filesToPublish.json") stop end sub Function jsonParseLiveDataFeed(liveDataFeedJson) liveDataFeedDescription = { } liveDataFeedDescription.id$ = liveDataFeedJson.id liveDataFeedDescription.isLiveBSNDataFeed = false liveDataFeedDescription.isLiveMediaFeed = false liveDataFeedDescription.isDynamicPlaylist = false if liveDataFeedJson.type = "BSNDataFeed" then liveDataFeedDescription.isLiveBSNDataFeed = true else if liveDataFeedJson.type = "BSNMediaFeed" then liveDataFeedDescription.isLiveMediaFeed = true else if liveDataFeedJson.type = "BSNDynamicPlaylist" then liveDataFeedDescription.isDynamicPlaylist = true else if liveDataFeedJson.type = "BSNTaggedPlaylist" then liveDataFeedDescription.isLiveMediaFeed = true end if if IsString(liveDataFeedJson.url) then liveDataFeedDescription.urlPV = newTextParameterValue(liveDataFeedJson.url) else ' BACONTODO - review carefully' liveDataFeedDescription.urlPV = jsonParseParameterValue(liveDataFeedJson.url) '' liveDataFeedDescription.urlPV = jsonParseParameterValue(liveDataFeedJson.url.params) ' liveDataFeedDescription.urlPV = liveDataFeedJson.url ' xml saves it as urlSpec$' end if liveDataFeedDescription.parserPluginName = liveDataFeedJson.parserPluginName liveDataFeedDescription.uvParserPluginName = "" liveDataFeedDescription.updateInterval% = liveDataFeedJson.updateInterval liveDataFeedDescription.useHeadRequest = liveDataFeedJson.useHeadRequest liveDataFeedDescription.usage$ = lcase(liveDataFeedJson.usage) liveDataFeedDescription.autoGenerateUserVariables = liveDataFeedJson.autoGenerateUserVariables liveDataFeedDescription.userVariableAccess$ = lcase(liveDataFeedJson.userVariableAccess) return liveDataFeedDescription end function Function syncSpecValueTrue(syncSpecValue$) as boolean if lcase(syncSpecValue$) = "yes" or syncSpecValue$ = "1" or lcase(syncSpecValue$) = "true" then return true end if return false end function Function syncSpecValueFalse(syncSpecValue$) as boolean if lcase(syncSpecValue$) = "no" or syncSpecValue$ = "0" or lcase(syncSpecValue$) = "false" then return true end if return false end function Function GetSynchronizerFilesToTransfer(userData as object, e as object) mVar = userData.mVar MoveFile(e.GetRequestBodyFile(), "filesInSite.json") filesToTransfer = GetDifferentOrMissingFiles() jsonStr$ = FormatJson(filesToTransfer) e.SetResponseBodyString(jsonStr$) e.SendResponse(200) end function Sub SynchronizerFilePosted(userData as object, e as object) mVar = userData.mVar destinationFilename = e.GetRequestHeader("Destination-Filename") ok = MoveFile(e.GetRequestBodyFile(), destinationFilename) if not ok then regex = CreateObject("roRegEx", "/", "i") parts = regex.Split(destinationFilename) if parts.Count() > 1 then dirName$ = "" for i% = 0 to (parts.Count() - 2) dirName$ = dirName$ + parts[i%] + "\" ' check to see if directory already exits dir = CreateObject("roReadFile", dirName$) if type(dir) <> "roReadFile" then ok = CreateDirectory(dirName$) if not ok then stop end if end if next ' directories have been created - try again ok = MoveFile(e.GetRequestBodyFile(), destinationFilename) if ok then print "Move successful after directory creation" end if end if end if if not ok then stop end if e.SetResponseBodyString("RECEIVED") e.SendResponse(200) end sub Function GetDifferentOrMissingFiles() as object filesToTransfer = [] filesInSite$ = ReadAsciiFile("filesInSite.json") if filesInSite$ = "" then stop filesInSite = ParseJson(filesInSite$) for each fileInSiteO in filesInSite.file fileInSite = { } fileInSite.name = fileInSiteO.fileName fileInSite.relativePath = fileInSiteO.filePath fileInSite.sha1 = fileInSiteO.hashValue fileInSite.size = fileInSiteO.fileSize fileOnCardIdentical = false file = CreateObject("roReadFile", fileInSite.relativePath) if type(file) = "roReadFile" then file.SeekToEnd() size% = file.CurrentPosition() if size% > 0 then ' file exists on card and is non zero size - see if it is the same file if size% = fileInSite.size then ' size is identical, check sha1 sha1 = GetSHA1(fileInSite.relativePath) if lcase(sha1) = lcase(fileInSite.sha1) then ' sha1 is identical - files are the same fileOnCardIdentical = true end if end if end if end if ' if the file is not on the card or is different, add it to the list of files that need to be downloaded if not fileOnCardIdentical then filesToTransfer.push(fileInSite) end if next return filesToTransfer end function Function GetSHA1(path as string) as string ba = CreateObject("roByteArray") ok = ba.ReadFile(path) hashGen = CreateObject("roHashGenerator", "SHA1") return hashGen.hash(ba).ToHexString() end function Sub RestartScript(userData as object, e as object) RestartScript() end sub 'endregion 'region MediaList REM ******************************************************* REM ******************************************************* REM *************** ******************** REM *************** MediaList ******************** REM *************** ******************** REM ******************************************************* REM ******************************************************* REM REM Sub newMediaListPlaylistItem(bsp as object, sign as object, playlistItemDescription as object, zoneHSM as object, state as object, playlistItemBS as object) if playlistItemDescription.playbackType = "FromBeginning" then state.playFromBeginning = true else state.playFromBeginning = false end if state.shuffle = playlistItemDescription.shuffle if type(playlistItemDescription.inactivityTimeout) = "Boolean" then state.inactivityTimeout = playlistItemDescription.inactivityTimeout else state.inactivityTimeout = false endif state.inactivityTime = playlistItemDescription.inactivityTime state.slideTransition% = GetSlideTransitionValue(playlistItemDescription.transitionEffect.transitionType) state.transitionDuration% = playlistItemDescription.transitionEffect.transitionDuration state.sendZoneMessage = playlistItemDescription.sendZoneMessage if type(playlistItemDescription.startIndex) = "Integer" and playlistItemDescription.startIndex > 0 then state.specifiedStartIndex% = playlistItemDescription.startIndex - 1 else state.specifiedStartIndex% = 0 end if state.startIndex% = state.specifiedStartIndex% state.populateFromMediaLibrary = not playlistItemDescription.useDataFeed liveDataFeedId$ = playlistItemDescription.dataFeedId if liveDataFeedId$ <> "" then state.liveDataFeed = bsp.liveDataFeeds.Lookup(CleanName(liveDataFeedId$)) else state.liveDataFeed = invalid end if ' parse BrightSignCmdsTransitionNextItem state.transitionNextItemCmds = [] transitionNextItemCmds = playlistItemDescription.transitionToNextCommands if transitionNextItemCmds.Count() > 0 then for each cmd in transitionNextItemCmds newCmd(bsp, cmd, state.transitionNextItemCmds) next end if ' parse BrightSignCmdsTransitionPreviousItem state.transitionPreviousItemCmds = [] transitionPreviousItemCmds = playlistItemDescription.transitionToPreviousCommands if transitionPreviousItemCmds.Count() > 0 then for each cmd in transitionPreviousItemCmds newCmd(bsp, cmd, state.transitionPreviousItemCmds) next end if ' Note - this is different from BA Classic. If the zone types supports images, then increment variable if zoneHSM.type$ = "VideoOrImages" or zoneHSM.type$ = "Images" then zoneHSM.numImageItems% = zoneHSM.numImageItems% + 1 end if state.transitionToNextEventList = [] for each transitionToNextEvent in playlistItemDescription.transitionToNextEventList eventName = transitionToNextEvent.name eventData = transitionToNextEvent.data transitionToNextEvent = { } transitionToNextEvent.eventName = eventName transitionToNextEvent.eventData = eventData state.transitionToNextEventList.push(transitionToNextEvent) next state.transitionToPreviousEventList = [] for each transitionToPreviousEvent in playlistItemDescription.transitionToPreviousEventList eventName = transitionToPreviousEvent.name eventData = transitionToPreviousEvent.data transitionToPreviousEvent = { } transitionToPreviousEvent.eventName = eventName transitionToPreviousEvent.eventData = eventData state.transitionToPreviousEventList.push(transitionToPreviousEvent) next ConfigureMediaListStateIO(bsp, sign, playlistItemDescription.transitionToNextEventList) ConfigureMediaListStateIO(bsp, sign, playlistItemDescription.transitionToPreviousEventList) if state.populateFromMediaLibrary then state.numItems% = playlistItemDescription.contentItems.count() state.items = CreateObject("roArray", state.numItems%, true) for each itemDescription in playlistItemDescription.contentItems item = { } item.type = itemDescription.type if item.type = "video" then newVideoPlaylistItem(bsp, itemDescription, state, item) else if item.type = "image" then newMediaPlaylistItem(bsp, itemDescription, state, item) item.slideTransition% = state.slideTransition% item.transitionDuration% = state.transitionDuration% else if item.type = "audio" or item.type = "audioItem" then newAudioPlaylistItem(bsp, itemDescription, state, item) end if state.items.push(item) next state.playbackIndices = CreateObject("roArray", state.numItems%, true) for i% = 0 to state.numItems% - 1 state.playbackIndices[i%] = i% next else state.numItems% = 0 end if state.playbackActive = false state.playbackIndex% = state.startIndex% state.HStateEventHandler = STDisplayingMediaListItemEventHandler state.PopulateMediaListFromLiveDataFeed = PopulateMediaListFromLiveDataFeed state.ShuffleMediaListContent = ShuffleMediaListContent state.ConfigureIntraStateEventHandlersButton = ConfigureIntraStateEventHandlersButton state.PrePlayAudio = PrePlayAudio state.PlayAudio = PlayAudio state.PostPlayAudio = PostPlayAudio state.PlayMixerAudio = PlayMixerAudio state.PrePlayVideo = PrePlayVideo state.PlayVideo = PlayVideo state.PostPlayVideo = PostPlayVideo state.PreDrawImage = PreDrawImage state.DrawImage = DrawImage state.PostDrawImage = PostDrawImage state.ClearVideo = ClearVideo state.StartInactivityTimer = StartInactivityTimer state.GetMatchingNavigationEvent = GetMatchingNavigationEvent state.HandleIntraStateEvent = HandleIntraStateEvent state.LaunchMediaListPlaybackItem = LaunchMediaListPlaybackItem state.GetTimeoutEvent = GetTimeoutEvent state.AdvanceMediaListPlayback = AdvanceMediaListPlayback state.RetreatMediaListPlayback = RetreatMediaListPlayback state.AddAudioTimeCodeEvent = AddAudioTimeCodeEvent state.SetAudioTimeCodeEvents = SetAudioTimeCodeEvents state.AddVideoTimeCodeEvent = AddVideoTimeCodeEvent state.SetVideoTimeCodeEvents = SetVideoTimeCodeEvents SetMediaItemEventHandlers(state) state.ExecuteTransition = ExecuteTransition state.MatchWssEvent = MatchWssEvent state.GetNextStateName = GetNextStateName state.UpdatePreviousCurrentStateNames = UpdatePreviousCurrentStateNames state.LaunchTimer = LaunchTimer state.PreloadItem = PreloadItem state.ConfigureBPButtons = ConfigureBPButtons state.ConfigureGPIOButtons = ConfigureGPIOButtons state.AtEndOfMediaList = AtEndOfMediaList end sub Function STDisplayingMediaListItemEventHandler(event as object, stateData as object) as object MEDIA_START = 3 MEDIA_END = 8 MEDIA_ERROR = 16 VIDEO_TIME_CODE = 12 stateData.nextState = invalid if type(event) = "roAssociativeArray" then ' internal message event if IsString(event["EventType"]) then if event["EventType"] = "ENTRY_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": entry signal") ' if using a live data feed, populate items here if type(m.liveDataFeed) = "roAssociativeArray" then ' ensure that data feed content has been loaded if type(m.liveDataFeed.assetPoolFiles) = "roAssetPoolFiles" then m.PopulateMediaListFromLiveDataFeed() end if end if if m.inactivityTimeout then m.inactivityTimer = CreateObject("roTimer") m.inactivityTimer.SetPort(m.bsp.msgPort) endif m.ConfigureIntraStateEventHandlersButton(m.transitionToNextEventList) m.ConfigureIntraStateEventHandlersButton(m.transitionToPreviousEventList) m.firstItemDisplayed = false ' prevent start index from pointing beyond the number of items in the case where m.playFromBeginning is false if m.numItems% > 0 and m.startIndex% >= m.numItems% then m.startIndex% = 0 end if ' reset playback index if appropriate if m.playFromBeginning then m.playbackIndex% = m.startIndex% end if if m.numItems% > 0 then m.playbackActive = true ' prevent start index from pointing beyond the number of items if m.playFromBeginning or m.playbackIndex% >= m.numItems% then if m.specifiedStartIndex% >= m.numItems% then m.startIndex% = 0 else m.startIndex% = m.specifiedStartIndex% end if m.playbackIndex% = m.startIndex% if not m.playFromBeginning then ' BCN-9609 m.bsp.diagnostics.PrintDebug("****** ------ STDisplayingMediaListItemEventHandler RESET PLAYBACK_INDEX") endif end if ' reshuffle media list if appropriate if m.playbackIndex% = m.startIndex% and m.shuffle then m.ShuffleMediaListContent() end if m.AdvanceMediaListPlayback(true, false) else m.playbackActive = false end if return "HANDLED" else if event["EventType"] = "VideoPlaybackFailureEvent" then if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) else if event["EventType"] = "AudioPlaybackFailureEvent" then if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if else if event["EventType"] = "CONTENT_DATA_FEED_LOADED" then if type(m.liveDataFeed) = "roAssociativeArray" and event["Name"] = m.liveDataFeed.id$ then m.PopulateMediaListFromLiveDataFeed() 'reset the playback index to the start point if m.specifiedStartIndex% >= m.numItems% then m.startIndex% = 0 else m.startIndex% = m.specifiedStartIndex% end if m.playbackIndex% = m.startIndex% if m.numItems% > 0 then if m.shuffle then m.ShuffleMediaListContent() end if if not m.playbackActive then m.playbackActive = true m.AdvanceMediaListPlayback(true, false) end if end if return "HANDLED" end if else if event["EventType"] = "EXIT_SIGNAL" then m.bsp.diagnostics.PrintDebug(m.id$ + ": exit signal") m.StartInactivityTimer() m.bsp.ExecuteMediaStateCommands(m.stateMachine, m.exitCmds) return "HANDLED" end if end if ' detect whether or not this is an event that indicates that the media list has completed a loop - if yes, act on it if there is a mediaListEnd event ' test with media end event on media list first else if m.AtEndOfMediaList(event) and type(m.mediaListEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.mediaListEndEvent, stateData, "") else if type(event) = "roVideoEvent" and type(m.stateMachine.videoPlayer) = "roVideoPlayer" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Video Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if m.transitionToNextEventList.count() > 0 then for each transitionToNextEvent in m.transitionToNextEventList if transitionToNextEvent.eventName = "mediaEnd" then ' equivalent to m.advanceOnMediaEnd in BrightAuthor if not(m.playbackIndex% = m.startIndex% and type(m.videoEndEvent) = "roAssociativeArray") then m.AdvanceMediaListPlayback(true, true) return "HANDLED" endif endif next endif if type(m.videoEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.videoEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) else if event.GetInt() = VIDEO_TIME_CODE then videoTimeCodeIndex$ = str(event.GetData()) m.bsp.diagnostics.PrintDebug("Video TimeCode Event " + videoTimeCodeIndex$) if type(m.videoTimeCodeEvents) = "roAssociativeArray" then videoTimeCodeEvent = m.videoTimeCodeEvents[videoTimeCodeIndex$] if type(videoTimeCodeEvent) = "roAssociativeArray" then m.bsp.ExecuteTransitionCommands(m.stateMachine, videoTimeCodeEvent) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "videoTimeCode", "", "1") return "HANDLED" end if end if m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "videoTimeCode", "", "0") end if else if m.stateMachine.type$ = "EnhancedAudio" and type(event) = "roAudioEventMx" then m.bsp.diagnostics.PrintDebug("AudioMx Event" + stri(event.GetInt())) if event.GetInt() = MEDIA_START then if event.GetSourceIdentity() = m.stateMachine.audioPlayer.GetIdentity() then ' index of track that just started playing currentTrackIndex% = int(val(event.GetUserData())) 'get index of track to queue m.playbackIndex% = currentTrackIndex% + 1 if m.playbackIndex% >= m.numItems% then m.playbackIndex% = 0 end if m.audioItem = m.items[m.playbackIndices[m.playbackIndex%]] 'send zone message for the current track if m.sendZoneMessage then item = m.items[m.playbackIndices[currentTrackIndex%]] fileNameWithoutExtension$ = item.filename$ 'if the file name has an extension, remove it before sending ext = GetFileExtension(item.filename$) if type(ext) = "roString" then index = instr(1, item.filename$, ext) if index > 2 then fileNameWithoutExtension$ = mid(item.filename$, 1, index - 2) end if end if ' send ZoneMessage using the file name as the message zoneMessageCmd = { } zoneMessageCmd["EventType"] = "SEND_ZONE_MESSAGE" zoneMessageCmd["EventParameter"] = fileNameWithoutExtension$ m.bsp.msgPort.PostMessage(zoneMessageCmd) end if if not(m.playbackIndex% = m.startIndex% and type(m.audioEndEvent) = "roAssociativeArray") then m.PlayMixerAudio(false, m.playbackIndex%, false) end if ' at this point, m.playbackIndex% points to both the item that is queued as well as the next item to play - the concept of ' "next item to play" is needed for NextNavigation, BackNavigation, and re-entering the state ' m.AdvanceMediaListPlayback(false) return "HANDLED" end if else if event.GetInt() = MEDIA_END then m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) end if else if m.stateMachine.type$ <> "EnhancedAudio" and IsAudioEvent(m.stateMachine, event) then if event.GetInt() = MEDIA_END then m.bsp.diagnostics.PrintDebug("Audio Event" + stri(event.GetInt())) m.bsp.logging.WriteEventLogEntry(m.stateMachine, m.id$, "mediaEnd", "", "1") if m.bsp.ProcessMediaEndEvent() then return "HANDLED" end if if m.transitionToNextEventList.count() > 0 then for each transitionToNextEvent in m.transitionToNextEventList if transitionToNextEvent.eventName = "mediaEnd" then ' equivalent to m.advanceOnMediaEnd in BrightAuthor if not(m.playbackIndex% = m.startIndex% and type(m.videoEndEvent) = "roAssociativeArray") then m.AdvanceMediaListPlayback(true, true) return "HANDLED" endif endif next endif if type(m.audioEndEvent) = "roAssociativeArray" then return m.ExecuteTransition(m.audioEndEvent, stateData, "") end if PostMediaEndEvent(m.bsp.msgPort) end if end if ' event received if m.transitionToNextEventList.count() > 0 then advance = m.HandleIntraStateEvent(event, m.transitionToNextEventList) if advance then m.AdvanceMediaListPlayback(true, true) return "HANDLED" end if end if if m.transitionToPreviousEventList.count() > 0 then retreat = m.HandleIntraStateEvent(event, m.transitionToPreviousEventList) if retreat then m.RetreatMediaListPlayback(true, true) return "HANDLED" end if end if return m.MediaItemEventHandler(event, stateData) end function Sub ConfigureMediaListStateIO(bsp as object, sign as object, eventList as object) for each event in eventList if event.name = "bp900AUserEvent" or event.name = "bp900BUserEvent" or event.name = "bp900CUserEvent" or event.name = "bp900DUserEvent" or event.name = "bp200AUserEvent" or event.name = "bp200BUserEvent" or event.name = "bp200CUserEvent" or event.name = "bp200DUserEvent" then bpUserEventButtonNumber$ = stri(event.data.buttonNumber) bpUserEventButtonPanelIndex$ = GetButtonPanelIndexFromBpIndex(event.data.bpIndex) bpIndex% = int(val(bpUserEventButtonPanelIndex$)) bsp.ConfigureBPInput(bpIndex%, bpUserEventButtonNumber$) else if event.name = "gpioUserEvent" then bsp.ConfigureGpioInput(stri(event.data.buttonNumber)) else if event.name = "serial" then bsp.CreateSerial(bsp, event.data.port, false) else if event.name = "udp" then bsp.CreateDatagramReceiver(sign.udpReceivePort) end if next end sub Sub PopulateMediaListFromLiveDataFeed() if type(m.liveDataFeed) = "roAssociativeArray" and type(m.liveDataFeed.assetPoolFiles) = "roAssetPoolFiles" then m.numItems% = m.liveDataFeed.itemUrls.Count() m.items = CreateObject("roArray", m.numItems%, true) for i% = 0 to m.liveDataFeed.itemUrls.Count() - 1 url = m.liveDataFeed.itemUrls[i%] fileName$ = m.liveDataFeed.fileKeys[i%] filePath$ = m.liveDataFeed.assetPoolFiles.GetPoolFilePath(url) item = { } item.fileName$ = fileName$ item.filePath$ = filePath$ item.isEncrypted = false if type(m.liveDataFeed.fileTypes) = "roArray" and m.liveDataFeed.fileTypes.Count() > i% then item.type = m.liveDataFeed.fileTypes[i%] end if if item.type = "image" then item.slideTransition% = 0 else if item.type = "video" then item.videoDisplayMode% = 0 end if m.items.push(item) next m.playbackIndices = CreateObject("roArray", m.numItems%, true) for i% = 0 to m.numItems% - 1 m.playbackIndices[i%] = i% next end if end sub Sub ShuffleMediaListContent() randomNumbers = CreateObject("roArray", m.numItems%, true) for each item in m.items randomNumbers.push(rnd(10000)) next numItemsToSort% = m.numItems% for i% = numItemsToSort% - 1 to 1 step -1 for j% = 0 to i% - 1 index0 = m.playbackIndices[j%] value0 = randomNumbers[index0] index1 = m.playbackIndices[j% + 1] value1 = randomNumbers[index1] if value0 > value1 then k% = m.playbackIndices[j%] m.playbackIndices[j%] = m.playbackIndices[j% + 1] m.playbackIndices[j% + 1] = k% end if next next end sub Function AtEndOfMediaList(event as object) as boolean MEDIA_END = 8 endOfMediaEvent = false if type(event) = "roVideoEvent" and type(m.stateMachine.videoPlayer) = "roVideoPlayer" and event.GetSourceIdentity() = m.stateMachine.videoPlayer.GetIdentity() and event.GetInt() = MEDIA_END then endOfMediaEvent = true else if m.stateMachine.type$ = "EnhancedAudio" and type(event) = "roAudioEventMx" and event.GetInt() = MEDIA_END then endOfMediaEvent = true else if m.stateMachine.type$ <> "EnhancedAudio" and IsAudioEvent(m.stateMachine, event) and event.GetInt() = MEDIA_END then endOfMediaEvent = true else if type(event) = "roTimerEvent" then ' if this is a timer event for some random timer, it's not necessarily the end of the media list ' fix for the case of a serial port retry timer - Bose issue. sourceIdentity$ = stri(event.getSourceIdentity()) if type(m.bsp.serialPortsToRetry) = "roAssociativeArray" then for each serialPortToRetryName in m.bsp.serialPortsToRetry serialPortToRetry = m.bsp.serialPortsToRetry[serialPortToRetryName] timer = serialPortToRetry.timer if stri(event.GetSourceIdentity()) = stri(timer.GetIdentity()) then return false endif next endif if (m.playbackIndex% <> m.startIndex% or type(m.mstimeoutEvent) <> "roAssociativeArray") then endOfMediaEvent = true end if endif if endOfMediaEvent and m.playbackIndex% = 0 then return true else return false end if end function 'endregion 'region LocalFileNetworking Sub PopulateDeviceConfigurationJson(mVar as object) as object out = { } modelObject = CreateObject("roDeviceInfo") out.AddReplace("model", modelObject.GetModel()) out.AddReplace("family", modelObject.GetFamily()) out.AddReplace("activePresentation", mVar.activePresentation$) ' TODO check with Ted as active presentation label attribute removed out.AddReplace("autorunVersion", mVar.sysInfo.autorunVersion$) out.AddReplace("firmwareVersion", mVar.sysInfo.deviceFWVersion$) out.AddReplace("functionality", mVar.lwsConfig$) out.AddReplace("serialNumber", mVar.sysInfo.deviceUniqueID$) out.AddReplace("snapshotId", invalid) out.AddReplace("snapshotOrientation", invalid) settings = GetActiveSettings() if settings.deviceScreenShotsEnabled then numSnapshots% = mVar.globalAA.listOfSnapshotFiles.Count() if numSnapshots% > 0 then latestSnapshot = mVar.globalAA.listOfSnapshotFiles[numSnapshots% - 1] index% = instr(1, latestSnapshot, ".jpg") if index% > 0 then id$ = mid(latestSnapshot, 1, index% - 1) out.AddReplace("snapshotId", id$) ' TODO check with Ted as snapshot id label attribute removed if IsNonEmptyString(settings.deviceScreenShotsOrientation) then out.AddReplace("snapshotOrientation", settings.deviceScreenShotsOrientation) else out.AddReplace("snapshotOrientation", "Landscape") end if end if end if end if out.AddReplace("unitName", settings.unitName) out.AddReplace("unitNamingMethod", settings.unitNamingMethod) out.AddReplace("unitDescription", settings.unitDescription) out.AddReplace("udpNotificationAddress", mVar.udpNotificationAddress$) out.AddReplace("udpNotifcationPort", StripLeadingSpaces(stri(mVar.udpNotificationPort%))) out.AddReplace("contentPort", "8008") if mVar.NetworkingIsActive() then out.AddReplace("bsnActive", true) ' TODO check with Ted for BA compatibility with boolean value else out.AddReplace("bsnActive", false) ' TODO check with Ted for BA compatibility with boolean value end if return out end sub Sub GetDeviceConfigurationJson(userData as object, e as object) mVar = userData.mVar respBody = PopulateDeviceConfigurationJson(mVar) e.AddResponseHeader("Content-type", "application/json") e.SetResponseBodyString(FormatJson(respBody)) e.SendResponse(200) end sub Sub PopulateDeviceStatusJson(mVar as object) as object out = { } modelObject = CreateObject("roDeviceInfo") out.AddReplace("uptime", modelObject.GetDeviceUptime()) ' TODO check with Ted as device update label attribute removed return out end sub Sub GetDeviceStatusJson(userData as object, e as object) mVar = userData.mVar respBody = PopulateDeviceStatusJson(mVar) e.AddResponseHeader("Content-type", "application/json") e.SetResponseBodyString(FormatJson(respBody)) e.SendResponse(200) end sub ' not called from Bacon (or anything else) as of 2020/Jan 8 Sub PopulateSnapshotConfigurationJson(mVar as object) as object stop ' ensure failure so that code can be updated as needed globalAA = GetGlobalAA() settings = GetActiveSettings() out = { } out.AddReplace("count", StripLeadingSpaces(stri(globalAA.listOfSnapshotFiles.Count()))) ' TODO check with Ted change count from attribute to value ' TODO check with Ted change Count > count if settings.deviceScreenShotsEnabled <> invalid then if settings.deviceScreenShotsEnabled then out.AddReplace("enabled", true) ' TODO check with Ted change enabled from attribute to value ' TODO check with Ted change Enabled > enabled ' TODO check with Ted change enabled value from boolean string to boolean else out.AddReplace("enabled", false) ' TODO check with Ted change enabled from attribute to value ' TODO check with Ted change Enabled > enabled ' TODO check with Ted change enabled value from boolean string to boolean end if end if if settings.deviceScreenShotsInterval <> invalid then out.AddReplace("interval", StripLeadingSpaces(stri(settings.deviceScreenShotsInterval))) ' TODO check with Ted change internval from attribute to value ' TODO check with Ted change Interval > interval else out.AddReplace("interval", invalid) end if if settings.deviceScreenShotsCountLimit <> invalid then out.AddReplace("maxImages", StripLeadingSpaces(stri(settings.deviceScreenShotsCountLimit))) ' TODO check with Ted change MaxImages > maxImages ' TODO check with Ted change maxImages from attribute to value else out.AddReplace("maxImages", invalid) end if if globalAA.deviceScreenShotsQuality <> invalid then out.AddReplace("quality", StripLeadingSpaces(stri(settings.deviceScreenShotsQuality))) ' TODO check with Ted change Quality > quality else out.AddReplace("quality", invalid) end if if mVar.videoMode <> invalid then out.AddReplace("resX", StripLeadingSpaces(stri(mVar.videoMode.GetOutputResX()))) ' TODO check with Ted change resX from attribute to value ' TODO check with Ted change ResX > resX out.AddReplace("resY", StripLeadingSpaces(stri(mVar.videoMode.GetOutputResY()))) ' TODO check with Ted change resY from attribute to value ' TODO check with Ted change ResY > resY else out.AddReplace("resX", invalid) out.AddReplace("resY", invalid) end if if IsNonEmptyString(settings.deviceScreenShotsOrientation) then out.AddReplace("orientation", settings.deviceScreenShotsOrientation) ' displayPortraitMode changed to orientation for BCN-6961 ' TODO check with Ted change displayPortraitMode from attribute to value ' TODO check with Ted change DisplayPortraitMode > displayPortraitMode ' TODO check with Ted change displayPortraitMode value from boolean string to boolean else out.AddReplace("orientation", "Landscape") ' displayPortraitMode changed to orientation for BCN-6961 ' TODO check with Ted change displayPortraitMode from attribute to value ' TODO check with Ted change DisplayPortraitMode > displayPortraitMode ' TODO check with Ted change displayPortraitMode value from boolean string to boolean end if ' CanConfigure attribute notifies clients if snapshot configuration can be managed by this unit out.AddReplace("canConfigure", true) ' TODO check with Ted change canConfigure from attribute to value ' TODO check with Ted change CanConfigure > canConfigure ' TODO check with Ted change canConfigure value from boolean string to boolean return out end sub ' not called from Bacon (or anything else) as of 2020/Jan 8 Function GetSnapshotConfigurationJson(userData as object, e as object) stop ' ensure failure so that code can be updated as needed mVar = userData.mVar globalAA = GetGlobalAA() startTimeSpecified = false startDT = CreateObject("roDateTime") startTime$ = e.GetRequestParam("startTime") ' TODO check with Ted change request param starttime > startTime if startTime$ <> "" then startTimeSpecified = startDT.FromIsoString(startTime$ + ".000") end if respBody = PopulateSnapshotConfigurationJson(mVar) e.AddResponseHeader("Content-type", "application/json") e.SetResponseBodyString(FormatJson(respBody)) e.SendResponse(200) end function ' not called from Bacon (or anything else) as of 2020/Jan 8 Sub PopulateSnapshotHistoryJson(mVar as object, startTimeSpecified as boolean, startTime as object) as object stop ' ensure failure so that code can be updated as needed globalAA = GetGlobalAA() out = { } out.AddReplace("snapshots", createObject("roArray", 0, true)) ' TODO check with Ted change BrightSignSnapshots > snapshots for each snapshotFile in globalAA.listOfSnapshotFiles ' get timestamp, id from file name index% = instr(1, snapshotFile, ".jpg") if index% > 0 then time$ = mid(snapshotFile, 1, index% - 1) snapshotTime = CreateObject("roDateTime") snapshotTime.FromIsoString(time$) if not startTimeSpecified or snapshotTime.GetString() >= startTime.GetString() then itemElem = { } 'item itemElem.AddReplace("id", time$) itemElem.AddReplace("time", time$) out.brightSignSnapshots.push(itemElem) end if end if next return out end sub ' not called from Bacon (or anything else) as of 2020/Jan 8 Function GetSnapshotHistoryJson(userData as object, e as object) stop ' ensure failure so that code can be updated as needed mVar = userData.mVar globalAA = GetGlobalAA() startTimeSpecified = false startDT = CreateObject("roDateTime") startTime$ = e.GetRequestParam("startTime") ' TODO check with Ted change request param starttime > startTime if startTime$ <> "" then startTimeSpecified = startDT.FromIsoString(startTime$ + ".000") end if respBody = PopulateSnapshotHistoryJson(mVar, startTimeSpecified, startDT) e.AddResponseHeader("Content-type", "application/json") e.SetResponseBodyString(FormatJson(respBody)) e.SendResponse(200) end function ' not called from Bacon (or anything else) as of 2020/Jan 8 Function GetSnapshotJson(userData as object, e as object) stop ' ensure failure so that code can be updated as needed mVar = userData.mVar globalAA = GetGlobalAA() listOfSnapshotFiles = globalAA.listOfSnapshotFiles snapshotID$ = e.GetRequestParam("id") ' TODO check with Ted ID > id if snapshotID$ <> "" then ' perform linear search to find image for each snapshotFile in listOfSnapshotFiles if snapshotFile = snapshotID$ + ".jpg" then e.AddResponseHeader("Content-type", "image/jpeg") e.SetResponseBodyFile("snapshots/" + snapshotID$ + ".jpg") e.SendResponse(200) return 0 end if next end if e.AddResponseHeader("Content-type", "text/plain") ' TODO check with Ted remove utf encoding if snapshotID$ <> "" then e.SetResponseBodyString("Snapshot file corresponding to id " + snapshotID$ + " not found") else e.SetResponseBodyString("Snapshot id not specified.") end if e.SendResponse(404) end function Sub PopulateCardSizeLimitsJson(mVar as object) as object out = { } out.AddReplace("limitStorageSpace", mVar.limitStorageSpace) if mVar.spaceLimitedByAbsoluteSize = "true" then out.AddReplace("limitStorageType", "Absolute") ' TODO synch with bauwdm types else if mVar.spaceLimitedByAbsoluteSize = "false" then out.AddReplace("limitStorageType", "Percent") ' TODO synch with bauwdm types else out.AddReplace("limitStorageType", invalid) end if out.AddReplace("publishDataAbsolute", mVar.publishedDataSizeLimitMB) out.AddReplace("publishDataPercent", mVar.publishedDataSizeLimitPercentage) out.AddReplace("dynamicDataAbsolute", mVar.dynamicDataSizeLimitMB) out.AddReplace("dynamicDataPercent", mVar.dynamicDataSizeLimitPercentage) out.AddReplace("htmlDataAbsolute", mVar.htmlDataSizeLimitMB) out.AddReplace("htmlDataPercent", mVar.htmlDataSizeLimitPercentage) out.AddReplace("htmlLocalStorageAbsolute", mVar.htmlLocalStorageSizeLimitMB) out.AddReplace("htmlLocalStoragePercent", mVar.htmlLocalStorageSizeLimitPercentage) out.AddReplace("htmlIndexedDbAbsolute", mVar.htmlIndexedDBSizeLimitMB) out.AddReplace("htmlIndexedDbPercent", mVar.htmlIndexedDBSizeLimitPercentage) return out end sub Sub GetCardSizeLimitsJson(userData as object, e as object) mVar = userData.mVar respBody = PopulateCardSizeLimitsJson(mVar) e.SetResponseBodyString(FormatJson(respBody)) e.SendResponse(200) end sub Sub PostCardSizeLimitsJson(userData as object, e as object) ' TODO check with Ted changed to post with post data passed as body mVar = userData.mVar args = e.GetFormData() if args.limitStorageEnabled <> invalid and lcase(args.limitStorageEnabled) = "true" then if args.limitStorageType <> invalid and lcase(args.limitStorageType) = "absolute" then if args.publishDataAbsolute <> invalid and args.dynamicDataAbsolute <> invalid and args.htmlDataAbsolute <> invalid and args.htmlLocalStorageAbsolute <> invalid and args.htmlIndexedDbAbsolute <> invalid then mVar.publishedDataSizeLimitMB = ConvertToInt(args.publishDataAbsolute) ' TODO check with Ted publishedDataSizeLimitMB > publishDataAbsolute mVar.dynamicDataSizeLimitMB = ConvertToInt(args.dynamicDataAbsolute) ' TODO check with Ted dynamicDataSizeLimitMB > dynamicDataAbsolute mVar.htmlDataSizeLimitMB = ConvertToInt(args.htmlDataAbsolute) ' TODO check with Ted htmlDataSizeLimitMB > htmlDataAbsolute mVar.htmlLocalStorageSizeLimitMB = ConvertToInt(args.htmlLocalStorageAbsolute) ' TODO check with Ted htmlLocalStorageSizeLimitMB > htmlLocalStorageAbsolute mVar.htmlIndexedDBSizeLimitMB = ConvertToInt(args.htmlDataAbsolute) ' TODO check with Ted htmlIndexedDBSizeLimitMB > htmlIndexedDbAbsolute else e.SendResponse(400) ' TODO check with Ted, add fallback for invalid request params instead of player crash return end if mVar.spaceLimitedByAbsoluteSize = "true" ' TODO check with Ted changed type from sting to boolean else if args.limitStorageType <> invalid and lcase(args.limitStorageType) = "percent" then if args.publishDataPercent <> invalid and args.dynamicDataPercent <> invalid and args.htmlDataPercent <> invalid and args.htmlLocalStoragePercent <> invalid and args.htmlIndexedDbPercent <> invalid then mVar.publishedDataSizeLimitPercentage = ConvertToInt(args.publishDataPercent) ' TODO check with Ted publishedDataSizeLimitPercentage > publishDataPercent mVar.dynamicDataSizeLimitPercentage = ConvertToInt(args.dynamicDataPercent) ' TODO check with Ted dynamicDataSizeLimitPercentage > dynamicDataPercent mVar.htmlDataSizeLimitPercentage = ConvertToInt(args.htmlDataPercent) ' TODO check with Ted htmlDataSizeLimitPercentage > htmlDataAbsolute mVar.htmlLocalStorageSizeLimitPercentage = ConvertToInt(args.htmlLocalStoragePercent) ' TODO check with Ted htmlLocalStorageSizeLimitPercentage > htmlLocalStorageAbsolute mVar.htmlIndexedDBSizeLimitPercentage = ConvertToInt(args.htmlIndexedDbPercent) ' TODO check with Ted htmlIndexedDBSizeLimitPercentage > htmlIndexedDbAbsolute else e.SendResponse(400) ' TODO check with Ted, add fallback for invalid request params instead of player crash return end if mVar.spaceLimitedByAbsoluteSize = "false" ' TODO check with Ted changed type from sting to boolean else e.SendResponse(400) return end if mVar.limitStorageSpace = true else if args.limitStorageEnabled <> invalid and lcase(args.limitStorageEnabled) = "false" then mVar.limitStorageSpace = false else e.SendResponse(400) return end if e.SendResponse(200) end sub Sub PostPrepareForTransferJson(userData as object, e as object) mVar = userData.mVar if e.GetRequestBodyFile() = invalid then e.SendResponse(400) ' TODO check with Ted, add fallback for invalid request params instead of player crash return end if reqFile = CreateObject("roReadFile", e.GetRequestBodyFile()) if reqFile.ReadByteIfAvailable() < 0 then e.SendResponse(400) ' TODO check with Ted, add fallback for invalid request params instead of player crash return end if MoveFile(e.GetRequestBodyFile(), "filesToPublish.json") filesToCopy = mVar.FreeSpaceOnDrive() if type(filesToCopy) = "roAssociativeArray" then respBody = { } respBody.AddReplace("family", mVar.sysInfo.deviceFamily$) ' TODO check with Ted Family > family respBody.AddReplace("model", mVar.sysInfo.deviceModel$) ' TODO check with Ted Model > model respBody.AddReplace("fwVersion", mVar.sysInfo.deviceFWVersion$) ' TODO check with Ted FWVersion > fwVersion respBody.AddReplace("fwVersionNumber", StripLeadingSpaces(stri(mVar.sysInfo.deviceFWVersionNumber%))) ' TODO check with Ted FWVersionNumber > fwVersionNumber respBody.AddReplace("file", createObject("roArray", 0, true)) for each key in filesToCopy ' TODO validate each file item ' TODO does this need to check what's in the pool already? fileItem = filesToCopy[key] if fileItem = invalid or fileItem.fileName = invalid or fileItem.filePath = invalid or fileItem.hash = invalid or fileItem.size = invalid then e.SendResponse(400) return end if file = { } file.AddReplace("fileName", fileItem.fileName) ' TODO check with Ted name change fileName > sourceFileName file.AddReplace("filePath", fileItem.filePath)' TODO check with Ted name change filePath > sourceFilePath file.AddReplace("hash", fileItem.hash) file.AddReplace("size", fileItem.size) ' TODO check with Ted name change fileSize > size respBody.file.push(file) next e.SetResponseBodyString(FormatJson(respBody)) e.SendResponse(200) else ' the following call is ignored on a post ' e.SetResponseBodyString("413") e.SendResponse(400) return end if end sub Sub PostFileJson(userData as object, e as object) if e.GetRequestBodyFile() = invalid then e.SendResponse(400) ' TODO check with Ted, add fallback for invalid request params instead of player crash return end if reqFile = CreateObject("roReadFile", e.GetRequestBodyFile()) if reqFile.ReadByteIfAvailable() < 0 or e.GetRequestHeader("Destination-Filename") = "" then e.SendResponse(400) ' TODO check with Ted, add fallback for invalid request params instead of player crash return end if destinationFilename = e.GetRequestHeader("Destination-Filename") currentDir$ = "pool/" poolDepth% = 2 while poolDepth% > 0 newDir$ = Left(Right(destinationFilename, poolDepth%), 1) currentDir$ = currentDir$ + newDir$ + "/" CreateDirectory(currentDir$) poolDepth% = poolDepth% - 1 end while regex = CreateObject("roRegEx", "/", "i") fileParts = regex.Split(destinationFilename) ' TODO this will silently fail for path depth > 2 fullFilePath$ = currentDir$ + fileParts[1] MoveFile(e.GetRequestBodyFile(), fullFilePath$) e.SendResponse(200) end sub ' invoked on completion of LFN publish Sub PostSyncSpecJson(userData as object, e as object) if e.GetRequestBodyFile() = invalid then e.SendResponse(400) ' TODO check with Ted, add fallback for invalid request params instead of player crash return end if reqFile = CreateObject("roReadFile", e.GetRequestBodyFile()) if reqFile.ReadByteIfAvailable() < 0 then e.SendResponse(400) ' TODO check with Ted, add fallback for invalid request params instead of player crash return end if EVENT_REALIZE_SUCCESS = 101 mVar = userData.mVar newSyncFileName$ = "new-sync.json" if ReadAsciiFile("local-sync.json") <> "" then oldSyncFileName$ = "local-sync.json" end if autoScheduleFileName$ = "autoschedule.json" MoveFile(e.GetRequestBodyFile(), newSyncFileName$) e.SendResponse(200) oldSync = CreateObject("roSyncSpec") ok = oldSync.ReadFromFile(oldSyncFileName$) if not ok then stop newSync = CreateObject("roSyncSpec") ok = newSync.ReadFromFile(newSyncFileName$) if not ok then stop oldSyncSpecScriptsOnly = oldSync.FilterFiles("download", { group: "script" }) newSyncSpecScriptsOnly = newSync.FilterFiles("download", { group: "script" }) mVar.diagnostics.PrintTimestamp() mVar.diagnostics.PrintDebug("### LWS DOWNLOAD COMPLETE") mVar.assetCollection = newSync.GetAssets("download") mVar.assetPoolFiles = CreateObject("roAssetPoolFiles", mVar.assetPool, mVar.assetCollection) if type(mVar.assetPoolFiles) <> "roAssetPoolFiles" then stop rebootRequired = false if not oldSyncSpecScriptsOnly.FilesEqualTo(newSyncSpecScriptsOnly) then ' Protect all the media files that the current sync spec is using in case we fail part way ' through and need to continue using it. if not (mVar.assetPool.ProtectAssets("current", oldSync) and mVar.assetPool.ProtectAssets("new", newSync)) then mVar.diagnostics.PrintDebug("Failed to protect files that we need in the pool") stop end if realizer = CreateObject("roAssetRealizer", mVar.assetPool, "/") globalAA = GetGlobalAA() globalAA.bsp.msgPort.DeferWatchdog(120) event = realizer.Realize(newSyncSpecScriptsOnly) realizer = invalid if event.GetEvent() <> EVENT_REALIZE_SUCCESS then mVar.logging.WriteDiagnosticLogEntry(mVar.diagnosticCodes.EVENT_REALIZE_FAILURE, stri(event.GetEvent()) + chr(9) + event.GetName() + chr(9) + event.GetFailureReason()) mVar.diagnostics.PrintDebug("### Realize failed " + stri(event.GetEvent()) + chr(9) + event.GetName() + chr(9) + event.GetFailureReason()) DeleteFile(newSyncFileName$) newSync = invalid return else ' destroy the Post LFN Device Setup Splash Screen registrySection = GetGlobalAA().registrySection registrySection.Delete("susse") registrySection.Flush() mVar.deviceSetupSplashScreen = invalid rebootRequired = true end if end if jsonSyncSpec$ = newSync.WriteToString({ format : "json" }) ok = WriteAsciiFile("local-sync.json", jsonSyncSpec$) if not ok then stop ' cause fsync CreateObject("roReadFile", "local-sync.json") if rebootRequired then mVar.diagnostics.PrintDebug("### new script or upgrade found - reboot") RebootSystem() end if globalAA = GetGlobalAA() globalAA.autoscheduleFilePath$ = GetPoolFilePath(mVar.assetPoolFiles, autoScheduleFileName$) globalAA.boseProductsFilePath$ = GetPoolFilePath(mVar.assetPoolFiles, "PartnerProducts.json") if globalAA.autoscheduleFilePath$ = "" then stop UpdateSyncSpecAndSettings(newSync, "local-sync.json", "local") metadata = newSync.GetMetadata("client") if metadata.DoesExist("obfuscatedPassphrase") then obfuscatedPassphrase$ = GetActiveSettings().obfuscatedPassphrase deviceCustomization = CreateObject("roDeviceCustomization") deviceCustomization.StoreObfuscatedEncryptionKey("AesCtrHmac", obfuscatedPassphrase$) mVar.contentEncrypted = true end if mVar.SetPoolSizes(GetActiveSyncSpecSettings()) ' at the moment, there are no settings that get communicated to the supervisor as a result of ' an lfn publish, so no need to send an update to the supervisor ' lastModifiedTime$ = newSync.LookupMetadata("client", "lastModifiedTime") DeleteFile(newSyncFileName$) newSync = invalid ' send internal message to prepare for restart prepareForRestartEvent = CreateObject("roAssociativeArray") prepareForRestartEvent["EventType"] = "PREPARE_FOR_RESTART" mVar.msgPort.PostMessage(prepareForRestartEvent) ' send internal message indicating that new content is available contentUpdatedEvent = CreateObject("roAssociativeArray") contentUpdatedEvent["EventType"] = "CONTENT_UPDATED" mVar.msgPort.PostMessage(contentUpdatedEvent) end sub 'endregion 'region SyncSpecAndSettings ' TEDTODO - compare against algorithm used by supervisor Function GetSyncSpec() as object syncSpecSpec = {} syncSpecSpec.syncSpec = CreateObject("roSyncSpec") syncSpecFileNames = ["current-sync.json", "local-sync.json", "localToBSN-sync.json"] syncSpecTypes = ["network", "local", "localToBsn"] for i = 0 to syncSpecFileNames.Count() - 1 if syncSpecSpec.syncSpec.ReadFromFile(syncSpecFileNames[i]) then syncSpecSpec.fileName = syncSpecFileNames[i] syncSpecSpec.syncSpecType = syncSpecTypes[i] return syncSpecSpec endif next return invalid end function Sub InitSupervisorSupport() globalAA = GetGlobalAA() CreateControlCloudInterface() globalAA.supervisorSupport = GetSupervisorSupport() globalAA.supervisorSupportsConfigureNetwork = GetSupervisorSupportsConfigureNetwork() globalAA.supervisorVersion = GetSupervisorVersion() ' until an autoplay is encountered that indicates that the supervisor should manage settings, ' don't have the supervisor manage settings. globalAA.supervisorEnableSettingsHandler = false SendAutorunCapabilitiesToSupervisorViaIpc([], "") globalAA.cellularModemActive = GetIsCellularModemActive() globalAA.useSupervisorConfigSpec = AutorunConfigEndpointExists() registrySection = globalAA.registrySection globalAA.debug_settings = GetBoolFromString(registrySection.Read("debug_settings"), false) end sub Sub InitializeSyncSpecAndSettings() globalAA = GetGlobalAA() syncSpecSpec = GetSyncSpec() if type(syncSpecSpec) <> "roAssociativeArray" then stop endif globalAA.syncSpec = syncSpecSpec.syncSpec globalAA.syncSpecFileName = syncSpecSpec.fileName globalAA.syncSpecType = syncSpecSpec.syncSpecType if globalAA.useSupervisorConfigSpec then globalAA.settings = GetConfigJsonSettings() else globalAA.settings = LoadSettingsFromLegacySyncSpec(syncSpecSpec.syncSpec) MergeLegacyRegistrySettingsIntoGlobalSettings(globalAA.settings) globalAA.supervisorSupportsUsbNetworkInterfaces = false endif globalAA.syncSpecSettings = LoadSyncSpecSettings(globalAA.syncSpec) BuildNetworkInterfacePriorityLists() globalAA.pendingSyncSpec = invalid globalAA.pendingSyncSpecSettings = invalid ' The circumstances when the updated settings should be sent to the supervisor. ' Standalone Publish - yes, if ' there's no config.json file ' there's a config.json file but its timestamp is older than the timestamp found in local-sync.json setupType = lcase(globalAA.settings.setupType) if globalAA.useSupervisorConfigSpec and globalAA.syncSpecType = "local" and (setupType = "standalone" or setupType = "") then ' get timestamps of config file and local-sync.json lastModifiedTimeSyncSpec = globalAA.syncSpec.LookupMetadata("client","lastModifiedTime") lastModifiedTimeConfig = globalAA.settings.lastModifiedTime if lastModifiedTimeConfig > lastModifiedTimeSyncSpec then ' settings have been loaded (in GetConfigJsonSettings) - nothing else needs to be done ' TEDTODO-Subban: globalAA.bsp doesn't exist yet. ' globalAA.bsp.diagnostics.PrintDebug("### Use settings from config. Config lastModifiedTime: " + lastModifiedTimeConfig + "SyncSpec lastModifiedTime" + lastModifiedTimeSyncSpec) ' print "### Use settings from config. Config lastModifiedTime: " + lastModifiedTimeConfig + ", SyncSpec lastModifiedTime: " + lastModifiedTimeSyncSpec return endif if lcase(setupType) = "standalone" or setupType = "" then ' retrieve settings from sync spec and registry settings = LoadSettingsFromLegacySyncSpec(syncSpecSpec.syncSpec) MergeLegacyRegistrySettingsIntoGlobalSettings(settings) globalAA = GetGlobalAA() AddRegistrySettingsNotInStandaloneSyncSpecIntoGlobalSettings(globalAA.registrySection, settings) globalAA.settings = settings BuildNetworkInterfacePriorityLists() lastModifiedTime = globalAA.syncSpec.LookupMetadata("client","lastModifiedTime") + "Z" SendUpdatedSettingsToSupervisor(lastModifiedTime, settings) 'TEDTODO-Subban - get settings from supervisor after setting them? ' globalAA.settings = GetConfigJsonSettings() endif endif end sub Sub SetPendingSettings() globalAA = GetGlobalAA() globalAA.pendingSettings = GetConfigJsonSettings() end sub Sub SetPendingSyncSpec(pendingSyncSpec as object) globalAA = GetGlobalAA() globalAA.pendingSyncSpec = pendingSyncSpec globalAA.pendingSyncSpecSettings = LoadSyncSpecSettings(globalAA.pendingSyncSpec) end sub Sub SetPendingSyncSpecAndSettings(pendingSyncSpec as object) globalAA = GetGlobalAA() globalAA.pendingSyncSpec = pendingSyncSpec legacySyncSpecSettings = LoadSettingsFromLegacySyncSpec(pendingSyncSpec) MergeLegacyRegistrySettingsIntoGlobalSettings(legacySyncSpecSettings) if globalAA.useSupervisorConfigSpec then pendingSettings = GetConfigJsonSettings() ' if sfn, retrieve properties from sync spec that are now 'owned' by the supervisor if lcase(GetGlobalAA().settings.setupType) = "sfn" then pendingSettings.deviceScreenShotsEnabled = legacySyncSpecSettings.deviceScreenShotsEnabled pendingSettings.deviceScreenShotsInterval = legacySyncSpecSettings.deviceScreenShotsInterval pendingSettings.deviceScreenShotsCountLimit = legacySyncSpecSettings.deviceScreenShotsCountLimit pendingSettings.deviceScreenShotsQuality = legacySyncSpecSettings.deviceScreenShotsQuality pendingSettings.deviceScreenShotsOrientation = legacySyncSpecSettings.deviceScreenShotsOrientation pendingSettings.contentDownloadsRestricted = legacySyncSpecSettings.contentDownloadsRestricted pendingSettings.contentDownloadRangeStart = legacySyncSpecSettings.contentDownloadRangeStart pendingSettings.contentDownloadRangeLength = legacySyncSpecSettings.contentDownloadRangeLength pendingSettings.playbackLoggingEnabled = legacySyncSpecSettings.playbackLoggingEnabled pendingSettings.diagnosticLoggingEnabled = legacySyncSpecSettings.diagnosticLoggingEnabled pendingSettings.eventLoggingEnabled = legacySyncSpecSettings.eventLoggingEnabled pendingSettings.stateLoggingEnabled = legacySyncSpecSettings.stateLoggingEnabled pendingSettings.variableLoggingEnabled = legacySyncSpecSettings.variableLoggingEnabled pendingSettings.uploadLogFilesAtBoot = legacySyncSpecSettings.uploadLogFilesAtBoot pendingSettings.uploadLogFilesAtSpecificTime = legacySyncSpecSettings.uploadLogFilesAtSpecificTime pendingSettings.uploadLogFilesTime = legacySyncSpecSettings.uploadLogFilesTime endif globalAA.pendingSettings = pendingSettings else globalAA.pendingSettings = legacySyncSpecSettings end if globalAA.pendingSyncSpecSettings = LoadSyncSpecSettings(globalAA.pendingSyncSpec) end sub Function GetConfigJsonSettings() as object settingsContainer = GetAutorunConfigFromSupervisor() return LoadSettingsFromConfig(settingsContainer) end function Sub UpdateSyncSpecAndSettings(syncSpec as object, syncSpecFileName as string, syncSpecType as string) globalAA = GetGlobalAA() globalAA.syncSpec = syncSpec globalAA.syncSpecFileName = syncSpecFileName globalAA.syncSpecType = syncSpecType if globalAA.useSupervisorConfigSpec then globalAA.settings = GetConfigJsonSettings() else globalAA.settings = LoadSettingsFromLegacySyncSpec(syncSpec) MergeLegacyRegistrySettingsIntoGlobalSettings(globalAA.settings) endif BuildNetworkInterfacePriorityLists() globalAA.syncSpecSettings = LoadSyncSpecSettings(globalAA.syncSpec) end sub Sub BuildNetworkInterfacePriorityLists() globalAA = GetGlobalAA() globalAA.networkInterfacePriorityLists = {} BuildContentXfersNetworkInterfacePriorityList(globalAA) BuildMrssContentXfersNetworkInterfacePriorityList(globalAA) BuildTextFeedXfersNetworkInterfacePriorityList(globalAA) BuildLogUploadsNetworkInterfacePriorityList(globalAA) end sub Sub BuildContentXfersNetworkInterfacePriorityList(globalAA as object) priorityList = [] for each interface in globalAA.settings.network.interfaces if DataTypeEnabledForInterface(globalAA.settings, "contentDownloadEnabled", interface) then priorityList.push(interface) endif next globalAA.networkInterfacePriorityLists.AddReplace("contentDownloadEnabled", priorityList) ' globalAA.contentXfersNetworkInterfacePriorityList = priorityList end sub Sub BuildMrssContentXfersNetworkInterfacePriorityList(globalAA as object) priorityList = [] for each interface in globalAA.settings.network.interfaces if DataTypeEnabledForInterface(globalAA.settings, "mediaFeedsDownloadEnabled", interface) then priorityList.push(interface) endif next globalAA.networkInterfacePriorityLists.AddReplace("mediaFeedsDownloadEnabled", priorityList) ' globalAA.contentXfersNetworkInterfacePriorityList = priorityList end sub Sub BuildTextFeedXfersNetworkInterfacePriorityList(globalAA as object) priorityList = [] for each interface in globalAA.settings.network.interfaces if DataTypeEnabledForInterface(globalAA.settings, "textFeedsDownloadEnabled", interface) then priorityList.push(interface) endif next globalAA.networkInterfacePriorityLists.AddReplace("textFeedsDownloadEnabled", priorityList) end sub Sub BuildLogUploadsNetworkInterfacePriorityList(globalAA as object) priorityList = [] for each interface in globalAA.settings.network.interfaces if DataTypeEnabledForInterface(globalAA.settings, "logsUploadEnabled", interface) then priorityList.push(interface) endif next globalAA.networkInterfacePriorityLists.AddReplace("logsUploadEnabled", priorityList) end sub Function DataTypeEnabledForInterface(settings as Object, dataType as string, interface as object) as boolean enabled = false if interface.DoesExist(dataType) then dataTypeEnabled = interface.LookupCi(dataType) if IsBoolean(dataTypeEnabled) and dataTypeEnabled then enabled = true endif endif return enabled end function Function GetActiveSyncSpec() as object return GetGlobalAA().syncSpec end function Function GetActiveSyncSpecType() as string return GetGlobalAA().syncSpecType end function Sub SetActiveSettingsFromPendingSettings() globalAA = GetGlobalAA() globalAA.settings = globalAA.pendingSettings BuildNetworkInterfacePriorityLists() globalAA.pendingSettings = invalid end sub Function GetActiveSettings() as object return GetGlobalAA().settings end function Function GetActiveSyncSpecSettings() as object return GetGlobalAA().syncSpecSettings end function Function GetPendingSettings() as object return GetGlobalAA().pendingSettings end function Function GetPendingSyncSpecSettings() as object return GetGlobalAA().pendingSyncSpecSettings end function Function GetSupervisorSupportsUsbNetworkInterfaces(config as object) as boolean if config.meta.DoesExist("client") and config.meta.client.DoesExist("bsnAPIVersion") then bsnAPIVersion = config.meta.client.bsnAPIVersion if IsString(bsnAPIVersion) and bsnAPIVersion <> "" and bsnAPIVersion <> "2019/03" then return true endif endif return false end function Function GetSetupTypeSetting(config as object, registrySection as object) as string if config.meta.DoesExist("client") and config.meta.client.DoesExist("setupType") and IsString(config.meta.client.setupType) then return config.meta.client.setupType else return registrySection.Read("sut") endif end function Function GetGroupSetting(config as object, registrySection as object) as string if config.meta.DoesExist("server") and config.meta.server.DoesExist("group") and IsString(config.meta.server.group) then return config.meta.server.group else return registrySection.Read("g") endif end function Function GetUnitNameSetting(config as object, registrySection as object) as string if config.meta.DoesExist("client") and config.meta.client.DoesExist("unitName") and IsString(config.meta.client.unitName) then return config.meta.client.unitName else return registrySection.Read("un") endif end function Function GetUnitDescriptionSetting(config as object, registrySection as object) as string if config.meta.DoesExist("client") and config.meta.client.DoesExist("unitDescription") and IsString(config.meta.client.unitDescription) then return config.meta.client.unitDescription else return registrySection.Read("ud") endif end function Function GetUnitNamingMethodSetting(config as object, registrySection as object) as string if config.meta.DoesExist("client") and config.meta.client.DoesExist("unitNamingMethod") and IsString(config.meta.client.unitNamingMethod) then return config.meta.client.unitNamingMethod else return registrySection.Read("unm") endif end function Function GetTimezoneSetting(config as object, registrySection as object) as string if config.meta.DoesExist("client") and config.meta.client.DoesExist("timeZone") and IsString(config.meta.client.timeZone) then return config.meta.client.timeZone else systemTime = CreateObject("roSystemTime") return systemTime.GetTimeZone() endif end function Function GetLwsEnableUpdateNotificationsSetting(config as object, registrySection as object) as boolean if config.meta.DoesExist("client") and config.meta.client.DoesExist("lwsEnableUpdateNotifications") and IsString(config.meta.client.lwsEnableUpdateNotifications) then return config.meta.client.lwsEnableUpdateNotifications else return GetBoolFromString(registrySection.Read("nlwseun"), true) endif end function Function GetLwsUserName(config as object, registrySection as object) as string if config.meta.DoesExist("client") and config.meta.client.DoesExist("lwsUserName") and IsString(config.meta.client.lwsUserName) then return config.meta.client.lwsUserName else return registrySection.Read("nlwsu") endif end function Function GetLwsPassword(config as object, registrySection as object) as string if config.meta.DoesExist("client") and config.meta.client.DoesExist("lwsPassword") and IsString(config.meta.client.lwsPassword) then return config.meta.client.lwsPassword else return registrySection.Read("nlwsp") endif end function Function GetLwsConfig(config as object, registrySection as object) as string if config.meta.DoesExist("client") and config.meta.client.DoesExist("lwsConfig") then settingLwsConfig = config.meta.client.lwsConfig if settingLwsConfig = "content" then lwsConfig = "c" else if settingLwsConfig = "status" then lwsConfig = "s" else lwsConfig = "" endif return lwsConfig else return registrySection.Read("nlws") endif end function Sub GetDeviceScreenShotsEnabled(config as object, registrySection as object) as boolean if config.meta.DoesExist("client") and config.meta.client.DoesExist("deviceScreenShotsEnabled") and IsBoolean(config.meta.client.deviceScreenShotsEnabled) then return config.meta.client.deviceScreenShotsEnabled else return GetBoolFromString(registrySection.Read("enableRemoteSnapshot"), false) endif end sub ' load string property, default to "" Function GetConfigStringDefaultToEmpty(val) as string if IsString(val) then return val else return "" endif end function Function LoadSettingsFromConfig(config as object) as object globalAA = GetGlobalAA() globalAA.supervisorSupportsUsbNetworkInterfaces = GetSupervisorSupportsUsbNetworkInterfaces(config) registrySection = globalAA.registrySection settings = {} dt = registrySection.Read("last_modified_autorun_settings_at") if IsString(dt) and dt <> "" then settings.lastModifiedTime = dt else ' new device; set last modified time in a way that always forces sending settings from supervisor for standalone units settings.lastModifiedTime = "2000-01-01T00:00:00.000" endif settings.group = GetGroupSetting(config, registrySection) settings.setupType = GetSetupTypeSetting(config, registrySection) settings.uploadUsage = GetConfigStringDefaultToEmpty(config.meta.client.uploadUsage) settings.nowPlaying = GetConfigStringDefaultToEmpty(config.meta.client.nowPlaying) settings.getFile = GetConfigStringDefaultToEmpty(config.meta.client.getFile) settings.recoveryHandler = GetConfigStringDefaultToEmpty(config.meta.client.recoveryHandler) settings.recoverySetup = GetConfigStringDefaultToEmpty(config.meta.client.recoverySetup) settings.batteryCharger = GetConfigStringDefaultToEmpty(config.meta.client.batteryCharger) settings.unitName = GetUnitNameSetting(config, registrySection) settings.unitNamingMethod = GetUnitNamingMethodSetting(config, registrySection) settings.unitDescription = GetUnitDescriptionSetting(config, registrySection) settings.timeZone = GetTimezoneSetting(config, registrySection) settings.contentDownloadsRestricted = GetValidBool(config.meta.client.contentDownloadsRestricted, false) settings.contentDownloadRangeStart = GetValidInt(config.meta.client.contentDownloadRangeStart, 0) settings.contentDownloadRangeLength = GetValidInt(config.meta.client.contentDownloadRangeLength, 0) settings.lwsConfig = GetLwsConfig(config, registrySection) settings.lwsEnableUpdateNotifications = GetLwsEnableUpdateNotificationsSetting(config, registrySection) settings.lwsUserName = GetLwsUserName(config, registrySection) settings.lwsPassword = GetLwsPassword(config, registrySection) settings.playbackLoggingEnabled = GetValidBool(config.meta.client.playbackLoggingEnabled, false) settings.eventLoggingEnabled = GetValidBool(config.meta.client.eventLoggingEnabled, false) settings.stateLoggingEnabled = GetValidBool(config.meta.client.stateLoggingEnabled, false) settings.diagnosticLoggingEnabled = GetValidBool(config.meta.client.diagnosticLoggingEnabled, false) settings.variableLoggingEnabled = GetValidBool(config.meta.client.variableLoggingEnabled, false) settings.uploadLogFilesAtBoot = GetValidBool(config.meta.client.uploadLogFilesAtBoot, false) settings.uploadLogFilesAtSpecificTime = GetValidBool(config.meta.client.uploadLogFilesAtSpecificTime, false) settings.uploadLogFilesTime = GetValidInt(config.meta.uploadLogFilesTime, 0) settings.deviceScreenShotsEnabled = GetDeviceScreenShotsEnabled(config, registrySection) settings.deviceScreenShotsInterval = GetValidInt(config.meta.client.deviceScreenShotsInterval, 300) settings.deviceScreenShotsCountLimit = GetValidInt(config.meta.client.deviceScreenShotsCountLimit, 2) settings.deviceScreenShotsQuality = GetValidInt(config.meta.client.deviceScreenShotsQuality, 50) settings.deviceScreenShotsOrientation = GetValidString(config.meta.client.deviceScreenShotsOrientation, "Landscape") settings.idleScreenColor = GetValidString(config.meta.client.idleScreenColor, "FF000000") settings.network = {} settings.network.interfaces = [] if config.meta.DoesExist("network") and config.meta.network.DoesExist("interfaces") then for each interface in config.meta.network.interfaces interfaceSpec = {} interfaceSpec.networkInterface = interface.name interfaceSpec.rateLimitRateInWindow% = GetValidInt(interface.rateLimitInsideContentDownloadWindow, -1) interfaceSpec.rateLimitRateInitialDownloads% = GetValidInt(interface.rateLimitDuringInitialDownloads, -1) interfaceSpec.rateLimitRateOutsideWindow% = GetValidInt(interface.rateLimitOutsideContentDownloadWindow, -1) interfaceSpec.contentDownloadEnabled = GetValidBool(interface.contentDownloadEnabled, true) interfaceSpec.textFeedsDownloadEnabled = GetValidBool(interface.textFeedsDownloadEnabled, true) interfaceSpec.mediaFeedsDownloadEnabled = GetValidBool(interface.mediaFeedsDownloadEnabled, true) interfaceSpec.healthReportingEnabled = GetValidBool(interface.healthReportingEnabled, true) interfaceSpec.logsUploadEnabled = GetValidBool(interface.logsUploadEnabled, true) settings.network.interfaces.push(interfaceSpec) next endif return settings end function ' Note - this function is called when running with a supervisor that supports settings ' on boot for standalone publishes ' from SetPendingSyncSpecAndSettings ' In those cases, this function is setting properties inappropriately (e.g., network settings unused by the autorun)/ ' It's not clear that it causes any issues however as these properties are never read / used in this case. Function LoadSettingsFromLegacySyncSpec(syncSpec as object) as object globalAA = GetGlobalAA() settings = {} settings.group = syncSpec.LookupMetadata("server", "group") ' the list of settings read here should be identical to those read in config.json ' TEDTODO - what about new supervisor, no setup / publish type === standalone if syncSpec.LookupMetadata("client", "deviceScreenShotsEnabled") <> "" then ' executed for old or new supervisors, for a publish type other than standalone settings.deviceScreenShotsEnabled = GetBoolFromString(syncSpec.LookupMetadata("client", "deviceScreenShotsEnabled"), false) settings.deviceScreenShotsCountLimit = GetIntFromString(syncSpec.LookupMetadata("client", "deviceScreenShotsCountLimit")) settings.deviceScreenShotsInterval = GetIntFromString(syncSpec.LookupMetadata("client", "deviceScreenShotsInterval")) settings.deviceScreenShotsQuality = GetIntFromString(syncSpec.LookupMetadata("client", "deviceScreenShotsQuality")) settings.deviceScreenShotsOrientation = syncSpec.LookupMetadata("client", "deviceScreenShotsOrientation") else if not globalAA.useSupervisorConfigSpec then ' executed for old supervisor, standalone registrySettings = globalAA.registrySettings settings.deviceScreenShotsEnabled = GetBoolFromString(registrySettings.deviceScreenShotsEnabled, false) settings.deviceScreenShotsInterval = int(val(registrySettings.deviceScreenShotsInterval)) settings.deviceScreenShotsCountLimit = int(val(registrySettings.deviceScreenShotsCountLimit)) settings.deviceScreenShotsQuality = int(val(registrySettings.deviceScreenShotsQuality)) settings.deviceScreenShotsOrientation = registrySettings.deviceScreenShotsOrientation end if settings.diagnosticLoggingEnabled = syncSpecValueTrue(syncSpec.LookupMetadata("client", "diagnosticLoggingEnabled")) settings.eventLoggingEnabled = syncSpecValueTrue(syncSpec.LookupMetadata("client", "eventLoggingEnabled")) settings.playbackLoggingEnabled = syncSpecValueTrue(syncSpec.LookupMetadata("client", "playbackLoggingEnabled")) settings.stateLoggingEnabled = syncSpecValueTrue(syncSpec.LookupMetadata("client", "stateLoggingEnabled")) settings.variableLoggingEnabled = syncSpecValueTrue(syncSpec.LookupMetadata("client", "variableLoggingEnabled")) settings.uploadLogFilesAtBoot = syncSpecValueTrue(syncSpec.LookupMetadata("client", "uploadLogFilesAtBoot")) settings.uploadLogFilesAtSpecificTime = syncSpecValueTrue(syncSpec.LookupMetadata("client", "uploadLogFilesAtSpecificTime")) settings.uploadLogFilesTime = GetIntFromString(syncSpec.LookupMetadata("client", "uploadLogFilesTime")) settings.unitDescription = syncSpec.LookupMetadata("client", "unitDescription") settings.unitName = syncSpec.LookupMetadata("client", "unitName") settings.unitNamingMethod = syncSpec.LookupMetadata("client", "unitNamingMethod") settings.contentDownloadsRestricted = GetBoolFromString(syncSpec.LookupMetadata("client", "contentDownloadsRestricted"), false) settings.contentDownloadRangeStart = GetIntFromString(syncSpec.LookupMetadata("client", "contentDownloadRangeStart")) settings.contentDownloadRangeLength = GetIntFromString(syncSpec.LookupMetadata("client", "contentDownloadRangeLength")) setupType = lcase(globalAA.registrySection.read("sut")) if setupType <> "bsn" then settings.networkConnectionPriorityWired = 0 if IsString(globalAA.registrySettings.wiredNetworkingParameters.networkConnectionPriority$) then ncp$ = globalAA.registrySettings.wiredNetworkingParameters.networkConnectionPriority$ if len(ncp$) > 0 then settings.networkConnectionPriorityWired = int(val(ncp$)) endif endif settings.networkConnectionPriorityWireless = 1 if IsString(globalAA.registrySettings.wirelessNetworkingParameters.networkConnectionPriority$) then ncp$ = globalAA.registrySettings.wirelessNetworkingParameters.networkConnectionPriority$ if len(ncp$) > 0 then settings.networkConnectionPriorityWireless = int(val(ncp$)) endif endif settings.dwsEnabled = globalAA.registrySettings.dwsEnabled settings.dwsPassword = globalAA.registrySettings.dwsPassword$ settings.idleScreenColor = globalAA.registrySettings.idleScreenColor$ settings.timezone = "" ' tells the rest of the system that the timeZone hasn't changed ' compatible with old code, but not necessarily right settings.useDHCP = "" settings.timeServer = GetGlobalAA().registrySettings.timeServer$ else settings.networkConnectionPriorityWired = int(val(syncSpec.LookupMetadata("client", "networkConnectionPriorityWired"))) settings.networkConnectionPriorityWireless = int(val(syncSpec.LookupMetadata("client", "networkConnectionPriorityWireless"))) settings.dwsEnabled = syncSpecValueTrue(syncSpec.LookupMetadata("client", "dwsEnabled")) settings.dwsPassword = syncSpec.LookupMetadata("client", "dwsPassword") settings.idleScreenColor = syncSpec.LookupMetadata("client", "idleScreenColor") settings.timezone = syncSpec.LookupMetadata("client", "timezone") settings.useDHCP = syncSpec.LookupMetadata("client", "useDHCP") settings.timeServer = syncSpec.LookupMetadata("client", "timeServer") endif ' TEDTODO ' the following values are not in the sync spec for SFN. However, it appears as though these variables are not used in a meaningful way settings.proxy = syncSpec.LookupMetadata("client", "proxy") settings.networkHosts = syncSpec.LookupMetadata("client", "networkHosts") settings.passphrase = syncSpec.LookupMetadata("client", "passphrase") settings.ssid = syncSpec.LookupMetadata("client", "ssid") settings.network = {} settings.network.interfaces = [] sysInfo = globalAA.sysInfo settings.useWireless = false if sysInfo.modelSupportsWifi then registrySettings = GetGlobalAA().registrySettings inheritNetworkProperties = registrySettings.inheritNetworkProperties if inheritNetworkProperties = "yes" or setupType <> "bsn" then settings.useWireless = GetBoolFromString(registrySettings.useWireless$, false) else settings.useWireless = GetBoolFromString(syncSpec.LookupMetadata("client", "useWireless"), false) end if endif if settings.useWireless then wiredSettings = GetNetworkSettings(syncSpec, "_2", "Wired") wiredSettings.networkInterface = "eth0" wirelessSettings = GetNetworkSettings(syncSpec, "", "Wireless") wirelessSettings.networkInterface = "wlan0" networkConnectionPriorityWired% = settings.networkConnectionPriorityWired networkConnectionPriorityWireless% = settings.networkConnectionPriorityWireless if networkConnectionPriorityWired% = 0 then settings.network.interfaces.push(wiredSettings) settings.network.interfaces.push(wirelessSettings) else settings.network.interfaces.push(wirelessSettings) settings.network.interfaces.push(wiredSettings) endif else wiredSettings = GetNetworkSettings(syncSpec, "", "Wired") wiredSettings.networkInterface = "eth0" settings.network.interfaces.push(wiredSettings) endif return settings end function ' Load the settings that are in the sync spec for both Settings and Non Settings Supervisors Function LoadSyncSpecSettings(syncSpec) as object syncSpecSettings = {} syncSpecSettings.account = syncSpec.LookupMetadata("server", "account") ' for sfn syncSpecSettings.user = syncSpec.LookupMetadata("server", "user") syncSpecSettings.password = GetPassword(syncSpec.LookupMetadata("server", "password")) syncSpecSettings.enableBasicAuthentication = GetBoolFromString(syncSpec.LookupMetadata("server", "enableBasicAuthentication"), false) ' for bsn syncSpecSettings.accessToken = syncSpec.LookupMetadata("server", "accessToken") syncSpecSettings.base = syncSpec.LookupMetadata("client", "base") syncSpecSettings.next = syncSpec.LookupMetadata("client", "next") syncSpecSettings.event = syncSpec.LookupMetadata("client", "event") syncSpecSettings.devicedownload = syncSpec.LookupMetadata("client", "devicedownload") syncSpecSettings.devicedownloadprogress = syncSpec.LookupMetadata("client", "devicedownloadprogress") syncSpecSettings.deviceerror = syncSpec.LookupMetadata("client", "deviceerror") syncSpecSettings.trafficdownload = syncSpec.LookupMetadata("client", "trafficdownload") clientMetadata = syncSpec.GetMetadata("client") syncSpecSettings.uploadlogs = clientMetadata.LookupCi("uploadLogs") syncSpecSettings.timeBetweenNetConnects = syncSpec.LookupMetadata("client", "timeBetweenNetConnects") syncSpecSettings.enableSerialDebugging = syncSpecValueTrue(syncSpec.LookupMetadata("client", "enableSerialDebugging")) syncSpecSettings.enableSystemLogDebugging = syncSpecValueTrue(syncSpec.LookupMetadata("client", "enableSystemLogDebugging")) syncSpecSettings.limitStorageSpace = syncSpecValueTrue(syncSpec.LookupMetadata("client", "limitStorageSpace")) syncSpecSettings.spaceLimitedByAbsoluteSize = syncSpecValueTrue(syncSpec.LookupMetadata("client", "spaceLimitedByAbsoluteSize")) syncSpecSettings.publishedDataSizeLimitMB = syncSpec.LookupMetadata("client", "publishedDataSizeLimitMB") syncSpecSettings.publishedDataSizeLimitPercentage = syncSpec.LookupMetadata("client", "publishedDataSizeLimitPercentage") syncSpecSettings.dynamicDataSizeLimitMB = syncSpec.LookupMetadata("client", "dynamicDataSizeLimitMB") syncSpecSettings.dynamicDataSizeLimitPercentage = syncSpec.LookupMetadata("client", "dynamicDataSizeLimitPercentage") syncSpecSettings.htmlDataSizeLimitPercentage = syncSpec.LookupMetadata("client", "htmlDataSizeLimitPercentage") syncSpecSettings.htmlDataSizeLimitMB = syncSpec.LookupMetadata("client", "htmlDataSizeLimitMB") syncSpecSettings.htmlLocalStorageSizeLimitPercentage = syncSpec.LookupMetadata("client", "htmlLocalStorageSizeLimitPercentage") syncSpecSettings.htmlLocalStorageSizeLimitMB = syncSpec.LookupMetadata("client", "htmlLocalStorageSizeLimitMB") syncSpecSettings.htmlIndexedDBSizeLimitPercentage = syncSpec.LookupMetadata("client", "htmlIndexedDBSizeLimitPercentage") syncSpecSettings.htmlIndexedDBSizeLimitMB = syncSpec.LookupMetadata("client", "htmlIndexedDBSizeLimitMB") syncSpecSettings.awsAccessKeyId = syncSpec.LookupMetadata("client", "awsAccessKeyId") syncSpecSettings.awsSecretAccessKey = syncSpec.LookupMetadata("client", "awsSecretAccessKey") syncSpecSettings.awsSessionToken = syncSpec.LookupMetadata("client", "awsSessionToken") syncSpecSettings.deviceScreenShotsTemporaryStorage = syncSpec.LookupMetadata("client", "deviceScreenShotsTemporaryStorage") syncSpecSettings.incomingDeviceScreenshotsQueue = syncSpec.LookupMetadata("client", "incomingDeviceScreenshotsQueue") syncSpecSettings.uploadSnapshots = syncSpec.LookupMetadata("client", "uploadSnapshots") syncSpecSettings.uploadDeviceScreenshotHandlerAddress = syncSpec.LookupMetadata("client", "uploadDeviceScreenshotHandlerAddress") syncSpecSettings.securityToken = syncSpec.LookupMetadata("client", "securityToken") syncSpecSettings.obfuscatedPassphrase = syncSpec.LookupMetadata("client", "obfuscatedPassphrase") return syncSpecSettings end function Function GetPassword(password$) as string ' No need to decrypt when not provided if password$ = "" then return "" hexKeyValue$ = "27ae0d9c871cc8b6eaabb1bdc7c53d66" hexIVValue$ = "6669fc502f92ba915164fafd7957ca70" key = CreateObject("roByteArray") iv = CreateObject("roByteArray") pw = CreateObject("roByteArray") key.FromHexString(hexKeyValue$) iv.FromHexString(hexIVValue$) pw.FromHexString(password$) c = CreateObject("roBlockCipher", { mode: "aes-128-cbc", padding: "pkcs7" }) c.SetIV(iv) decrypted = c.Decrypt(key, pw) ' If the password failed to decrypt, return the original value if type(decrypted) <> "roByteArray" then return password$ plainText = decrypted.toAsciiString() return plainText end function Function GetNetworkSettings(syncSpec as object, suffix as string, interfaceName as string) as object networkSettings = {} networkSettings.rateLimitRateInWindow% = GetRateLimitValue(syncSpec.LookupMetadata("client", "rateLimitModeInWindow" + suffix), syncSpec.LookupMetadata("client", "rateLimitRateInWindow" + suffix)) networkSettings.rateLimitRateInitialDownloads% = GetRateLimitValue(syncSpec.LookupMetadata("client", "rateLimitModeInitialDownloads" + suffix), syncSpec.LookupMetadata("client", "rateLimitRateInitialDownloads" + suffix)) networkSettings.rateLimitRateOutsideWindow% = GetRateLimitValue(syncSpec.LookupMetadata("client", "rateLimitModeOutsideWindow" + suffix), syncSpec.LookupMetadata("client", "rateLimitRateOutsideWindow" + suffix)) networkSettings.contentDownloadEnabled = GetBoolFromString(syncSpec.LookupMetadata("client", "contentXfersEnabled" + interfaceName), true) networkSettings.textFeedsDownloadEnabled = GetBoolFromString(syncSpec.LookupMetadata("client", "textFeedsXfersEnabledWired" + interfaceName), true) networkSettings.mediaFeedsDownloadEnabled = GetBoolFromString(syncSpec.LookupMetadata("client", "mediaFeedsXfersEnabledWired" + interfaceName), true) networkSettings.healthReportingEnabled = GetBoolFromString(syncSpec.LookupMetadata("client", "healthXfersEnabledWired" + interfaceName), true) networkSettings.logsUploadEnabled = GetBoolFromString(syncSpec.LookupMetadata("client", "logUploadsXfersEnabledWired" + interfaceName), true) networkSettings.useDHCP = syncSpec.LookupMetadata("client", "useDHCP" + suffix) networkSettings.staticIPAddress = syncSpec.LookupMetadata("client", "staticIPAddress" + suffix) networkSettings.subnetMask = syncSpec.LookupMetadata("client", "subnetMask" + suffix) networkSettings.gateway = syncSpec.LookupMetadata("client", "gateway" + suffix) networkSettings.dns1 = syncSpec.LookupMetadata("client", "dns1" + suffix) networkSettings.dns2 = syncSpec.LookupMetadata("client", "dns2" + suffix) networkSettings.dns3 = syncSpec.LookupMetadata("client", "dns3" + suffix) return networkSettings end function Function GetWiredInterfaceIndex(settings as object) as integer if (settings.useWireless) then nc = CreateObject("roNetworkConfiguration", 1) if type(nc) = "roNetworkConfiguration" then currentConfig = nc.GetCurrentConfig() if type(currentConfig) = "roAssociativeArray" then if settings.networkConnectionPriorityWireless = 0 then return 1 endif endif endif else return 0 endif end function Sub GetDeviceSerialNumberJson(userData as object, e as object) mVar = userData.mVar respBody = { } respBody.AddReplace("serialNumber", mVar.sysInfo.deviceUniqueID$) e.AddResponseHeader("Content-type", "application/json") e.SetResponseBodyString(FormatJson(respBody)) e.SendResponse(200) end sub Function GetWirelessInterfaceIndex(settings as object) if (settings.useWireless) then nc = CreateObject("roNetworkConfiguration", 1) if type(nc) = "roNetworkConfiguration" then currentConfig = nc.GetCurrentConfig() if type(currentConfig) = "roAssociativeArray" then if settings.networkConnectionPriorityWireless = 0 then return 0 else return 1 endif endif endif endif return invalid end function 'region remoteSnapshots Sub UpdateRemoteSnapshotSettingsFromSyncSpec(syncSpec as object) settings = GetActiveSettings() registrySection = GetGlobalAA().registrySection ' remember current settings currentDeviceScreenShotsEnabled = settings.deviceScreenShotsEnabled m.SetRemoteSnapshotUrls() if not GetGlobalAA().useSupervisorConfigSpec then registrySection.Write("enableRemoteSnapshot", syncSpec.LookupMetadata("client", "deviceScreenShotsEnabled")) settings.deviceScreenShotsEnabled = GetBoolFromString(registrySection.Read("enableRemoteSnapshot"), false) registrySection.Write("remoteSnapshotInterval", syncSpec.LookupMetadata("client", "deviceScreenShotsInterval")) settings.deviceScreenShotsInterval = GetIntFromString(registrySection.Read("remoteSnapshotInterval")) registrySection.Write("remoteSnapshotMaxImages", syncSpec.LookupMetadata("client", "deviceScreenShotsCountLimit")) settings.deviceScreenShotsCountLimit = GetIntFromString(registrySection.Read("remoteSnapshotMaxImages")) registrySection.Write("remoteSnapshotJpegQualityLevel", syncSpec.LookupMetadata("client", "deviceScreenShotsQuality")) settings.deviceScreenShotsQuality = GetIntFromString(registrySection.Read("remoteSnapshotJpegQualityLevel")) orientationFromSyncSpec = syncSpec.LookupMetadata("client", "deviceScreenShotsOrientation") if IsNonEmptyString(orientationFromSyncSpec) then registrySection.Write("remoteSnapshotOrientation", orientationFromSyncSpec) settings.deviceScreenShotsOrientation = orientationFromSyncSpec else registrySection.Write("remoteSnapshotOrientation", "Landscape") settings.deviceScreenShotsOrientation = "Landscape" end if end if if currentDeviceScreenShotsEnabled <> settings.deviceScreenShotsEnabled then if settings.deviceScreenShotsEnabled then ' remote snapshots were off; turn them on m.bsp.remoteSnapshotTimer = CreateObject("roTimer") m.bsp.remoteSnapshotTimer.SetPort(m.bsp.msgPort) m.bsp.remoteSnapshotTimer.SetElapsed(settings.deviceScreenShotsInterval, 0) m.bsp.remoteSnapshotTimer.Start() else ' remote snapshots were on; turn them off if type(m.bsp.remoteSnapshotTimer) = "roTimer" then m.bsp.remoteSnapshotTimer.Stop() m.bsp.remoteSnapshotTimer = invalid end if end if end if end sub Sub UpdateRemoteSnapshotSettingsFromNewSettings(newSettings as object) globalAA = GetGlobalAA() bsp = globalAA.bsp settings = GetActiveSettings() currentDeviceScreenShotsEnabled = settings.deviceScreenShotsEnabled ' update global settings settings.deviceScreenShotsEnabled = newSettings.deviceScreenShotsEnabled settings.deviceScreenShotsInterval = newSettings.deviceScreenShotsInterval settings.deviceScreenShotsCountLimit = newSettings.deviceScreenShotsCountLimit settings.deviceScreenShotsQuality = newSettings.deviceScreenShotsQuality settings.deviceScreenShotsOrientation = newSettings.deviceScreenShotsOrientation if currentDeviceScreenShotsEnabled <> settings.deviceScreenShotsEnabled then if settings.deviceScreenShotsEnabled then ' remote snapshots were off; turn them on bsp.remoteSnapshotTimer = CreateObject("roTimer") bsp.remoteSnapshotTimer.SetPort(bsp.msgPort) bsp.remoteSnapshotTimer.SetElapsed(settings.deviceScreenShotsInterval, 0) bsp.remoteSnapshotTimer.Start() else ' remote snapshots were on; turn them off if type(bsp.remoteSnapshotTimer) = "roTimer" then bsp.remoteSnapshotTimer.Stop() bsp.remoteSnapshotTimer = invalid end if end if end if if settings.deviceScreenShotsEnabled then DeleteExcessSnapshots() endif end sub ' invoked when changing snapshot properties on BrightSign app Function SetSnapshotConfiguration(userData as object, e as object) as object globalAA = GetGlobalAA() mVar = userData.mVar mVar.diagnostics.PrintDebug("### SetSnapshotConfiguration") mVar.logging.WriteDiagnosticLogEntry(mVar.diagnosticCodes.EVENT_SET_SNAPSHOT_CONFIGURATION, "") e.AddResponseHeader("Content-type", "text/plain") args = e.GetFormData() if globalAA.useSupervisorConfigSpec then systemTime = CreateObject("roSystemTime") dt = systemTime.GetUtcDateTime() lastModifiedTime = FormatDateTime(dt) + "Z" supervisorSettings = {} if args.enableRemoteSnapshot <> invalid then supervisorSettings.AddReplace("deviceScreenShotsEnabled", GetBoolFromString(args.enableRemoteSnapshot, false)) end if if args.remoteSnapshotInterval <> invalid then supervisorSettings.AddReplace("deviceScreenShotsInterval", int(val(args.remoteSnapshotInterval))) end if if args.remoteSnapshotMaxImages <> invalid then supervisorSettings.AddReplace("deviceScreenShotsCountLimit", int(val(args.remoteSnapshotMaxImages))) end if if args.remoteSnapshotJpegQualityLevel <> invalid then supervisorSettings.AddReplace("deviceScreenShotsQuality", int(val(args.remoteSnapshotJpegQualityLevel))) end if ' check both remoteSnapshotOrientation (new name) and remoteSnapshotDisplayPortrait (obsolete name still used by BrightSignApp) supervisorSettings.AddReplace("deviceScreenShotsOrientation", "Landscape") if IsNonEmptyString(args.remoteSnapshotOrientation) then supervisorSettings.AddReplace("deviceScreenShotsOrientation", args.remoteSnapshotOrientation) else if IsNonEmptyString(args.remoteSnapshotDisplayPortrait) then if lcase(args.remoteSnapshotDisplayPortrait) = "true" then supervisorSettings.AddReplace("deviceScreenShotsOrientation", "PortraitBottomLeft") end if end if SendUpdatedSettingsToSupervisor(lastModifiedTime, supervisorSettings) else settings = GetActiveSettings() registrySection = globalAA.registrySection currentDeviceScreenShotsEnabled = settings.deviceScreenShotsEnabled currentDeviceScreenShotsInterval = settings.deviceScreenShotsInterval ' set global values from form data args = e.GetFormData() if args.enableRemoteSnapshot <> invalid then settings.deviceScreenShotsEnabled = GetBoolFromString(args.enableRemoteSnapshot, false) registrySection.Write("enableRemoteSnapshot", GetYesNoFromBool(settings.deviceScreenShotsEnabled)) end if if args.remoteSnapshotInterval <> invalid then settings.deviceScreenShotsInterval = int(val(args.remoteSnapshotInterval)) registrySection.Write("remoteSnapshotInterval", stri(settings.deviceScreenShotsInterval)) end if if args.remoteSnapshotMaxImages <> invalid then settings.deviceScreenShotsCountLimit = int(val(args.remoteSnapshotMaxImages)) registrySection.Write("remoteSnapshotMaxImages", stri(settings.deviceScreenShotsCountLimit)) end if if args.remoteSnapshotJpegQualityLevel <> invalid then settings.deviceScreenShotsQuality = int(val(args.remoteSnapshotJpegQualityLevel)) registrySection.Write("remoteSnapshotJpegQualityLevel", stri(settings.deviceScreenShotsQuality)) end if settings.deviceScreenShotsOrientation = "Landscape" if IsNonEmptyString(args.remoteSnapshotOrientation) then settings.deviceScreenShotsOrientation = args.remoteSnapshotOrientation registrySection.Write("remoteSnapshotOrientation", settings.deviceScreenShotsOrientation) else if IsNonEmptyString(args.remoteSnapshotDisplayPortrait) then if lcase(args.remoteSnapshotDisplayPortrait) = "true" then settings.deviceScreenShotsOrientation = "PortraitBottomLeft" end if registrySection.Write("remoteSnapshotOrientation", settings.deviceScreenShotsOrientation) end if if settings.deviceScreenShotsEnabled then DeleteExcessSnapshots() if not currentDeviceScreenShotsEnabled or settings.deviceScreenShotsInterval <> currentDeviceScreenShotsInterval then mVar.InitiateRemoteSnapshotTimer() end if else if currentDeviceScreenShotsEnabled then mVar.RemoveRemoteSnapshotTimer() end if endif e.SetResponseBodyString("OK") e.SendResponse(200) end function Sub DeleteExcessSnapshots() globalAA = GetGlobalAA() while globalAA.listOfSnapshotFiles.Count() >= GetActiveSettings().deviceScreenShotsCountLimit and globalAA.listOfSnapshotFiles.Count() > 0 fileToDelete = globalAA.listOfSnapshotFiles.Shift() if type(globalAA.bsp.networkingHSM) = "roAssociativeArray" then if type(globalAA.bsp.networkingHSM.uploadSnapshotUrl) = "roUrlTransfer" then if lcase(globalAA.bsp.networkingHSM.uploadSnapshotUrl.getUserData().lookup("name")) = lcase(fileToDelete) then globalAA.bsp.diagnostics.PrintDebug("DeleteExcessSnapshots: roUrlTransfer in process for " + fileToDelete) globalAA.bsp.NetworkingHSM.uploadSnapshotUrl.AsyncCancel() globalAA.bsp.NetworkingHSM.uploadSnapshotUrl = invalid ' and setup the retry timer if type(globalAA.bsp.networkingHSM.retrySnapshotUploadTimer) <> "roTimer" then globalAA.bsp.networkingHSM.retrySnapshotUploadTimer = CreateObject("roTimer") globalAA.bsp.networkingHSM.retrySnapshotUploadTimer.SetPort(globalAA.bsp.networkingHSM.msgPort) globalAA.bsp.networkingHSM.retrySnapshotUploadTimer.SetElapsed(30, 0) '30 seconds seems to be what this gets set to other places. globalAA.bsp.networkingHSM.retrySnapshotUploadTimer.Start() end if end if end if if globalAA.bsp.networkingHSM.pendingSnapshotsToUpload.DoesExist(fileToDelete) then globalAA.bsp.diagnostics.PrintDebug("DeleteExcessSnapshots: removed from pendingSnapshotsToUpload: " + fileToDelete) globalAA.bsp.networkingHSM.pendingSnapshotsToUpload.Delete(fileToDelete) end if end if ok = DeleteFile("snapshots/" + fileToDelete) end while end sub Sub TakeSnapshot(systemTime as object, activePresentation$ as string) globalAA = GetGlobalAA() settings = GetActiveSettings() ' before taking snapshot, delete the oldest if necessary DeleteExcessSnapshots() videoMode = CreateObject("roVideoMode") if type(videoMode) <> "roVideoMode" then globalAA.bsp.diagnostics.PrintDebug("TakeSnapshot: cannot take snapshot as device does not support video mode") return end if ' create a file name based on the current date/time dtLocal = systemTime.GetLocalDateTime() ' strip illegal characters from string dateTime$ = GetISODateTimeString(dtLocal) ' rotation depend on screen orientation rotationDegree% = 0 isBSPObject = (type(globalAA.bsp) = "roAssociativeArray") isBSPSignObject = (isBSPObject) and (type(globalAA.bsp.sign) = "roAssociativeArray") if (isBSPSignObject and HasMultiScreenOutputs(globalAA.bsp.sign)) then ' No need to rotate if it's multi screen mode because the rotation is done in video mode per screen else if IsNonEmptyString(settings.deviceScreenShotsOrientation) then orientation$ = settings.deviceScreenShotsOrientation if IsPortraitBottomLeft(lcase(orientation$)) then rotationDegree% = 90 else if IsPortraitBottomRight(lcase(orientation$)) then rotationDegree% = 270 end if end if end if ' snapshot resolution width = videoMode.GetResX() height = videoMode.GetResY() ' if original resolution higher than 4k(3840x2160) pixels, downsample to 4k ' 1.calculate the num of pixels(p1) in 4k image and the current image(p2) fourKPixels% = 3840 * 2160 imagePixels% = width * height if fourKPixels% < imagePixels% then ' 2.ratio = square root of (p2/p1) = width or height ratio ratio = sqr(imagePixels% / fourKPixels%) ' 3.final width or height = original width or height / ratio width = int(width / ratio) height = int(height / ratio) end if aa = { } aa.filename = "snapshots/" + dateTime$ + ".jpg" aa.Width = width aa.Height = height aa.filetype = "JPEG" aa.quality = settings.deviceScreenShotsQuality aa.rotation = rotationDegree% aa.Async = 0 if IsString(activePresentation$) then aa.description = activePresentation$ end if if isBSPObject then globalAA.bsp.diagnostics.PrintDebug("------------------------------------------- SCREENSHOT " + aa.filename + "-------------------------------------") end if ok = videoMode.Screenshot(aa) if not ok then if isBSPObject then globalAA.bsp.diagnostics.PrintDebug("TakeSnapshot: not ok returned from Screenshot") globalAA.bsp.logging.WriteDiagnosticLogEntry(globalAA.bsp.diagnosticCodes.EVENT_SCREENSHOT_ERROR, "") end if else globalAA.listOfSnapshotFiles.push(dateTime$ + ".jpg") ' upload snapshot to BSN if it's initialized if isBSPObject then snapshotCaptured = { } snapshotCaptured["EventType"] = "SNAPSHOT_CAPTURED" snapshotCaptured["SnapshotName"] = dateTime$ + ".jpg" globalAA.bsp.msgPort.PostMessage(snapshotCaptured) end if end if videoMode = invalid end sub 'endregion 'region settingsInConfig Sub UpdateSettingsFromConfig() newSettings = GetPendingSettings() bsp = GetGlobalAA().bsp ' deviceScreenShots, etc. UpdateRemoteSnapshotSettingsFromNewSettings(newSettings) ' ****loggingEnabled playbackLoggingEnabled = newSettings.playbackLoggingEnabled eventLoggingEnabled = newSettings.eventLoggingEnabled diagnosticLoggingEnabled = newSettings.diagnosticLoggingEnabled stateLoggingEnabled = newSettings.stateLoggingEnabled variableLoggingEnabled = newSettings.variableLoggingEnabled uploadLogFilesAtBoot = newSettings.uploadLogFilesAtBoot uploadLogFilesAtSpecificTime = newSettings.uploadLogFilesAtSpecificTime uploadLogFilesTime% = newSettings.uploadLogFilesTime bsp.logging.ReinitializeLogging(playbackLoggingEnabled, eventLoggingEnabled, stateLoggingEnabled, diagnosticLoggingEnabled, variableLoggingEnabled, uploadLogFilesAtBoot, uploadLogFilesAtSpecificTime, uploadLogFilesTime%) ' contentDownloadsRestricted contentDownloadsRestricted = newSettings.contentDownloadsRestricted notInDownloadWindow = false if contentDownloadsRestricted then currentTime = bsp.systemTime.GetLocalDateTime() startOfRange% = newSettings.contentDownloadRangeStart endOfRange% = startOfRange% + newSettings.contentDownloadRangeLength notInDownloadWindow = OutOfDownloadWindow(currentTime, startOfRange%, endOfRange%) end if ' rate limits downloadRateLimits = GetDownloadRateLimits(newSettings, notInDownloadWindow) SetDownloadRateLimits(bsp.diagnostics, downloadRateLimits) end sub 'endregion 'region supervisor Function GetSupervisorSupport() supervisorSupport = invalid localBaseUrl = "http://localhost" supervisorApiV1Route = "/api/v1" supervisorSupportUrlXfer = CreateObject("roUrlTransfer") supervisorSupportUrlXfer$ = localBaseUrl + supervisorApiV1Route + "/system" supervisorSupportUrlXfer.SetUrl(supervisorSupportUrlXfer$) supervisorSupportUrlXfer.SetTimeout(15000) supervisorSupportUrlXfer.SetProxyBypass(["127.0.0.1", "localhost"]) supervisorSupportStr = supervisorSupportUrlXfer.GetToString() if IsString(supervisorSupportStr) and len(supervisorSupportStr) > 0 then supervisorSupport = ParseJson(supervisorSupportStr) if type(supervisorSupport) = "roAssociativeArray" and type(supervisorSupport.data) = "roAssociativeArray" then if type(supervisorSupport.data.result) = "roAssociativeArray" then supervisorSupport = supervisorSupport.data.result endif endif endif return supervisorSupport end function Function GetSupervisorSupportsConfigureNetwork() as boolean supervisorSupport = GetGlobalAA().supervisorSupport if type(supervisorSupport) <> "roAssociativeArray" then return false endif url = CreateObject("roUrlTransfer") bootstrapRestBase = "http://127.0.0.1" registrationRequestUrl = bootstrapRestBase + "/api/v1/system/supervisor/registration" url.SetUrl(registrationRequestUrl) url.SetProxyBypass(["127.0.0.1", "localhost"]) ' BCN-8732 result = url.GetToString() if result <> "" then payload = ParseJson(result) if type(payload) = "roAssociativeArray" and type(payload.data) = "roAssociativeArray" and type(payload.data.result) = "roAssociativeArray" then if type(payload.data.result.setupSupport) = "roAssociativeArray" and IsBoolean(payload.data.result.setupSupport.network) and payload.data.result.setupSupport.network then return true endif endif endif return false end function Function GetSupervisorVersion() as string supervisorSupport = GetGlobalAA().supervisorSupport if type(supervisorSupport) <> "roAssociativeArray" then supervisorVersion = "preS2P" else if type(supervisorSupport.supportAutorunNetwork) = "roAssociativeArray" then supervisorVersion = "s3P" else autorunConfigEndpointFound = AutorunConfigEndpointExists() if not autorunConfigEndpointFound then supervisorVersion = "preS2P" else supervisorVersion = "s2P" endif endif endif return supervisorVersion end function Function GetEnableSettingsHandler() as boolean supervisorSupport = GetGlobalAA().supervisorSupport supervisorVersion = GetGlobalAA().supervisorVersion if supervisorVersion = "s3P" or supervisorVersion = "s2P" then if IsBoolean(supervisorSupport.settingsHandler.enabled) and supervisorSupport.settingsHandler.enabled then ' TEDTODO - issue if the user publishes a presentation without ever having run setup return true else return false endif else if supervisorVersion = "preS2P" then return false else ' TEDTODO stop return false endif end function Function GetIsCellularModemActive() as boolean cellularModemActive = false supervisorSupport = GetGlobalAA().supervisorSupport if type(supervisorSupport) = "roAssociativeArray" then cellularModemActive = supervisorSupport.LookupCi("cellularModemActive") if cellularModemActive = invalid then cellularModemActive = false endif endif return cellularModemActive end function Sub CreateAutorunConfigMsgPort() globalAA = GetGlobalAA() if type(globalAA.autorunConfigMsgPort) <> "roMessagePort" then globalAA.autorunConfigMsgPort = CreateObject("roMessagePort") if type(globalAA.autorunConfigMsgPort) <> "roMessagePort" then stop endif endif end sub Function InvokeAutorunConfigGet() as object globalAA = GetGlobalAA() ' GET http://[device-ip]/api/v1/system/supervisor/autorun-config localBaseUrl = "http://localhost" supervisorApiV1Route = "/api/v1" autorunConfigUrlXfer = CreateObject("roUrlTransfer") autorunConfigUrlXfer$ = localBaseUrl + supervisorApiV1Route + "/system/supervisor/autorun-config" autorunConfigUrlXfer.SetUrl(autorunConfigUrlXfer$) autorunConfigUrlXfer.SetPort(globalAA.autorunConfigMsgPort) autorunConfigUrlXfer.SetProxyBypass(["127.0.0.1", "localhost"]) ok = autorunConfigUrlXfer.AsyncGetToString() if not ok then stop endif return autorunConfigUrlXfer end function Function GetAutorunConfigEvent() as object while true event = wait(5000, GetGlobalAA().autorunConfigMsgPort) if type(event) = "roUrlEvent" then return event endif endwhile end function Function AutorunConfigEndpointExists() as boolean globalAA = GetGlobalAA() CreateAutorunConfigMsgPort() autorunConfigUrlXfer = InvokeAutorunConfigGet() maxRetryCount = 5 currentRetryAttempt = 0 while currentRetryAttempt < maxRetryCount event = GetAutorunConfigEvent() if event.getResponseCode() = 200 then return true else if event.getResponseCode() = 404 then return false endif sleep(1000) ' retry ok = autorunConfigUrlXfer.AsyncGetToString() if not ok then stop endif currentRetryAttempt = currentRetryAttempt + 1 end while return false end function Function GetAutorunConfigFromSupervisor() globalAA = GetGlobalAA() CreateAutorunConfigMsgPort() autorunConfigUrlXfer = InvokeAutorunConfigGet() while true event = GetAutorunConfigEvent() if event.getResponseCode() = 200 then autorunConfigStr = event.GetString() if IsBoolean(globalAA.debug_settings) and globalAA.debug_settings then WriteConfigToStorage(autorunConfigStr) endif autorunConfig = ParseJSON(autorunConfigStr) if type(autorunConfig) = "roAssociativeArray" and type(autorunConfig.data) = "roAssociativeArray" and type(autorunConfig.data.result) = "roAssociativeArray" and type(autorunConfig.data.result.settings) = "roAssociativeArray" then return autorunConfig.data.result.settings endif endif sleep(1000) ' retry ok = autorunConfigUrlXfer.AsyncGetToString() if not ok then stop endif end while end function ' this function is not called by the autorun, but can be accessed from within the debugger as follows ' config = GetAutorunConfigFromSupervisorSync() Function GetAutorunConfigFromSupervisorSync() localBaseUrl = "http://localhost" supervisorApiV1Route = "/api/v1" autorunConfigUrlXfer = CreateObject("roUrlTransfer") autorunConfigUrlXfer$ = localBaseUrl + supervisorApiV1Route + "/system/supervisor/autorun-config" autorunConfigUrlXfer.SetUrl(autorunConfigUrlXfer$) autorunConfigUrlXfer.SetProxyBypass(["127.0.0.1", "localhost"]) autorunConfigStr = autorunConfigUrlXfer.GetToString() systemTime = createObject("roSystemTime") dateTime = systemTime.GetLocalDateTime() isoDateTime = GetISODateTimeString(dateTime) ok = WriteAsciiFile("config" + isoDateTime + ".json", autorunConfigStr) autorunConfig = ParseJSON(autorunConfigStr) return autorunConfig.data.result.payload end function Sub CreateControlCloudInterface() globalAA = GetGlobalAA() ' If we have a new enough system, use the control cloud access method globalAA.ccloud = CreateObject("roControlCloud") if globalAA.ccloud <> invalid then globalAA.ccloud.SetPort(globalAA.msgPort) globalAA.ccloud.SetUserData("bootstrap") ' send an empty message to indicate we'll use roControlCloud globalAA.ccloud.SendMessage({}) endif end sub Sub SendAutorunCapabilitiesToSupervisorViaPost(plugins as object, presentationName as string) localBaseUrl = "http://localhost" supervisorApiV1Route = "/api/v1" supervisorCapabilitiesUrlXfer = CreateObject("roUrlTransfer") supervisorCapabilitiesUrlXfer$ = localBaseUrl + supervisorApiV1Route + "/system/supervisor/capabilities" supervisorCapabilitiesUrlXfer.SetUrl(supervisorCapabilitiesUrlXfer$) supervisorCapabilitiesUrlXfer.SetTimeout(15000) supervisorCapabilitiesUrlXfer.SetProxyBypass(["127.0.0.1", "localhost"]) supervisorCapabilitiesUrlXfer.addHeader("Content-type", "application/json") reqBody = BuildCapabilitiesBody(plugins, presentationName) stringifiedJson = FormatJson(reqBody) aa = {} aa.method = "POST" aa.request_body_string = stringifiedJson aa.response_body_string = true postEvent = supervisorCapabilitiesUrlXfer.SyncMethod(aa) ' for s3P, check the response if GetGlobalAA().supervisorVersion = "s3P" then rc = postEvent.getResponseCode() if rc = 200 then result = postEvent.getString() resultBody = ParseJSON(result) if type(resultBody) = "roAssociativeArray" and type(resultBody.data) = "roAssociativeArray" and type(resultBody.data.result) = "roAssociativeArray" and IsBoolean(resultBody.data.result.success) and resultBody.data.result.success then ok = true else ok = false endif else ok = false endif endif end sub Sub SendAutorunCapabilitiesToSupervisorViaUdp(plugins as object, presentationName as string) if m.dgSocket <> invalid then script = ConstructAutorunScriptObject(plugins) presentation = ConstructAutorunPresentationObject(presentationName) body = {} body.AddReplace("script", script) if type(presentation) = "roAssociativeArray" then body.AddReplace("presentation", presentation) endif payload = {} payload.AddReplace("route", "/v1/script-status") payload.AddReplace("body", body) jsonArray = {} jsonArray.AddReplace("payload", payload) jsonString = FormatJson(jsonArray) result = m.dgSocket.SendTo("127.0.0.1", GetGlobalAA().registrySettings.wsUdpSocketPort%, jsonString) end if end sub Sub SendAutorunCapabilitiesToSupervisorViaIpc(plugins as object, presentationName as string) globalAA = GetGlobalAA() if globalAA.ccloud <> invalid then ' TEDTODO - globalAA.ccloud <> invalid for the BCN-10310 scenario - is that a problem? script = ConstructAutorunScriptObject(plugins) presentation = ConstructAutorunPresentationObject(presentationName) body = {} body.AddReplace("script", script) if type(presentation) = "roAssociativeArray" then body.AddReplace("presentation", presentation) endif payload = {} payload.AddReplace("route", "/v1/script-status") payload.AddReplace("body", body) jsonArray = {} jsonArray.AddReplace("payload", payload) globalAA.ccloud.SendMessage(jsonArray) endif end sub Sub ResendAutorunCapabilitiesToSupervisor(plugins as object, presentationName as string) supervisorVersion = GetGlobalAA().supervisorVersion if supervisorVersion = "s2P" or supervisorVersion = "s3P" then SendAutorunCapabilitiesToSupervisorViaPost(plugins, presentationName) else m.SendAutorunCapabilitiesToSupervisorViaUdp(plugins, presentationName) endif end sub Function BuildCapabilitiesBody(plugins as object, presentationName as string) as object settings = ConstructSettingsApplied() script = ConstructAutorunScriptObject(plugins) presentation = ConstructAutorunPresentationObject(presentationName) body = {} body.AddReplace("settings", settings) body.AddReplace("script", script) if type(presentation) = "roAssociativeArray" then body.AddReplace("presentation", presentation) endif return body end function Function ConstructSettingsApplied() as object settingsApplied = ["data-types", "content-downloads", "screenshots", "rate-limits", "log-uploads", "logging-enabled"] settings = {} settings.AddReplace("min-supervisor-version", "1.2.82") settings.AddReplace("autorun-applied", settingsApplied) settings.AddReplace("enabled", GetGlobalAA().supervisorEnableSettingsHandler) return settings end function Function ConstructAutorunScriptObject(plugins as object) as object sysInfo = GetGlobalAA().sysInfo script = CreateObject("roAssociativeArray") scriptType$ = "Autorun" version$ = sysInfo.autorunVersion$ ' autorun version use the same logic as firmware version ' reuse the function CompareFirmwareVersions autorunVsCustomAutorunVersion% = CompareFirmwareVersions(sysInfo.autorunVersion$, sysInfo.customAutorunVersion$) if autorunVsCustomAutorunVersion% <= 0 then scriptType$ = "Custom" version$ = sysInfo.customAutorunVersion$ end if script.AddReplace("type", scriptType$) script.AddReplace("version", version$) ' Constructing jsonArray.payload.body.script.plugins if type(plugins) = "roArray" and plugins.count() > 0 then script.AddReplace("plugins", plugins) end if return script end function Function ConstructAutorunPresentationObject(presentationName as string) as object presentation = invalid if presentationName <> invalid and presentationName <> "" then presentation = {} ' Hardcode presentation type to "Regular" until BrightWall feature is in place presentation.AddReplace("type", "Regular") presentation.AddReplace("name", presentationName) end if return presentation end function Sub WriteConfigToStorage(autorunConfigStr as string) systemTime = createObject("roSystemTime") dateTime = systemTime.GetLocalDateTime() isoDateTime = GetISODateTimeString(dateTime) ok = WriteAsciiFile("config" + isoDateTime + ".json", autorunConfigStr) end sub ' Autorun notifies the supervisor when it receives a new sync spec Sub SendUpdatedSettingsToSupervisor(lastModifiedTimestamp as string, settings as object) globalAA = GetGlobalAA() if not globalAA.useSupervisorConfigSpec then return endif ' PUT /api/v1/system/supervisor/autorunSettingsUpdate ' https://jira.brightsign.biz/browse/BCN-8534 ' https://docs.brightsign.biz/display/DOC/roUrlTransfer#roUrlTransfer-PutFromString(aAsString)AsInteger ' PutFromString(a As String) As Integer ' Uses the HTTP PUT method to write the supplied string to the current URL and return the response code. ' Any response body is discarded; use roUrlTransfer.SyncMethod to retrieve the response body. ' https://docs.brightsign.biz/display/BSV61/6.1-Global+Functions#id-6.1GlobalFunctions-FormatJson(jsonAsroAssociativeArray,flagsAsInteger)AsString ' FormatJson(json As roAssociativeArray, flags As Integer) As String supervisorSettingsUpdateHandlerUrl = CreateObject("roUrlTransfer") supervisorSettingsUpdateHandlerUrl.SetUrl("http://localhost/api/v1/system/supervisor/autorun-settings-update") supervisorSettingsUpdateHandlerUrl.addHeader("Content-type", "application/json") supervisorSettingsUpdateHandlerUrl.SetTimeout(15000) supervisorSettingsUpdateHandlerUrl.SetProxyBypass(["127.0.0.1", "localhost"]) supervisorSettings = PopulateSupervisorSettings(settings) reqBody = {} reqBody.SetModeCaseSensitive() reqBody.AddReplace("settings", supervisorSettings) reqBody.AddReplace("lastModifiedTimeStamp", lastModifiedTimeStamp) stringifiedJson = FormatJson(reqBody) rc = supervisorSettingsUpdateHandlerUrl.PutFromString(stringifiedJson) ' TEDTODO-Subban - for dev purposes only? if rc <> 200 then stop endif end sub Sub PopulateSupervisorSettings(settings as object) as object supervisorSettings = {} PopulateSettingIfItExists(supervisorSettings, settings, "playbackLoggingEnabled") PopulateSettingIfItExists(supervisorSettings, settings, "eventLoggingEnabled") PopulateSettingIfItExists(supervisorSettings, settings, "diagnosticLoggingEnabled") PopulateSettingIfItExists(supervisorSettings, settings, "stateLoggingEnabled") PopulateSettingIfItExists(supervisorSettings, settings, "variableLoggingEnabled") PopulateSettingIfItExists(supervisorSettings, settings, "uploadLogFilesAtBoot") PopulateSettingIfItExists(supervisorSettings, settings, "uploadLogFilesAtSpecificTime") PopulateSettingIfItExists(supervisorSettings, settings, "uploadLogFilesTime") PopulateSettingIfItExists(supervisorSettings, settings, "contentDownloadsRestricted") PopulateSettingIfItExists(supervisorSettings, settings, "contentDownloadRangeStart") PopulateSettingIfItExists(supervisorSettings, settings, "contentDownloadRangeLength") PopulateSettingIfItExists(supervisorSettings, settings, "deviceScreenShotsEnabled") PopulateSettingIfItExists(supervisorSettings, settings, "deviceScreenShotsInterval") PopulateSettingIfItExists(supervisorSettings, settings, "deviceScreenShotsCountLimit") PopulateSettingIfItExists(supervisorSettings, settings, "deviceScreenShotsQuality") PopulateSettingIfItExists(supervisorSettings, settings, "deviceScreenShotsOrientation") return supervisorSettings end sub Sub PopulateSettingIfItExists(newSettings as object, existingSettings as object, propertyName as string) if existingSettings.DoesExist(propertyName) then newSettings.AddReplace(propertyName, existingSettings[propertyName]) endif end sub 'end region 'region Non Settings Supervisor specific code Sub ReadLegacyRegistrySettings(registrySection as object, registrySettings as object) registrySettings.inheritNetworkProperties = registrySection.Read("inp") registrySettings.useProxy = registrySection.Read("up") if registrySettings.useProxy = "yes" then registrySettings.proxy$ = registrySection.Read("ps") registrySettings.networkHosts$ = registrySection.Read("bph") else registrySettings.proxy$ = "" registrySettings.networkHosts$ = "" end if registrySettings.useWireless$ = registrySection.Read("wifi") registrySettings.ssid$ = registrySection.Read("ss") registrySettings.passphrase$ = registrySection.Read("pp") registrySettings.timeServer$ = registrySection.Read("ts") registrySettings.wiredNetworkingParameters = { } registrySettings.wiredNetworkingParameters.networkConnectionPriority$ = registrySection.Read("ncp") registrySettings.wirelessNetworkingParameters = { } registrySettings.wirelessNetworkingParameters.networkConnectionPriority$ = registrySection.Read("ncp2") if registrySettings.useWireless$ = "yes" then registrySettings.wirelessNetworkingParameters.useDHCP$ = registrySection.Read("dhcp") registrySettings.wirelessNetworkingParameters.staticIPAddress$ = registrySection.Read("sip") registrySettings.wirelessNetworkingParameters.subnetMask$ = registrySection.Read("sm") registrySettings.wirelessNetworkingParameters.gateway$ = registrySection.Read("gw") registrySettings.wirelessNetworkingParameters.dns1$ = registrySection.Read("d1") registrySettings.wirelessNetworkingParameters.dns2$ = registrySection.Read("d2") registrySettings.wirelessNetworkingParameters.dns3$ = registrySection.Read("d3") registrySettings.wiredNetworkingParameters.useDHCP$ = registrySection.Read("dhcp2") registrySettings.wiredNetworkingParameters.staticIPAddress$ = registrySection.Read("sip2") registrySettings.wiredNetworkingParameters.subnetMask$ = registrySection.Read("sm2") registrySettings.wiredNetworkingParameters.gateway$ = registrySection.Read("gw2") registrySettings.wiredNetworkingParameters.dns1$ = registrySection.Read("d12") registrySettings.wiredNetworkingParameters.dns2$ = registrySection.Read("d22") registrySettings.wiredNetworkingParameters.dns3$ = registrySection.Read("d32") else registrySettings.wiredNetworkingParameters.useDHCP$ = registrySection.Read("dhcp") registrySettings.wiredNetworkingParameters.staticIPAddress$ = registrySection.Read("sip") registrySettings.wiredNetworkingParameters.subnetMask$ = registrySection.Read("sm") registrySettings.wiredNetworkingParameters.gateway$ = registrySection.Read("gw") registrySettings.wiredNetworkingParameters.dns1$ = registrySection.Read("d1") registrySettings.wiredNetworkingParameters.dns2$ = registrySection.Read("d2") registrySettings.wiredNetworkingParameters.dns3$ = registrySection.Read("d3") end if registrySettings.contentXfersEnabledWired$ = registrySection.Read("cwr") registrySettings.textFeedsXfersEnabledWired$ = registrySection.Read("twr") registrySettings.healthXfersEnabledWired$ = registrySection.Read("hwr") registrySettings.mediaFeedsXfersEnabledWired$ = registrySection.Read("mwr") registrySettings.logUploadsXfersEnabledWired$ = registrySection.Read("lwr") registrySettings.contentXfersEnabledWireless$ = registrySection.Read("cwf") registrySettings.textFeedsXfersEnabledWireless$ = registrySection.Read("twf") registrySettings.healthXfersEnabledWireless$ = registrySection.Read("hwf") registrySettings.mediaFeedsXfersEnabledWireless$ = registrySection.Read("mwf") registrySettings.logUploadsXfersEnabledWireless$ = registrySection.Read("lwf") registrySettings.dwsEnabled = GetBoolFromString(registrySection.Read("dwse"), false) registrySettings.dwsPassword$ = registrySection.Read("dwsp") registrySettings.deviceScreenShotsEnabled = registrySection.Read("enableRemoteSnapshot") registrySettings.deviceScreenShotsInterval = registrySection.Read("remoteSnapshotInterval") registrySettings.deviceScreenShotsCountLimit = registrySection.Read("remoteSnapshotMaxImages") registrySettings.deviceScreenShotsQuality = registrySection.Read("remoteSnapshotJpegQualityLevel") registrySettings.deviceScreenShotsOrientation = registrySection.Read("remoteSnapshotOrientation") end sub Sub UpdateSettingsFromSyncSpec(newSettings as object, newSyncSpecSettings as object) m.ProcessSyncSpecSettingsUpdates0(newSettings) ' parameters are not set in SFN case, so don't change them here if lcase(GetGlobalAA().settings.setupType) <> "sfn" then m.ProcessSyncSpecSettingsUpdates2(newSettings) endif m.ProcessSyncSpecSettingsUpdates3(newSettings, newSyncSpecSettings) end sub Sub ProcessSyncSpecSettingsUpdates0(newSettings as object) ' Retrieve network connection priorities from the sync spec networkConnectionPriorityWired% = newSettings.networkConnectionPriorityWired nc = CreateObject("roNetworkConfiguration", 0) if type(nc) = "roNetworkConfiguration" then nc.SetRoutingMetric(networkConnectionPriorityWired%) nc.Apply() nc = invalid end if networkConnectionPriorityWireless% = newSettings.networkConnectionPriorityWireless nc = CreateObject("roNetworkConfiguration", 1) if type(nc) = "roNetworkConfiguration" then nc.SetRoutingMetric(networkConnectionPriorityWireless%) nc.Apply() nc = invalid end if ' Retrieve logging information from the sync spec playbackLoggingEnabled = newSettings.playbackLoggingEnabled eventLoggingEnabled = newSettings.eventLoggingEnabled diagnosticLoggingEnabled = newSettings.diagnosticLoggingEnabled stateLoggingEnabled = newSettings.stateLoggingEnabled variableLoggingEnabled = newSettings.variableLoggingEnabled uploadLogFilesAtBoot = newSettings.uploadLogFilesAtBoot uploadLogFilesAtSpecificTime = newSettings.uploadLogFilesAtSpecificTime uploadLogFilesTime% = newSettings.uploadLogFilesTime m.stateMachine.logging.ReinitializeLogging(playbackLoggingEnabled, eventLoggingEnabled, stateLoggingEnabled, diagnosticLoggingEnabled, variableLoggingEnabled, uploadLogFilesAtBoot, uploadLogFilesAtSpecificTime, uploadLogFilesTime%) end sub ' Update settings on bsn sync spec update. These are the settings that are not updated on sfn sync spec updates. Sub ProcessSyncSpecSettingsUpdates2(newSettings as object) settings = GetActiveSettings() unitNameFromRegistry$ = settings.unitName unitNamingMethodFromRegistry$ = settings.unitNamingMethod unitDescriptionFromRegistry$ = settings.unitDescription unitName$ = newSettings.unitName unitNamingMethod$ = newSettings.unitNamingMethod unitDescription$ = newSettings.unitDescription if unitName$ <> unitNameFromRegistry$ then WriteRegistrySetting("un", unitName$) settings.unitName = unitName$ end if if unitNamingMethod$ <> unitNamingMethodFromRegistry$ then WriteRegistrySetting("unm", unitNamingMethod$) settings.unitNamingMethod = unitNamingMethod$ end if if unitDescription$ <> unitDescriptionFromRegistry$ then WriteRegistrySetting("ud", unitDescription$) settings.unitDescription = unitDescription$ end if proxyFromSyncSpec$ = newSettings.proxy if proxyFromSyncSpec$ <> "" then useProxy$ = "yes" else useProxy$ = "no" end if useProxy$ = m.UpdateRegistrySetting(useProxy$, GetGlobalAA().registrySettings.useProxy, "up") proxySpec$ = m.UpdateRegistrySetting(newSettings.proxy, GetGlobalAA().registrySettings.proxy$, "ps") nc = CreateObject("roNetworkConfiguration", 0) if type(nc) = "roNetworkConfiguration" then ok = nc.SetProxy(proxySpec$) nc.Apply() end if nc = CreateObject("roNetworkConfiguration", 1) if type(nc) = "roNetworkConfiguration" then ok = nc.SetProxy(proxySpec$) nc.Apply() end if nc = invalid networkHosts$ = m.UpdateRegistrySetting(newSettings.networkHosts, GetGlobalAA().registrySettings.networkHosts$, "bph") networkHosts = ParseJSON(networkHosts$) bypassProxyHosts = [] for each networkHost in networkHosts if networkHost.HostName <> "" then bypassProxyHosts.push(networkHost.HostName) end if next ' bypass proxy servers nc = CreateObject("roNetworkConfiguration", 0) if type(nc) = "roNetworkConfiguration" then ok = nc.SetProxyBypass(bypassProxyHosts) nc.Apply() end if nc = CreateObject("roNetworkConfiguration", 1) if type(nc) = "roNetworkConfiguration" then ok = nc.SetProxyBypass(bypassProxyHosts) nc.Apply() end if nc = invalid end sub Sub ProcessSyncSpecSettingsUpdates3(newSettings as object, newSyncSpecSettings as object) ' Retrieve latest network configuration information from sync spec timeServer$ = newSettings.timeServer useWireless = newSettings.useWireless if not m.stateMachine.modelSupportsWifi then useWireless = false ' TEDTODO - parameters are not accurate for SFN, but AFAIK, it has no negative impact if useWireless then ssid$ = newSettings.ssid passphrase$ = newSettings.passphrase end if wiredInterfaceIndex = GetWiredInterfaceIndex(newSettings) wiredInterface = newSettings.network.interfaces[wiredInterfaceIndex] wirelessInterface = invalid wirelessInterfaceIndex = GetWirelessInterfaceIndex(newSettings) if wirelessInterfaceIndex <> invalid then wirelessInterface = newSettings.network.interfaces[wirelessInterfaceIndex] endif ' don't update settings if SFN publish type if lcase(GetGlobalAA().settings.setupType) <> "sfn" then wiredNetworkingParameters = { } wiredNetworkingParameters.networkConfigurationIndex% = 0 wiredNetworkingParameters.networkConnectionPriority = newSettings.networkConnectionPriorityWired if useWireless and wirelessInterface <> invalid then wirelessNetworkingParameters = { } wirelessNetworkingParameters.networkConfigurationIndex% = 1 wirelessNetworkingParameters.networkConnectionPriority = newSettings.networkConnectionPriorityWireless wirelessNetworkingParameters.useDHCP$ = wirelessInterface.useDHCP if wirelessInterface.useDHCP = "no" then wirelessNetworkingParameters.staticIPAddress$ = wirelessInterface.staticIPAddress wirelessNetworkingParameters.subnetMask$ = wirelessInterface.subnetMask wirelessNetworkingParameters.gateway$ = wirelessInterface.gateway wirelessNetworkingParameters.dns1$ = wirelessInterface.dns1 wirelessNetworkingParameters.dns2$ = wirelessInterface.dns2 wirelessNetworkingParameters.dns3$ = wirelessInterface.dns3 end if wirelessNetworkingParameters.useWireless = true wirelessNetworkingParameters.ssid$ = ssid$ wirelessNetworkingParameters.passphrase$ = passphrase$ wirelessNetworkingParameters.timeServer$ = timeServer$ m.ConfigureNetwork(wirelessNetworkingParameters, GetGlobalAA().registrySettings.wirelessNetworkingParameters, "") wiredNetworkingParameters.useDHCP$ = wiredInterface.useDHCP if wiredNetworkingParameters.useDHCP$ = "no" then wiredNetworkingParameters.staticIPAddress$ = wiredInterface.staticIPAddress wiredNetworkingParameters.subnetMask$ = wiredInterface.subnetMask wiredNetworkingParameters.gateway$ = wiredInterface.gateway wiredNetworkingParameters.dns1$ = wiredInterface.dns1 wiredNetworkingParameters.dns2$ = wiredInterface.dns2 wiredNetworkingParameters.dns3$ = wiredInterface.dns3 end if wiredNetworkingParameters.useWireless = false wiredNetworkingParameters.timeServer$ = timeServer$ m.ConfigureNetwork(wiredNetworkingParameters, GetGlobalAA().registrySettings.wiredNetworkingParameters, "2") else wiredNetworkingParameters.useDHCP$ = wiredInterface.useDHCP if wiredNetworkingParameters.useDHCP$ = "no" then wiredNetworkingParameters.staticIPAddress$ = wiredInterface.staticIPAddress wiredNetworkingParameters.subnetMask$ = wiredInterface.subnetMask wiredNetworkingParameters.gateway$ = wiredInterface.gateway wiredNetworkingParameters.dns1$ = wiredInterface.dns1 wiredNetworkingParameters.dns2$ = wiredInterface.dns2 wiredNetworkingParameters.dns3$ = wiredInterface.dns3 end if wiredNetworkingParameters.useWireless = false wiredNetworkingParameters.timeServer$ = timeServer$ m.ConfigureNetwork(wiredNetworkingParameters, GetGlobalAA().registrySettings.wiredNetworkingParameters, "") ' if a device is setup to not use wireless, ensure that wireless is not used if m.stateMachine.modelSupportsWifi then nc = CreateObject("roNetworkConfiguration", 1) if type(nc) = "roNetworkConfiguration" then nc.SetDHCP() nc.SetWiFiESSID("") nc.SetObfuscatedWifiPassphrase("") nc.Apply() end if end if end if end if useWirelessFromRegistry$ = GetGlobalAA().registrySettings.useWireless$ ' TEDTODO - SFN: correct the first time; broken afterwards; probably due to bug below ssidFromRegistry$ = GetGlobalAA().registrySettings.ssid$ passphraseFromRegistry$ = GetGlobalAA().registrySettings.passphrase$ useWireless$ = GetYesNoFromBool(useWireless) if useWirelessFromRegistry$ <> useWireless$ then WriteRegistrySetting("wifi", useWireless$) GetGlobalAA().registrySettings.useWireless$ = useWireless$ end if if useWireless then if ssidFromRegistry$ <> ssid$ then ' TEDTODO - SFN. this is messed up; it writes an empty string to the registry. WriteRegistrySetting("ss", ssid$) GetGlobalAA().registrySettings.ssid$ = ssid$ end if if passphraseFromRegistry$ <> passphrase$ then WriteRegistrySetting("pp", passphrase$) GetGlobalAA().registrySettings.passphrase$ = passphrase$ end if end if timeServerFromRegistry$ = GetGlobalAA().registrySettings.timeServer$ if timeServerFromRegistry$ <> timeServer$ then WriteRegistrySetting("ts", timeServer$) GetGlobalAA().registrySettings.timeServer$ = timeServer$ end if ' Retrieve latest net connect spec information from sync spec timeBetweenNetConnects$ = newSyncSpecSettings.timeBetweenNetConnects if timeBetweenNetConnects$ <> "" then ' if the timeBetweenNetConnects has changed, restart the timer newTimeBetweenNetConnects% = val(timeBetweenNetConnects$) if newTimeBetweenNetConnects% <> m.stateMachine.timeBetweenNetConnects% then m.stateMachine.timeBetweenNetConnects% = newTimeBetweenNetConnects% m.bsp.diagnostics.PrintDebug("### Time between net connects has changed to: " + timeBetweenNetConnects$) else m.bsp.diagnostics.PrintDebug("### Time between net connects = " + timeBetweenNetConnects$) end if end if ' clear any existing timers associated with rate limitings / content download window if type(m.stateMachine.contentDownloadWindowStartTimer) = "roTimer" then m.stateMachine.contentDownloadWindowStartTimer.Stop() end if if type(m.stateMachine.contentDownloadWindowEndTimer) = "roTimer" then m.stateMachine.contentDownloadWindowEndTimer.Stop() end if if newSettings.contentDownloadsRestricted then m.bsp.diagnostics.PrintDebug("### Content downloads are restricted to the time from " + stri(newSettings.contentDownloadRangeStart) + " for " + stri(newSettings.contentDownloadRangeLength) + " minutes.") else m.bsp.diagnostics.PrintDebug("### Content downloads are unrestricted") end if end sub Sub ConfigureNetwork(networkingParameters as object, registryNetworkingParameters as object, registryKeySuffix$ as string) nc = CreateObject("roNetworkConfiguration", networkingParameters.networkConfigurationIndex%) if type(nc) = "roNetworkConfiguration" then if networkingParameters.useDHCP$ = "no" then nc.SetIP4Address(networkingParameters.staticIPAddress$) nc.SetIP4Netmask(networkingParameters.subnetMask$) nc.SetIP4Gateway(networkingParameters.gateway$) dnsServers = [] if networkingParameters.dns1$ <> "" then dnsServers.push(networkingParameters.dns1$) end if if networkingParameters.dns2$ <> "" then dnsServers.push(networkingParameters.dns2$) end if if networkingParameters.dns3$ <> "" then dnsServers.push(networkingParameters.dns3$) end if if dnsServers.Count() > 0 then ok = nc.SetDNSServers([]) ok = nc.SetDNSServers(dnsServers) end if else nc.SetDHCP() end if nc.SetRoutingMetric(networkingParameters.networkConnectionPriority) if networkingParameters.useWireless then nc.SetWiFiESSID(networkingParameters.ssid$) nc.SetObfuscatedWifiPassphrase(networkingParameters.passphrase$) end if newSettings = GetPendingSettings() timeServer$ = newSettings.timeServer nc.SetTimeServer(networkingParameters.timeServer$) success = nc.Apply() nc = invalid if not success then m.bsp.diagnostics.PrintDebug("### roNetworkConfiguration.Apply failure.") else ' save parameters to the registry networkConnectionPriorityFromRegistry$ = registryNetworkingParameters.networkConnectionPriority$ if int(val(networkConnectionPriorityFromRegistry$)) <> networkingParameters.networkConnectionPriority then networkConnectionPriority$ = StripLeadingSpaces(stri(networkingParameters.networkConnectionPriority)) WriteRegistrySetting("ncp" + registryKeySuffix$, networkConnectionPriority$) registryNetworkingParameters.networkConnectionPriority$ = networkConnectionPriority$ end if if networkingParameters.useDHCP$ = "no" then if registryNetworkingParameters.useDHCP$ <> "no" then WriteRegistrySetting("dhcp" + registryKeySuffix$, "no") registryNetworkingParameters.useDHCP$ = "no" end if staticIPAddressFromRegistry$ = registryNetworkingParameters.staticIPAddress$ subnetMaskFromRegistry$ = registryNetworkingParameters.subnetMask$ gatewayFromRegistry$ = registryNetworkingParameters.gateway$ dns1FromRegistry$ = registryNetworkingParameters.dns1$ dns2FromRegistry$ = registryNetworkingParameters.dns2$ dns3FromRegistry$ = registryNetworkingParameters.dns3$ if staticIPAddressFromRegistry$ <> networkingParameters.staticIPAddress$ then WriteRegistrySetting("sip" + registryKeySuffix$, networkingParameters.staticIPAddress$) registryNetworkingParameters.staticIPAddress$ = networkingParameters.staticIPAddress$ end if if subnetMaskFromRegistry$ <> networkingParameters.subnetMask$ then WriteRegistrySetting("sm" + registryKeySuffix$, networkingParameters.subnetMask$) registryNetworkingParameters.subnetMask$ = networkingParameters.subnetMask$ end if if gatewayFromRegistry$ <> networkingParameters.gateway$ then WriteRegistrySetting("gw" + registryKeySuffix$, networkingParameters.gateway$) registryNetworkingParameters.gateway$ = networkingParameters.gateway$ end if if dns1FromRegistry$ <> networkingParameters.dns1$ then WriteRegistrySetting("d1" + registryKeySuffix$, networkingParameters.dns1$) registryNetworkingParameters.dns1$ = networkingParameters.dns1$ end if if dns2FromRegistry$ <> networkingParameters.dns2$ then WriteRegistrySetting("d2" + registryKeySuffix$, networkingParameters.dns2$) registryNetworkingParameters.dns2$ = networkingParameters.dns2$ end if if dns3FromRegistry$ <> networkingParameters.dns3$ then WriteRegistrySetting("d3" + registryKeySuffix$, networkingParameters.dns3$) registryNetworkingParameters.dns3$ = networkingParameters.dns3$ end if else if registryNetworkingParameters.useDHCP$ <> "yes" then WriteRegistrySetting("dhcp" + registryKeySuffix$, "yes") registryNetworkingParameters.useDHCP$ = "yes" end if end if end if else m.bsp.diagnostics.PrintDebug("Unable to create roNetworkConfiguration - index = " + stri(networkConfigurationIndex%)) end if end sub Function GetBsnce() as boolean supervisorRegistrySection = CreateObject("roRegistrySection", "!supervisor.brightsignnetwork.com") if type(supervisorRegistrySection) <> "roRegistrySection" then print "Error: Unable to create supervisorRegistrySection roRegistrySection": stop end if bsnce = supervisorRegistrySection.Read("bsnce") if bsnce = "false" then return false else return true endif end function Function GetAndSaveDwsParams(settings as object, registrySettings as object) dwsEnabled = settings.dwsEnabled bsnce = GetBsnce() if not (dwsEnabled or bsnce) then enableDws = false else enableDws = true endif dwsAA = {} if enableDws then dwsPassword$ = settings.dwsPassword dwsAA["port"] = "80" dwsAA["password"] = dwsPassword$ else dwsAA["port"] = 0 endif nc = CreateObject("roNetworkConfiguration", 0) if type(nc) <> "roNetworkConfiguration" then nc = CreateObject("roNetworkConfiguration", 1) end if if type(nc) = "roNetworkConfiguration" then dwsRebootRequired = nc.SetupDWS(dwsAA) nc = invalid return dwsRebootRequired end if return false end function 'end region