Posted to tcl by Napier at Thu Oct 16 23:27:33 GMT 2014view raw
- # --------------------------------------------
- # Denon HEOS Integration
- # Integration with the Denon HEOS Product Line
- #
- # Author: Braden Napier
- # --------------------------------------------
- ##---------- Include URC SDK Files: ----------##
- source [file dirname [info script]]/URC_SDK.tcl
- # ------------- SYSTEM SETTINGS -------------
- # System Logging: 1 = ON, 0 = OFF (Turn OFF Before Final Deployment)
- # Showroom Variable handles the "Demo / Showroom" Mode of the Module
- # --------------------------------------------
- enableLOG 0
- # --------------------------------------------
- set_visible "room_2_name" 0
- set_visible "nowplaying_2_indicator" 0
- set_visible "nowplaying_2_songinfo" 0
- set_visible "nowplaying_2_artwork" 0
- set_visible "player2" 0
- set_visible "nowplaying_3_indicator" 0
- set_visible "room_3_name" 0
- set_visible "nowplaying_3_songinfo" 0
- set_visible "nowplaying_3_artwork" 0
- set_visible "player3" 0
- # TODO: ::Widgets::Rooms::UpdateList: Get Name of Room based on ID, if Unknown Get or Display Provided Value
- # TODO: ::Events::State Need to set the variable "i" to be the index of the pid if required.
- #### System Variable Value Examples
- ## Active Players Without Group:
- # -1367676363 {name Kitchen pid -1367676363 model {HEOS 3} version 1.239.93 type player} 479981077 {name {Office Desk} pid 479981077 model {HEOS 3} version 1.239.93 type player}
- ## Active Players With Group:
- # -1367676363 {name {Kitchen + Office Desk} gid -1367676363 players {{name {Office Desk} pid 479981077 role member} {name Kitchen pid -1367676363 role leader}} type group}
- ## Players:
- # {name {Office Desk} pid 479981077 gid -1367676363 model {HEOS 3} version 1.239.93} {name Kitchen pid -1367676363 gid -1367676363 model {HEOS 3} version 1.239.93}
- ## Player State:
- # 479981077 {pid 479981077 state stop nowplaying {type station song Shuffle station {} artist {} image_url {} mid 62572596943575335 qid 1 gid -1367676363}}
- # -1367676363 {pid -1367676363 state stop nowplaying {type station song Shuffle station {} artist {} image_url {} mid 62572596943575335 qid 1 gid -1367676363}}
- ####
- #### pid Index
- ## Variable Name:
- # pid Index is the currently showing listing of players in the main menu. This is to provide what players are currently showing in the
- # Rooms Menu. The value of the index will provide either the pid or gid dependent on if the resulting option is a group or not.
- # -> 1 {pid $pid} 2 {gid $gid}
- ####
- ## Variable Name: $::Module::totalPlayers
- # This variable reflects the total number of players in the account that are active. Should grouping occur, the grouped players will
- # be represented as a single player.
- ####
- ## Variable Name: $::Module::selectedRoom
- # This Variable reflects the index of the currently selected room/player/group.
- ## Variable Name: $::Module::selectedPage
- # This Variable reflects the page number of the currently selected room/player/group.
- ## Variable Name: $::Module::selectedIndex
- # This Variable reflects the overall index of the selected room/player/group.
- ## Variable Name: $::Module::currentPage
- # This Variable reflects the current page being viewed by the end-user.
- ####
- #### System Variables & Descriptions:
- ## $::Widgets::NowPlaying::PlayPauseState
- # Provides the Current State of the Play/Pause Button on the Now Playing Page (0 = Pause Icon, 1 = Play Icon)
- ## $::Widgets::DefaultArtwork
- # Provides path to the default Artwork to use when Album Art is not available.
- ## $::Widgets::Rooms::CurrentlyDisplayed
- # Nested Dictionary with the keys being the index of the list and the value being either the pid or gid
- ####
- namespace eval Module {
- variable activePage 0
- variable totalActivePlayers -1
- variable selectedRoom 1
- variable selectedPage 1
- variable selectedIndex 1
- variable currentPage 1
- variable activeIndex [dict create 1 479981077 2 -1367676363]
- variable isConnected 0
- variable wasConnectedPreviously 0
- variable Players ""
- variable CurrentRoom ""
- variable ActivePlayers ""
- variable playerStatus ""
- variable sourceList ""
- variable moduleHeartbeatTimer ""
- variable isAuthorized 0
- variable isAuthorizing 0
- variable currentKeyboard ""
- variable userCredentials
- variable pid "479981077"
- variable selectedpid ""
- variable nowPlayingTime ""
- variable timedSong ""
- variable everyIndicator ""
- variable timedSongDuration ""
- variable timedSongElapsed ""
- variable prevPlayerStatus ""
- variable selectedpidMuted 0
- variable playpauseDelay 0
- }
- proc changePage {p} {display_page "$p"; set ::Module::activePage "$p"}
- namespace eval Clients {
- # The Clients Namespace provides variables that are specific to the interface running the module.
- # These will be created at startup with values based upon the URC Interface launching the script.
- # $::Clients::totalPages will be updated based on how many players are currently available. This
- # is evaluated each time players are received from the System Script.
- variable totalPages 1
- variable playersPerPage 1
- switch -- $::varIfClientType {
- TRC-1080 {
- set ::Clients::playersPerPage 3
- }
- TRC-1280 {}
- TKP-7000 {
- set ::Clients::playersPerPage 5
- }
- TKP-2000 {}
- MRX-20 {
- set ::Clients::playersPerPage 5
- }
- }
- }
- # ------------- Session Control -------------
- # Handles Control of Various Sessions
- # --------------------------------------------
- LOG "--- SESSION STARTED ---"
- LOG "Denon HEOS Session Started"
- LOG "Module Connection from $varIfClientType"
- LOG "Resetting Module Global Variables"
- LOG "-----------------------"
- proc onClientDisconnect {} {
- LOG "--- SESSION CLOSED ---"
- LOG "Module Name: Denon HEOS Module, Client: $::varIfClientType"
- LOG "-----------------------"
- # device_proc "::URC::moduleCallback -session closed -client $::varIfClientType"
- }
- namespace eval Events {
- # Events from the Denon HEOS are Called by the System Script here. These procedures
- # will generally update the UI's that are currently open.
- # ::Events::State -state play -pid $pid
- # ::Events::MediaProgess
- # ::Events::NewMedia -pid $pid -song "Come Together" -artist "The Beatles" -album "Abbey Road" -image $url -service "Pandora"
- proc GroupingChanged {args} {
- # Called by System Script when Grouping is Changed
- }
- proc PlayersChanged {args} {
- # Called by System Script when the Players have changed
- systemCall getPlayers
- }
- proc ConnectionState {state} {
- # Sets the isConnected Variable to the received Connection State.
- # 0 = Disconnected , 1 = Online
- # - This will call the Callback to Update the UI Appropriately
- set ::Module::isConnected $state
- }
- proc PlayersIndex {tempDict} {
- # DEPRECIATED ???
- # Called by the System Script when a players "Index" has changed. This could be due to
- # reordering of a list, regrouping, etc.
- # $tempDict will hold the new dictionary value.
- set ::Module::activeIndex $tempDict
- }
- proc State {args} {
- # Updates the Play State of the Players
- # Arguments are:
- # -state : <play, stop, pause>
- # -pid : The player id of the player that was changed
- # -gid : The group id of the player if a group is present
- if {[dict exists $args -state]} {set state [dict get $args -state]} else {LOG "No State Detected in Events::State Procedure Event"; return}
- if {[dict exists $args -pid]} {set id [dict get $args -pid]} else {LOG "No PID Provided for Player State Event"; set id 0}
- if {[dict exists $args -gid]} {set id [dict get $args -gid]} else {LOG "No GID Provided for Player State Event"}
- if {$id == "0"} {LOG "ERROR: No Player or Group ID Provided in ::Events::State"; return}
- # TODO: ::Events::State Need to set the variable "i" to be the index of the pid if required.
- # Note: If pid is not currently being shown must save to info dict
- set index [::Module::getIndexbyID $id]
- set pageOfPlayer [expr {floor($index / $::Clients::playersPerPage) + 1}]
- set i 0
- if {$pageOfPlayer == $::Module::currentPage} {
- # Checks to see if the Player is currently displayed on the "Rooms" page based on the "$::Module::currentPage" variable.
- # If it is visible, it will provide the player # to update with the new Play State Value
- set i [expr {($index % $::Clients::playersPerPage) + 1}]
- }
- switch -- $state {
- "play" {
- if {$i != 0} {catch {::set_text "nowplaying_${i}_indicator" "b"}}
- # Set the Play Pause Icon on Now Playing Page to Pause Icon
- if {$::Module::selectedpid == $id} {::Widgets::NowPlaying::PlayPause -state pause}
- }
- "pause" {
- if {$i != 0} {catch {set_text "nowplaying_${i}_indicator" "i"}}
- # Set the Play Pause Icon on Now Playing Page to Play Icon
- if {$::Module::selectedpid == $id} {::Widgets::NowPlaying::PlayPause -state play}
- }
- "stop" {
- if {$i != 0} {catch {set_text "nowplaying_${i}_indicator" "j"}}
- # Set the Play Pause Icon on Now Playing Page to Play Icon
- if {$::Module::selectedpid == $id} {::Widgets::NowPlaying::PlayPause -state play}
- }
- default {LOG "State Switch in State Event Failure"}
- }
- }
- proc MediaProgress {ARGS} {
- # Updates the Media Progress details of the HEOS Player
- }
- proc NewMedia {args} {
- # Called by the System Script to update media metadata for a player or group
- # Arguments are:
- # -pid : The player id that is being updated.
- # -gid : The group id that is being updated.
- # -song : The name of the Song that is now playing.
- # -artist : The name of the Artist that is now playing.
- # -album : The name of the Album that is now playing.
- # -image : An URL to the image that is now playing. If not specified default will be used.
- # -service : The name of the service that is now playing.
- LOG "Executing ::Events::NewMedia with args\n$args"
- if {[dict exists $args -song]} {set song [dict get $args -song]} else {LOG "No Song Name Provided"; set song ""}
- if {[dict exists $args -artist]} {set artist [dict get $args -artst]} else {LOG "No Artist Provided"; set artist ""}
- if {[dict exists $args -album]} {set artist [dict get $args -album]} else {LOG "No Album Provided"; set album ""}
- if {[dict exists $args -image]} {set image [dict get $args -image]} else {LOG "No Artwork Provided"; set image 0}
- if {[dict exists $args -service]} {set service [dict get $args -service]} else {LOG "No Service Provided"; set service ""}
- if {[dict exists $args -pid]} {set id [dict get $args -pid]; set playerType "Player"} else {LOG "No PID Provided, is it a Group?"; set playerType 0}
- if {[dict exists $args -gid]} {set id [dict get $args -gid]; set playerType "Group"} else {LOG "No GID Provided"}
- if {$playerType == "0"} {LOG "ERROR: Player or Group ID Not Provided"; return}
- LOG "New Media for $playerType ${id}:\nService: $service\nSong $song\nArtist: $artist\nAlbum: $album\nImage URL: $image"
- if {$id == $::Module::selectedpid} {
- # Currently Playing Player/Group - Update "Now Playing" Page
- ::Widgets::NowPlaying::Metadata -song $song -artist $artist -album $album -service $service
- ::Widgets::NowPlaying::Artwork $image
- }
- set pageOfPlayer [::Module::whatPage $id]
- if {$::Module::currentPage == $pageOfPlayer} {
- set position [::Module::positionOnPage $id]
- ::Widgets::Rooms::UpdateList -i $position -song $song -artist $artist -image $image -id $id
- }
- # Called after updating the UI (for speed) to get the entire Player Status Dictionary from the System Script
- systemCall getPlayerStatus
- }
- proc UpdateAll {} {
- # This procedure is called to update all UI Elements based on the current
- # status from the System Script. It will query the current data from the System
- systemCall getAuthorized
- ::Widgets::NowPlaying::Refresh
- ::Widgets::Rooms::UpdateListsPage
- }
- }
- namespace eval Update {
- proc ActivePlayers {} {
- # Updates the Active Players Variables and all associated values that may be affected by a change
- # in the value.
- systemCall getActivePlayers
- catch {
- set pids [dict keys $::Module::ActivePlayers]
- set ::Module::totalActivePlayers [llength $pids]
- set ::Clients::totalPages [expr {ceil(double($::Module::totalActivePlayers) / $::Clients::playersPerPage)}]
- } { LOG "ERROR: While Updating Active Players in ::Update::ActivePlayers" }
- return
- }
- proc playerStatus {} {
- # Updates the Player Status Variable which includes all players now playing information & states.
- systemCall getPlayerStatus
- }
- }
- namespace eval Widgets {
- variable DefaultArtwork "C:\tkp7-art-missing.png"
- namespace eval Rooms {
- # $::Widgets::Rooms::CurrentlyDisplayed is a Dictionary with the keys being the index of the list and the value being either the pid or gid
- # -> 1 {pid $pid} 2 {gid $gid} 3 {pid $pid} 4 {pid $pid}
- variable CurrentlyDisplayed [dict create]
- proc Set {args} {
- # Sets/Refreshes the Rooms Listing in the "Rooms" Page.
- }
- proc UpdateList {args} {
- # Updates an Item in the Rooms Listing of the "Rooms" Page
- # Arguments Are:
- # -i : The list item number to update in the Rooms Listing
- # -song : The text to play in the "Song" field of the provided index
- # -artist : The text to put in the "Artist" field of the provided index
- # -image : The artwork URL to display, 0 or undefined for default artwork
- # -id : The pid or gid of the player, this is used to get the name. A name can also be provided instead.
- if {[dict exists $args -i]} {set i [dict get $args -index]} else {LOG "ERROR: No Index Provided in ::Widgets::Rooms::UpdateList"; return}
- if {[dict exists $args -song]} {set song [dict get $args -song]} else {LOG "No Song Name Provided"; set song ""}
- if {[dict exists $args -artist]} {set artist [dict get $args -artist]} else {LOG "No Artist Provided"; set artist ""}
- if {[dict exists $args -image]} {set image [dict get $args -image]} else {set image 0}
- if {[dict exists $args -id]} {set id [dict get $args -id]} else {LOG "ERROR: No ID Provided in ::Widgets::Rooms::UpdateList"; set id 0}
- if {$artist == "" && $song == ""} {set songinfo "Refreshing..."} else {set songinfo "${artist} - ${song}"}
- if {$image == "0" || $image == ""} {set image $::Widgets::DefaultArtwork}
- if {$i > $::Clients::playersPerPage} {LOG "ERROR: Attempted to Update a List Item Number Not Supported by the Current Client"; return}
- LOG "Updating Rooms Listing\nIitem: ${i}\nArtist: ${artist}\nSong: ${song}"
- # TODO: ::Widgets::Rooms::UpdateList: Get Name of Room based on ID, if Unknown Get or Display Provided Value
- if {![dict exists $::Module::ActivePlayers $id]} {LOG "ERROR: Provided ID in ::Widgets::Rooms::UpdateList does not exist"; return}
- set name [dict get $::Module::ActivePlayers $id name]
- catch {
- ::set_text "room_${i}_name" $name
- ::set_text "nowplaying_${i}_songinfo" $songinfo
- ::set_image "nowplaying_${i}_artwork" $image
- }
- }
- proc UpdateListsPage {} {
- # Called whenever the "$::Module::currentPage" variable is changed and will update the
- # Rooms/Players/Groups list to reflect the current page.
- # Note: This can also be used to refresh the lists metadata (however this should be done automatically)
- ::Update::ActivePlayers
- ::Update::playerStatus
- if {$::Module::currentPage > $::Clients::totalPages} {
- # Attempting to Update a Page Which Doesn't Exist, will instead jump to the last available page.
- LOG "Attempted to jump to a Rooms Page that doesn't exist, jumping to the last available page"
- set ::Module::currentPage $::Clients::totalPages
- return
- } else {
- set pids [dict keys $::Module::ActivePlayers]
- set i 1
- while {$i <= $::Clients::playersPerPage} {
- if {$i > $::Module::totalActivePlayers} {break}
- set index [expr {(($::Module::currentPage * $::Clients::playersPerPage) + ($i - 1)) - $::Clients::playersPerPage}]
- set id [::Module::getIDbyIndex $index]
- # TODO : COMPLETE List Updating
- ::Widgets::UpdateList \
- -i $i \
- -song [dict get $::Module::playerStatus $id nowplaying song] \
- -artist [dict get $::Module::playerStatus $id nowplaying artist] \
- -image [dict get $::Module::playerStatus $id nowplaying image_url] \
- -id $id
- incr i
- }
- LOG "Done Parsing Players for Rooms Listing"
- }
- }
- proc UpdateState {ARGS} {
- # Updates the Play State of the specified player.
- }
- proc Scroll {args} {
- ## Use to Scroll the Rooms Listing Up or Down based on the arguments provided
- ## Arguments:
- # -direction <up, down> - Used to Navigate the Rooms List Up or Down
- # -reset <1, 0> - Boolean if Reset should be done (scroll to top of the list)
- if {[dict exists $args -reset]} {set reset [dict get $args -reset]} else {set reset 0}
- if {[dict exists $args -direction]} {
- set direction [dict get $args -direction]
- } elseif {!$reset} {
- LOG "ERROR: No Direction and Not Being Reset in ::Widgets::Rooms::Scroll"
- return
- }
- if {$reset} {
- set ::Module::currentPage 1
- ::Widgets::Rooms::UpdateListsPage
- } else {
- switch -- $direction {
- up {
- if {$::Module::currentPage == $::Clients::totalPages}
- }
- down {
- # Scroll the Rooms List Down
- }
- default {LOG "ERROR: Direction Doesn't Match a valid option in ::Widgets::Rooms::Scroll"}
- }
- }
- }
- }
- namespace eval NowPlaying {
- variable PlayPauseState -1
- proc Refresh {} {
- # This Procedure is used to Refresh the "Now Playing" listed according to the currently selected player/groups now playing data.
- ::Update::PlayerStatus
- set id $::Module::selectedpid
- catch {
- set name [dict get $::Module::ActivePlayers $id name]
- ::set_text "current_room_rooms" $name
- ::set_text "current_room_nowplaying" $name
- ::set_text "current_room_sources" $name
- ::set_text "current_room_pandora" $name
- } {LOG "ERROR: While Setting Text of Selected Room Name in ::Widgets::NowPlaying::Refresh"}
- if {[dict exists $::Module::playerStatus $id nowplaying song]} {set song [dict get $::Module::playerStatus $id nowplaying song]} else {set song ""}
- if {[dict exists $::Module::playerStatus $id nowplaying artist]} {set artist [dict get $::Module::playerStatus $id nowplaying artist]} else {set artist ""}
- if {[dict exists $::Module::playerStatus $id nowplaying album]} {set album [dict get $::Module::playerStatus $id nowplaying album]} else {set album ""}
- if {[dict exists $::Module::playerStatus $id nowplaying image_url]} {set image [dict get $::Module::playerStatus $id nowplaying image_url]} else {set image 0}
- if {[dict exists $::Module::playerStatus $id nowplaying service]} {set service [dict get $::Module::playerStatus $id nowplaying service]} else {set service ""}
- ::Widgets::NowPlaying::Metadata -song $song -artist $artist -album $album -service $service
- ::Widgets::NowPlaying::Artwork $image
- }
- proc SelectedRoom {id} {
- # This Procedure should be called whenever the "Selected Room/Player/Group" has changed. It will take
- # check to make sure the id is a valid player, update the selected player variable, then call to refresh
- # the UI as needed.
- if {![dict exists $::Module::playerStatus $id]} {LOG "ERROR: Provided ID Doesn't Exist in ::Widgets::NowPlaying::SelectedRoom"; return}
- set ::Module::selectedpid $id
- # Call the Now Playing Refresh Procedure to update the Now Playing page for the newly selected Player/Group
- # will also update the "Current Room" titles throughout the UI.
- ::Widgets::NowPlaying::Refresh
- }
- proc Metadata {args} {
- # Updates the Metadata Text for the Now Playing Song on the Now Playing Page
- # Arguments Are:
- # -song : Name of the Song to put into the Song Info Field
- # -artist : Name of the Artist to put into the Artist Info Field
- # -album : Name of the Album to put into the Album Info Field
- # -service : Name of the Service Currently Being Used
- if {[dict exists $args -song]} {set song [dict get $args -song]} else {set song ""}
- if {[dict exists $args -artist]} {set artist [dict get $args -artist]} else {set artist ""}
- if {[dict exists $args -album]} {set album [dict get $args -album]} else {set album ""}
- if {[dict exists $args -service]} {set service [dict get $args -service]} else {set service ""}
- catch {
- ::set_text "nowplaying_artist" $artist
- ::set_text "nowplaying_song" $song
- ::set_text "nowplaying_album" $album
- } {LOG "ERROR: While Setting Metadata Text as Specified on Now Playing Page in ::Widgets::NowPlaying::Metadata"}
- }
- proc Artwork {url} {
- # Sets the Artwork on the Now Playing Page to the Specified URL
- # Example Call:
- # ::Widgets::NowPlaying::Artwork http://www.website.com/image.png
- if {$url == "0" || $url == ""} {set url $::Widgets::DefaultArtwork}
- catch {::set_image "nowplaying_artwork" $url}
- }
- proc PlayPause {args} {
- # Controls the PlayPause Multi-State Button available in the "Now Playing" Page.
- # Stores the current state in $::Widgets::NowPlaying::PlayPauseState
- # 0 = Pause Icon, 1 = Play Icon
- # Arguments:
- # -state : Provide the State the Button should change to. 0 = Pause Icon, 1 = Play Icon
- # Example Calls:
- # ::Widgets::NowPlaying::PlayPause -state <0, pause, stop>
- # ::Widgets::NowPlaying::PlayPause -state <1, play>
- if {[dict exists $args -state]} {set state [dict get $args -state]} else {return -1}
- switch -- $state {
- 1 -
- play {set state 1}
- 0 -
- pause -
- stop {set state 0}
- default {LOG "Invalid Switch in PlayPause Widget"}
- }
- LOG "Setting "
- catch {::set_state "btn_playpause" $state} {LOG "Set PlayPause Button State Failed"; return -1}
- set ::Widgets::NowPlaying::PlayPauseState $state
- }
- }
- }
- namespace eval Module {
- proc Startup {args} {
- # Called when the Module is First Launched - this procedure will gather the
- # data from the System Script and populate the UI properly. Once it has loaded
- # the module will jump to the rooms menu.
- # Load the Please Wait Page while the Module Loads Details from the System Script
- changePage "Please Wait"
- # A trace is placed on the "$::Module::isConnected" variable to call the Callbacks::ConnectionState
- # procedure when the variables state changes. If not connected, it will jump to the not connected
- # page until a connection is successful. If connected, it will jump to Please Wait, load all the
- # information from the System Script, then jump to the Rooms Page.
- ::Tools::setTrace ::Module::isConnected ::Callbacks::ConnectionState
- ::Tools::setTrace ::Module::currentPage ::Callbacks::currentPage
- set ::Module::isConnected [systemCall checkConnection]
- }
- proc getIDbyIndex {index} {
- # This Procedure will take an Index Value and respond with the pid or gid.
- set selectedPlayer [lindex [dict keys $::Module::ActivePlayers] $index]
- if {$selectedPlayer == ""} {LOG "ERROR: Illegal Index Provided in ::Module::getIDbyIndex"; return -1}
- return $selectedPlayer
- }
- proc getIndexbyID {id} {
- # This procedure will return the index of the provided Room/Player/Group ID.
- # Note: Index starts at 0 not 1
- set index [lsearch [dict keys $::Module::ActivePlayers] $id]
- if {$index == -1} {LOG "ERROR: ID Provided is not a currently Active Player, it may be grouped and not acting as the group leader?"; return -1}
- return $index
- }
- proc whatPage {id} {
- # Checks what page a given ID should be on. Returns -1 if the ID is not found in the Active Players listing.
- if {![dict exists $::Module::ActivePlayers $id]} {LOG "ERROR: Provided ID in ::Module::whatPage is not an Active Player"; return -1}
- set index [lsearch [dict keys $::Module::ActivePlayers] $id]
- set pageOfPlayer [expr {floor($index / $::Clients::playersPerPage) + 1}]
- return $pageOfPlayer
- }
- proc positionOnPage {id} {
- # Checks what position on its page the provided Player ID should be. For example, if the player is on page but list item 1, it will
- # return "1" - to get the page use ::Module::whatPage
- set index [lsearch [dict keys $::Module::ActivePlayers] $id]
- set position [expr {($index % $::Clients::playersPerPage) + 1}]
- return $position
- }
- }
- namespace eval Callbacks {
- proc ConnectionState {oldValue callbackName varName args} {
- # This is called whenever the $::Module::isConnected variable is changed using the Trace Functions that are called
- # at the module startup.
- LOG "CALLBACK: Connection State"
- upvar 1 $varName newValue
- # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
- uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
- uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
- if {$oldValue != $newValue} {
- if {$newValue == "0"} {
- LOG "Connection: Lost Connection to the HEOS System"
- changePage "Not Connected"
- } elseif {$newValue == "1"} {
- LOG "Connection: Connected or Re-Connected to the HEOS System"
- changePage "Please Wait"
- ::Events::UpdateAll
- changePage "Rooms"
- }
- } else {LOG "Connection: Variable was set but remains unchanged - State is $newValue"}
- }
- proc currentPage {oldValue callbackName varName args} {
- # This is called whenever the $::Module::currentPage variable is changed using the Trace Functions that are called
- # at the module startup.
- LOG "CALLBACK: Current Rooms Page"
- upvar 1 $varName newValue
- # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
- uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
- uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
- # Update the Rooms/Players/Groups Listing to reflect the new pages values:
- ::Widgets::Rooms::UpdateListsPage
- }
- }
- proc moduleStartup {} {
- set ::Module::isConnected [systemCall checkConnection]
- if {$::Module::isConnected} {
- if {!$::Module::wasConnectedPreviously} {
- ::Tools::setTrace ::Module::ActivePlayers ::UpdateUI::Players
- ::Tools::setTrace ::Module::playerStatus ::UpdateUI::playerStatus
- ::Tools::setTrace ::Module::isAuthorized ::UpdateUI::getCredentials
- ::Tools::setTrace ::Module::CurrentRoom ::UpdateUI::CurrentRoom
- redirectHardButtons
- }
- moduleHeartbeat
- changePage "Rooms"
- catch {::every cancel moduleHeartbeat}
- catch {::every cancel moduleStartup}
- ::every 5000 moduleHeartbeat
- set ::Module::wasConnectedPreviously 1
- # device_proc "::URC::moduleCallback -session opened -client $::varIfClientType"
- } else {
- LOG "Can't Connect! Will try again in 5 Seconds"
- changePage "NotConnected"
- catch {::every cancel moduleStartup}
- catch {::every cancel moduleHeartbeat}
- ::every 5000 moduleStartup
- }
- }
- # --------------------------------------------
- # ------------- Hard/Soft Button Events -------------
- # Handles Hard Button Presses while in the Module
- # -----------------------------------------------
- proc hardbutton_release {id} {
- switch -- $id {
- "$::HKEY_LEFT" {LOG "Left Button Event"}
- "$::HKEY_RIGHT" {LOG "Right Button Event"}
- "$::HKEY_UP" {LOG "Up Button Event"}
- "$::HKEY_DOWN" {LOG "Down Button Event"}
- "$::HKEY_SELECT" {LOG "Select Button Event"}
- default {LOG "Unknown Button Release Received"}
- }
- }
- proc redirectHardButtons {} {
- # redirect_hardbutton $::HKEY_PAGEPREV 1
- # redirect_hardbutton $::HKEY_PAGENEXT 1
- redirect_hardbutton $::HKEY_UP 1
- redirect_hardbutton $::HKEY_DOWN 1
- redirect_hardbutton $::HKEY_LEFT 1
- redirect_hardbutton $::HKEY_RIGHT 1
- redirect_hardbutton $::HKEY_SELECT 1
- # redirect_hardbutton $::HKEY_CHUP 1
- # redirect_hardbutton $::HKEY_CHDN 1
- }
- proc btn_handler {event id} {
- # Events: 1 CLICK, 2 PRESS, 3 RELEASE
- if {$event == 3} {
- switch -regexp -- $id {
- "btn_1" {
- # Selected Player 1
- set pid [lindex [dict keys $::Module::ActivePlayers] 0]
- if {$pid == ""} {return}
- set ::Module::CurrentRoom [dict get $::Module::ActivePlayers $pid name]
- set ::Module::selectedRoom 1
- set ::Module::selectedpid $pid
- systemCall getActivePlayers
- systemCall getPlayerStatus
- }
- "btn_2" {
- # Selected Player 2
- set pid [lindex [dict keys $::Module::ActivePlayers] 1]
- if {$pid == ""} {return}
- set ::Module::CurrentRoom [dict get $::Module::ActivePlayers $pid name]
- set ::Module::selectedRoom 2
- set ::Module::selectedpid $pid
- systemCall getActivePlayers
- systemCall getPlayerStatus
- }
- "btn_3" {
- # Selected Player 3
- set pid [lindex [dict keys $::Module::ActivePlayers] 2]
- if {$pid == ""} {return}
- set ::Module::CurrentRoom [dict get $::Module::ActivePlayers $pid name]
- set ::Module::selectedRoom 3
- set ::Module::selectedpid $pid
- systemCall getActivePlayers
- systemCall getPlayerStatus
- }
- "^player_.*" {
- set pressed [::Tools::extract_numbers $id]
- set index [expr {(($::Module::currentPage * $::Clients::playersPerPage) + ($pressed - 1)) - $::Clients::playersPerPage}]
- set pid [::Module::getIDbyIndex $index]
- ::Widgets::NowPlaying::SelectedRoom $pid
- }
- "btn_nowplaying" {changePage "NowPlaying"}
- "btn_volup" {
- if {[dict exists $::Module::ActivePlayers $::Module::selectedpid gid]} {
- device_proc "::Control::group_volume up [dict get $::Module::ActivePlayers $::Module::selectedpid gid]"
- } else {
- device_proc "::Control::volume up $::Module::selectedpid"
- }
- }
- "btn_voldown" {
- if {[dict exists $::Module::ActivePlayers $::Module::selectedpid gid]} {
- device_proc "::Control::group_volume down [dict get $::Module::ActivePlayers $::Module::selectedpid gid]"
- } else {
- device_proc "::Control::volume down $::Module::selectedpid"
- }
- }
- "btn_mute" {
- if {$::Module::selectedpidMuted} {
- if {[dict exists $::Module::ActivePlayers $::Module::selectedpid gid]} {
- device_proc "::Control::group_volume muteoff [dict get $::Module::ActivePlayers $::Module::selectedpid gid]"
- } else {
- device_proc "::Control::volume muteoff $::Module::selectedpid"
- }
- set_text "btn_mute" "pq" -1 0
- set ::Module::selectedpidMuted 0
- } else {
- if {[dict exists $::Module::ActivePlayers $::Module::selectedpid gid]} {
- device_proc "::Control::group_volume muteon [dict get $::Module::ActivePlayers $::Module::selectedpid gid]"
- } else {
- device_proc "::Control::volume muteon $::Module::selectedpid"
- }
- set_text "btn_mute" "p" -1 0
- set ::Module::selectedpidMuted 1
- }
- }
- "btn_skip_forward" {device_proc "::Control::Skip next $::Module::selectedpid"; after 1000 {systemCall getPlayerStatus}}
- "btn_skip_back" {device_proc "::Control::Skip previous $::Module::selectedpid"; ; after 1000 {systemCall getPlayerStatus}}
- "btn_home" -
- "btn_rooms" {changePage "Rooms"}
- ".*goback_sources" -
- "btn_music_source" {changePage "MusicSources"}
- "btn_src_goback.*" {changePage "Rooms"}
- "btn_src_pandora" {changePage "Pandora"}
- "btn_src_napster" {changePage "Napster"}
- "btn_src_spotify" {changePage "Spotify"}
- "btn_src_tunein" {changePage "tunein"}
- "btn_src_rhapsody" {changePage "Rhapsody"}
- ".*_beta" {catch {system_notification "Coming Soon" "This Feature is Coming Soon!" "Continue"}}
- "btn_ExitMain" {simulate_hardbutton $::HKEY_MAIN 1; simulate_hardbutton $::HKEY_MAIN 0}
- default {LOG "Unknown Button Release Event"}
- }
- }
- }
- proc multibtn_handler {event id} {
- if {$event == 3} {
- switch -regexp -- $id {
- "btn_playpause" {
- switch -- [dict get $::Module::playerStatus $::Module::selectedpid state] {
- "play" {
- device_proc "::Control::setPlayState pause $::Module::selectedpid"
- set ::Module::playpauseDelay 1
- set_state "btn_playpause" 1
- after 5000 {set ::Module::playpauseDelay 0}
- dict set ::Module::playerStatus $::Module::selectedpid state pause
- }
- "pause" {
- device_proc "::Control::setPlayState play $::Module::selectedpid"
- set ::Module::playpauseDelay 1
- set_state "btn_playpause" 0
- after 5000 {set ::Module::playpauseDelay 0}
- dict set ::Module::playerStatus $::Module::selectedpid state play
- }
- "stop" {
- set ::Module::playpauseDelay 1
- device_proc "::Control::setPlayState play $::Module::selectedpid"
- set_state "btn_playpause" 0
- after 5000 {set ::Module::playpauseDelay 0}
- dict set ::Module::playerStatus $::Module::selectedpid state play
- }
- }
- systemCall getPlayerStatus
- }
- default {LOG "Unknown Multi-State BTN Release Event"}
- }
- }
- }
- proc keyboard_handler { return_data } {
- switch -- $::Module::currentKeyboard {
- "username" {
- dict set ::Module::userCredentials username $return_data
- set ::Module::currentKeyboard "password"
- system_keyboard "HEOS Password" "" "Login Now" "Exit"
- }
- "password" {
- dict set ::Module::userCredentials password $return_data
- set ::Module::currentKeyboard ""
- device_proc "::Control::auth SignIn [dict get $::Module::userCredentials username] [dict get $::Module::userCredentials password]"
- set ::Module::isAuthorizing 0
- }
- }
- }
- # ------------- Module Procedures -------------
- # Handles the Settings Menu UI
- # --------------------------------------------
- proc systemCall {value args} {
- switch -- $value {
- "heartbeat" {LOG "Starting Heartbeat"; device_proc "::URC::moduleCallback heartbeat"}
- "checkConnection" {LOG "Checking for Connection"; return [device_proc "::URC::moduleCallback connection"]}
- "getPlayers" {LOG "Getting Heos Players from System"; set ::Module::Players [device_proc "::URC::moduleCallback getPlayers $args"]; LOG "Players\n$::Module::Players"}
- "getActivePlayers" {LOG "Getting Active Players from System"; set ::Module::ActivePlayers [device_proc "::URC::moduleCallback getActivePlayers"]; LOG "Active Players:\n$::Module::ActivePlayers"}
- "getSources" {LOG "Getting System Source List"; set ::Module::sourceList [device_proc "::URC::moduleCallback getSources $args"]; LOG "$::Module::sourceList"}
- "getChanged" {LOG "Getting System AA Dict"; return [device_proc "::URC::moduleCallback getChanged $args"]}
- "getAll" {LOG "Getting All Information from System"; }
- "getPlayerStatus" {LOG "Getting Player Statuses"
- if {$args == ""} {
- set ::Module::playerStatus [device_proc "::URC::moduleCallback playerStatus"]
- } else {
- set response [device_proc "::URC::moduleCallback playerStatus $args"]
- dict set ::Module::playerStatus [dict get $response pid] $response
- }
- LOG "Player Status\n$::Module::playerStatus"
- # ::UpdateUI::playerStatus
- }
- "getAuthorized" {LOG "Checking if Authenticated"; set ::Module::isAuthorized [device_proc "::URC::moduleCallback getAuthorized $args"]; LOG "Authorized? $::Module::isAuthorized"}
- "SendData" {LOG "Send Data"; device_proc "::URC::moduleCallback $::Module::selectedpid"}
- default {LOG "systemCall Switch Failure"; return -1}
- }
- }
- proc moduleHeartbeat {} {
- LOG "Heartbeat for Module"
- set ::Module::isConnected [systemCall checkConnection]
- if {!$::Module::isConnected} {
- ::moduleStartup
- }
- systemCall heartbeat
- systemCall getActivePlayers
- systemCall getPlayerStatus
- systemCall getPlayers
- systemCall getAuthorized
- # systemCall SendData
- }
- namespace eval UpdateUI {
- proc Players {oldValue callbackName varName args} {
- LOG "Update UI: Players"
- upvar 1 $varName newValue
- # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
- uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
- uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
- ::Update::ActivePlayers
- set pids $::Module::totalActivePlayers
- set i 1
- foreach pid $pids {
- while {$i <= $::Clients::playersPerPage} {
- if {$i == $::Module::selectedRoom} {::Widgets::NowPlaying::SelectedRoom "[dict get $::Module::ActivePlayers $pid name]"}
- set_text "room_${i}_name" "[dict get $::Module::ActivePlayers $pid name]"
- incr i
- }
- }
- # set changed [::Tools::dictCompare $newValue $oldValue]
- # LOG "Player Status Change:\n$changed"
- }
- proc playerStatus {oldValue callbackName varName args} {
- LOG "Update UI: Status"
- LOG "Player Status is:\n$::Module::playerStatus"
- upvar 1 $varName newValue
- # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
- uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
- uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
- catch {set pids [dict keys $::Module::ActivePlayers]}
- set ::Module::totalActivePlayers [llength $pids]
- LOG "Active ID's are $pids"
- }
- proc playerStatus {oldValue callbackName varName args} {
- LOG "Update UI: Status"
- LOG "Player Status is:\n$::Module::playerStatus"
- upvar 1 $varName newValue
- # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
- uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
- uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
- catch {set pid [dict keys $::Module::ActivePlayers]}
- set length [llength $pid]
- LOG "PID Values are: $pid"
- set i 1
- if {[dict exists $::Module::playerStatus $::Module::selectedpid nowplaying song] && $::Module::timedSong != [dict get $::Module::playerStatus $::Module::selectedpid nowplaying song]} {
- set ::Module::everyIndicator "2"
- set_text "nowplaying_timeplayed" "0:00"
- set_text "nowplaying_timeleft" "-0:00"
- LOG "Done..."
- }
- foreach id $pid {
- # Need to Edit for Now Playing Page
- LOG "Setting up $id"
- if {$i == $::Module::selectedRoom} {
- catch {set ::Module::CurrentRoom "[dict get $::Module::ActivePlayers $id name]"}
- catch {set ::Module::selectedpid $id}
- }
- LOG "Set $id Name"; catch {set_text "room_${i}_name" "[dict get $::Module::ActivePlayers $id name]"}
- LOG "Set $id SongInfo"; catch {set_text "nowplaying_${i}_songinfo" "[dict get $::Module::playerStatus $id nowplaying artist] - [dict get $::Module::playerStatus $id nowplaying song]"}
- if {[dict exists $::Module::playerStatus $id nowplaying image_url] && [dict get $::Module::playerStatus $id nowplaying image_url] == ""} {
- # If No "Album Artwork" show "ArtMissing" Graphic
- catch {set_image "nowplaying_${i}_artwork" "C:\ArtMissing_tkp7.png"}
- } elseif {[dict exists $::Module::playerStatus $id nowplaying image_url]} {
- # Set "Heos Player" Album Artwork
- catch {set_image "nowplaying_${i}_artwork" "[dict get $::Module::playerStatus $id nowplaying image_url]"}
- }
- if {![dict exists $::Module::playerStatus $id state]} {
- LOG "No State Detected, Can't Set Indicator"
- } else {
- LOG "Indicator Setup"
- if {[dict get $::Module::playerStatus $id state] == "play"} {
- set_text "nowplaying_${i}_indicator" "b"
- if {$id == $::Module::selectedpid} {
- if {!$::Module::playpauseDelay} {catch {set_state "btn_playpause" 0}}
- if {[dict exists $::Module::prevPlayerStatus $::Module::selectedpid state]} {
- if {[dict get $::Module::prevPlayerStatus $::Module::selectedpid state] != "play"} {::UpdateUI::nowPlaying 1}
- }
- }
- } else {
- catch {set_text "nowplaying_${i}_indicator" "i"}
- if {[dict exists $::Module::playerStatus $id nowplaying artist] && [dict get $::Module::playerStatus $id nowplaying artist] == "" && [dict get $::Module::playerStatus $id nowplaying type] == "station"} {
- catch {set_text "nowplaying_${i}_songinfo" "[dict get $::Module::playerStatus $id nowplaying station]"}
- }
- if {$id == $::Module::selectedpid} {
- if {!$::Module::playpauseDelay} {catch {set_state "btn_playpause" 1}}
- if {[dict exists $::Module::prevPlayerStatus $::Module::selectedpid state]} {
- if {[dict get $::Module::prevPlayerStatus $::Module::selectedpid state] == "play"} {
- LOG "Status Changed - Stop Progress Indicator"
- ::every cancel {::UpdateUI::nowPlayingTime}
- #HAVE NOT TESTED THIS FULLY
- if {[dict exists $::Module::playerStatus $::Module::selectedpid nowplaying duration]} {
- dict set ::Module::nowPlayingTime newElapsed [dict get $::Module::playerStatus $::Module::selectedpid nowplaying elapsed]
- set pct [expr {(double([dict get $::Module::nowPlayingTime newElapsed])/[dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration])*100}]
- set pct [expr {round($pct)}]
- ::Tools::songTimeFormat [dict get $::Module::nowPlayingTime newElapsed] [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]
- catch {set_text "nowplaying_timeplayed" "[dict get $::Module::nowPlayingTime elapsed]"}
- catch {set_text "nowplaying_timeleft" "-[dict get $::Module::nowPlayingTime timeleft]"}
- catch {set_progress "nowplaying_progress" $pct}
- } else {
- set_text "nowplaying_timeplayed" "0:00"
- set_text "nowplaying_timeleft" "-0:00"
- set_progress "nowplaying_progress" 0
- }
- }
- }
- }
- }
- }
- LOG "$id Visibility Setup"
- set_visible "room_${i}_name" 1
- set_visible "nowplaying_${i}_indicator" 1
- set_visible "nowplaying_${i}_songinfo" 1
- set_visible "nowplaying_${i}_artwork" 1
- set_visible "player${i}" 1
- incr i
- }
- if {$i == 2} {
- set_visible "room_2_name" 0
- set_visible "btn_2" 0
- set_visible "nowplaying_2_indicator" 0
- set_visible "nowplaying_2_songinfo" 0
- set_visible "nowplaying_2_artwork" 0
- set_visible "player2" 0
- set_visible "btn_3" 0
- set_visible "nowplaying_3_indicator" 0
- set_visible "room_3_name" 0
- set_visible "nowplaying_3_songinfo" 0
- set_visible "nowplaying_3_artwork" 0
- set_visible "player3" 0
- } elseif {$i == 3} {
- set_visible "room_3_name" 0
- set_visible "btn_3" 0
- set_visible "nowplaying_3_indicator" 0
- set_visible "nowplaying_3_songinfo" 0
- set_visible "nowplaying_3_artwork" 0
- set_visible "player3" 0
- } elseif {$i == 1} {
- set_visible "player1" 0
- set_visible "room_1_name" 0
- set_visible "nowplaying_1_songinfo" 0
- set_visible "nowplaying_1_artwork" 0
- set_visible "nowplaying_1_indicator" 0
- }
- if {[dict exists $::Module::playerStatus $::Module::selectedpid nowplaying elapsed] && [dict exists $::Module::playerStatus $::Module::selectedpid nowplaying duration]} {
- if {[dict get $::Module::playerStatus $::Module::selectedpid nowplaying elapsed] != "" && [dict get $::Module::playerStatus $::Module::selectedpid nowplaying song] != $::Module::timedSong} {
- ::UpdateUI::nowPlaying
- } elseif {$::Module::timedSongDuration != [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]} {
- ::UpdateUI::nowPlaying
- }
- }
- LOG "After Visibility"
- catch {set_text "nowplaying_song" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying song]"} {
- set_text "nowplaying_song" ""
- }
- catch {set_text "nowplaying_artist" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying artist]"} {
- set_text "nowplaying_artist" ""
- }
- if {[dict exists $::Module::playerStatus $id nowplaying type] && [dict get $::Module::playerStatus $id nowplaying type] == "station"} {
- catch {set_text "nowplaying_album" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying station]"}
- } elseif {[dict exists $::Module::playerStatus $id nowplaying type] && [dict get $::Module::playerStatus $id nowplaying type] == "song"} {
- catch {set_text "nowplaying_album" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying album]"}
- }
- catch {set_image "nowplaying_art" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying image_url]"} {
- catch {set_image "C:\ArtMissing_tkp7.png"}
- }
- catch {set_image "nowplaying_art_sm" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying image_url]"} {
- catch {set_image "C:\ArtMissing_tkp7.png"}
- }
- LOG "Finishing PlayerStatus Setup"
- # set changed [::Tools::dictCompare $newValue $oldValue]
- # LOG "Player Status Change:\n$changed"
- set ::Module::prevPlayerStatus $::Module::playerStatus
- }
- proc groups {args} {
- LOG "Setting Groups"
- set i 1
- foreach player $::Module::Players {
- if {[dict exists $player pid] & [dict get $player pid] != $::Module::selectedpid} {
- set pid [dict get $player pid]
- catch {set_text "link_player_${i}" "[dict get $player name]"}
- if {[dict exists $::Module::playerStatus $pid nowplaying gid]} {
- }
- }
- }
- }
- proc nowPlaying {args} {
- LOG "Updater Timer"
- if {[dict exists $::Module::playerStatus $::Module::selectedpid nowplaying duration] && $::Module::timedSongDuration == [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]} {
- if {$args == "1"} {set ::Module::timedSongDuration -1; ::UpdateUI::nowPlaying; return}
- LOG "|-------- Duration has not Updated!"
- after 1000 {::UpdateUI::nowPlaying}
- } else {
- catch {set ::Module::timedSong [dict get $::Module::playerStatus $::Module::selectedpid nowplaying song]}
- catch {set ::Module::timedSongDuration [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]}
- catch {set ::Module::timedSongElapsed [dict get $::Module::playerStatus $::Module::selectedpid nowplaying elapsed]}
- LOG "Timed Song: $::Module::timedSong"
- catch {dict set ::Module::nowPlayingTime newElapsed [expr {[dict get $::Module::playerStatus $::Module::selectedpid nowplaying elapsed] + 3000}]}
- catch {::every cancel {::UpdateUI::nowPlayingTime}}
- if {[dict exists $::Module::playerStatus $::Module::selectedpid state] && [dict get $::Module::playerStatus $::Module::selectedpid state] != "play"} {
- catch {set pct [expr {(double([dict get $::Module::nowPlayingTime newElapsed])/[dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration])*100}]}
- catch {set pct [expr {round($pct)}]}
- catch {::Tools::songTimeFormat [dict get $::Module::nowPlayingTime newElapsed] [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]}
- catch {set_text "nowplaying_timeplayed" "[dict get $::Module::nowPlayingTime elapsed]"}
- catch {set_text "nowplaying_timeleft" "-[dict get $::Module::nowPlayingTime timeleft]"}
- catch {set_progress "nowplaying_progress" $pct}
- } else {
- ::every 1000 {::UpdateUI::nowPlayingTime}
- }
- set ::Module::everyIndicator 1
- LOG "Every Indicator is $::Module::everyIndicator"
- }
- }
- proc nowPlayingTime {} {
- LOG "Update NowPlaying Time"
- catch {dict set ::Module::nowPlayingTime newElapsed [expr {[dict get $::Module::nowPlayingTime newElapsed] + 1000}]}
- catch {set pct [expr {(double([dict get $::Module::nowPlayingTime newElapsed])/[dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration])*100}]}
- set pct [expr {round($pct)}]
- LOG "Percent: $pct"
- ::Tools::songTimeFormat [dict get $::Module::nowPlayingTime newElapsed] [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]
- catch {set_text "nowplaying_timeplayed" "[dict get $::Module::nowPlayingTime elapsed]"}
- catch {set_text "nowplaying_timeleft" "-[dict get $::Module::nowPlayingTime timeleft]"}
- catch {set_progress "nowplaying_progress" $pct}
- }
- proc getCredentials {oldValue callbackName varName args} {
- LOG "Authentication Change Received..."
- upvar 1 $varName newValue
- # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
- uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
- uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
- # if {!$newValue} {LOG "Not Authenticated, Requesting Credentials"
- # if {!$::Module::isAuthorizing} {
- # set ::Module::isAuthorizing 1
- # system_notification "Not Signed In" "You are not currently signed into your Denon HEOS Account." "Sign In"
- # set ::Module::currentKeyboard "username"
- # system_keyboard "HEOS Username" "" "Continue" "Exit"
- # } else {LOG "Currently Authorizing, Waiting for Credentials..."}
- # }
- # set changed [::Tools::dictCompare $newValue $oldValue]
- # LOG "Player Status Change:\n$changed"
- }
- proc CurrentRoom {oldValue callbackName varName args} {
- LOG "Current Room Changed..."
- upvar 1 $varName newValue
- # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
- uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
- uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
- LOG "Old Room: $oldValue\nNew Room: $newValue"
- if {$oldValue != $newValue} {set_text "current_room" $newValue; set_text "current_room2" $newValue; set_text "current_room3" $newValue; set_text "current_room4" $newValue}
- }
- }
- namespace eval Tools {
- proc traceCallback {oldValue callbackName varName args} {
- upvar 1 $varName newValue
- LOG "$varName has Changed:\n$oldValue > changed to > $newValue"
- uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
- uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
- }
- proc setTrace {varName callbackName} {
- set value ""
- upvar 1 $varName currValue
- if {[info exists currValue]} {set value $currValue}
- uplevel [list trace add variable $varName write [list $callbackName $value $callbackName]]
- LOG "Trace Created"
- }
- proc difflists {l1 l2} {
- set lcsData [::struct::list longestCommonSubsequence $l1 $l2]
- set diffs [::struct::list lcsInvert $lcsData [llength $l1] [llength $l2]]
- set changedKeys ""
- foreach d $diffs {
- set op ""
- lassign $d type i1 i2; lassign $i1 s1 e1; lassign $i2 s2 e2
- LOG "$type: [lrange $l2 {*}$i2] to -> [lrange $l1 {*}$i1]"
- #:[lindex $l1 $e1] -> [lindex $l2 $s2-1]:[lindex $l2 $e2]
- set flatkey [lindex $l1 $s1-1]; lappend op [string map {. " "} $flatkey]
- lappend op [lrange $l1 {*}$i1]
- lappend changedKeys $op
- }
- return $changedKeys
- }
- proc dict_flatten {dictVal} {
- dict for {k v} $dictVal {
- if {[llength $v] % 2 == 0} {
- set dictVal [dict remove $dictVal[set dictVal {}] $k]
- dict for {sk sv} [::Tools::dict_flatten $v] {dict set dictVal ${k}.${sk} $sv}
- }
- }
- return $dictVal
- }
- proc songTimeFormat {elapsed duration} {
- LOG "Time Format Starts"
- set timeleft [expr {$duration - $elapsed}]
- LOG "Elapsed $elapsed Duration $duration Time Left $timeleft"
- if {$timeleft <= 1000} {
- catch {::every cancel {::UpdateUI::nowPlayingTime}}
- set_text "nowplaying_timeplayed" "0:00"
- set_text "nowplaying_timeleft" "-0:00"
- set ::Module::timedSongDuration -1
- after 2000 {::systemCall getPlayerStatus}
- }
- set secs [expr {$elapsed / 1000}]
- set mins [expr {$secs / 60}]
- set secs [expr {$secs - 60*$mins}]
- set Felapsed [format %d:%02d $mins $secs]
- LOG "Elapsed: $Felapsed"
- set secs [expr {$duration / 1000}]
- set mins [expr {$secs / 60}]
- set secs [expr {$secs - 60*$mins}]
- set Fduration [format %d:%02d $mins $secs]
- LOG "Duration: $Fduration"
- set secs [expr {$timeleft / 1000}]
- set mins [expr {$secs / 60}]
- set secs [expr {$secs - 60*$mins}]
- set timeleft [format %d:%02d $mins $secs]
- LOG "Time Left: $timeleft"
- dict set ::Module::nowPlayingTime elapsed $Felapsed
- #dict set ::Module::nowPlayingTime mselapsed $elapsed
- dict set ::Module::nowPlayingTime timeleft $timeleft
- dict set ::Module::nowplayingTime duration $Fduration
- #dict set ::Module::nowPlayingTime msduration $duration
- LOG "Completed Time Format"
- }
- proc dictCompare {newDict oldDict} {
- # Compares Two Dictionaries and Returns with a List
- # with all changed Dictionary Keys.
- # Example Resonse:
- # {key1 key1 key1} {key1 key1 key2} {key2 key1 key1}
- # Get Values using the {*} operator on the response:
- # dict get $dict {*}[lindex $differences 0]
- set differences [::Tools::difflists [::Tools::dict_flatten $newDict] [::Tools::dict_flatten $oldDict]]
- return $differences
- }
- }
- proc every {option args} { # Schedules a Command to happen every XX MS. Can be canceled using ::tools::every cancel $returnedvalue
- LOG "Every Procedure Executed"
- global everyPriv every:UID
- if {[string equal -length [string length $option] $option cancel]} {
- set id {}
- if {[llength $args] == 1 && [string match every#* [lindex $args 0]]} {set id [lindex $args 0]
- } else {set script [eval [list concat] $args]; foreach {key value} [array get everyPriv] {if {[string equal $script [lindex $value 1]]} {set id $key; break}}}
- if {[string length $id]} {after cancel [lindex $everyPriv($id) 2]; unset everyPriv($id)}
- } else {
- set id [format "every#%d" [incr every:UID]]; set script [eval [list concat] $args]; set delay $option
- set aid [after $delay [list every:afterHandler $id]]; set everyPriv($id) [list $delay $script $aid]; return $id
- }
- }
- array set everyPriv {}; set every:UID 0
- proc every:afterHandler {id} {
- global everyPriv
- foreach {delay script oldaid} $everyPriv($id) {}
- set aid [after $delay [info level 0]]; set everyPriv($id) [list $delay $script $aid]
- uplevel #0 $script
- }
- # --------------------------------------------
- changePage "Rooms"
- moduleStartup