From f593e79b539de14185df8d13d391c25096e9ff51 Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Sat, 23 Aug 2014 22:04:26 -0700 Subject: [PATCH] Added remote reloading of all displays. --- arena.go | 2 + displays.go | 87 ++++++++++++++++++++++++++++++++++++++ setup_field.go | 6 +++ static/js/pit_display.js | 4 ++ templates/field.html | 4 ++ templates/pit_display.html | 3 ++ web.go | 2 + 7 files changed, 108 insertions(+) diff --git a/arena.go b/arena.go index 7b7349c..1bf4624 100644 --- a/arena.go +++ b/arena.go @@ -81,6 +81,7 @@ type Arena struct { allianceSelectionNotifier *Notifier lowerThirdNotifier *Notifier hotGoalLightNotifier *Notifier + reloadDisplaysNotifier *Notifier audienceDisplayScreen string allianceStationDisplays map[string]string allianceStationDisplayScreen string @@ -128,6 +129,7 @@ func (arena *Arena) Setup() { arena.allianceSelectionNotifier = NewNotifier() arena.lowerThirdNotifier = NewNotifier() arena.hotGoalLightNotifier = NewNotifier() + arena.reloadDisplaysNotifier = NewNotifier() // Load empty match as current. arena.MatchState = PRE_MATCH diff --git a/displays.go b/displays.go index 5e11b48..5c54e23 100644 --- a/displays.go +++ b/displays.go @@ -64,6 +64,8 @@ func AudienceDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { defer close(allianceSelectionListener) lowerThirdListener := mainArena.lowerThirdNotifier.Listen() defer close(lowerThirdListener) + reloadDisplaysListener := mainArena.reloadDisplaysNotifier.Listen() + defer close(reloadDisplaysListener) // Send the various notifications immediately upon connection. var data interface{} @@ -194,6 +196,12 @@ func AudienceDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { } messageType = "lowerThird" message = lowerThird + case _, ok := <-reloadDisplaysListener: + if !ok { + return + } + messageType = "reload" + message = nil } err = websocket.Write(messageType, message) if err != nil { @@ -234,6 +242,53 @@ func PitDisplayHandler(w http.ResponseWriter, r *http.Request) { } } +// The websocket endpoint for the pit display, used only to force reloads remotely. +func PitDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { + websocket, err := NewWebsocket(w, r) + if err != nil { + handleWebErr(w, err) + return + } + defer websocket.Close() + + reloadDisplaysListener := mainArena.reloadDisplaysNotifier.Listen() + defer close(reloadDisplaysListener) + + // Spin off a goroutine to listen for notifications and pass them on through the websocket. + go func() { + for { + var messageType string + var message interface{} + select { + case _, ok := <-reloadDisplaysListener: + if !ok { + return + } + messageType = "reload" + message = nil + } + err = websocket.Write(messageType, message) + if err != nil { + // The client has probably closed the connection; nothing to do here. + return + } + } + }() + + // Loop, waiting for commands and responding to them, until the client closes the connection. + for { + _, _, err := websocket.Read() + if err != nil { + if err == io.EOF { + // Client has closed the connection; nothing to do here. + return + } + log.Printf("Websocket error: %s", err) + return + } + } +} + // Renders the announcer display which shows team info and scores for the current match. func AnnouncerDisplayHandler(w http.ResponseWriter, r *http.Request) { template := template.New("").Funcs(templateHelpers) @@ -271,6 +326,8 @@ func AnnouncerDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { defer close(scorePostedListener) audienceDisplayListener := mainArena.audienceDisplayNotifier.Listen() defer close(audienceDisplayListener) + reloadDisplaysListener := mainArena.reloadDisplaysNotifier.Listen() + defer close(reloadDisplaysListener) // Send the various notifications immediately upon connection. var data interface{} @@ -374,6 +431,12 @@ func AnnouncerDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { } messageType = "setAudienceDisplay" message = mainArena.audienceDisplayScreen + case _, ok := <-reloadDisplaysListener: + if !ok { + return + } + messageType = "reload" + message = nil } err = websocket.Write(messageType, message) if err != nil { @@ -459,6 +522,8 @@ func ScoringDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { matchLoadTeamsListener := mainArena.matchLoadTeamsNotifier.Listen() defer close(matchLoadTeamsListener) + reloadDisplaysListener := mainArena.reloadDisplaysNotifier.Listen() + defer close(reloadDisplaysListener) // Send the various notifications immediately upon connection. err = websocket.Write("score", *score) @@ -479,6 +544,12 @@ func ScoringDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { } messageType = "score" message = *score + case _, ok := <-reloadDisplaysListener: + if !ok { + return + } + messageType = "reload" + message = nil } err = websocket.Write(messageType, message) if err != nil { @@ -710,6 +781,8 @@ func RefereeDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { matchLoadTeamsListener := mainArena.matchLoadTeamsNotifier.Listen() defer close(matchLoadTeamsListener) + reloadDisplaysListener := mainArena.reloadDisplaysNotifier.Listen() + defer close(reloadDisplaysListener) // Spin off a goroutine to listen for notifications and pass them on through the websocket. go func() { @@ -723,6 +796,12 @@ func RefereeDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) { } messageType = "reload" message = nil + case _, ok := <-reloadDisplaysListener: + if !ok { + return + } + messageType = "reload" + message = nil } err = websocket.Write(messageType, message) if err != nil { @@ -892,6 +971,8 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque defer close(realtimeScoreListener) hotGoalLightListener := mainArena.hotGoalLightNotifier.Listen() defer close(hotGoalLightListener) + reloadDisplaysListener := mainArena.reloadDisplaysNotifier.Listen() + defer close(reloadDisplaysListener) // Send the various notifications immediately upon connection. var data interface{} @@ -997,6 +1078,12 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque } messageType = "hotGoalLight" message = side + case _, ok := <-reloadDisplaysListener: + if !ok { + return + } + messageType = "reload" + message = nil } err = websocket.Write(messageType, message) if err != nil { diff --git a/setup_field.go b/setup_field.go index 5b8b525..e4a13b9 100644 --- a/setup_field.go +++ b/setup_field.go @@ -36,3 +36,9 @@ func FieldPostHandler(w http.ResponseWriter, r *http.Request) { mainArena.matchLoadTeamsNotifier.Notify(nil) http.Redirect(w, r, "/setup/field", 302) } + +// Force-reloads all the websocket-connected displays. +func FieldReloadDisplaysHandler(w http.ResponseWriter, r *http.Request) { + mainArena.reloadDisplaysNotifier.Notify(nil) + http.Redirect(w, r, "/setup/field", 302) +} diff --git a/static/js/pit_display.js b/static/js/pit_display.js index 18b0961..4a6debd 100644 --- a/static/js/pit_display.js +++ b/static/js/pit_display.js @@ -4,6 +4,7 @@ // // Client-side methods for the pit display. +var websocket; var initial_dwell_ms = 3000; var scroll_ms_per_row = 700; // How long in milliseconds it takes to scroll a height of one row. var static_update_interval_ms = 10000; // How long between updates if not scrolling. @@ -80,5 +81,8 @@ var setHighestPlayedMatch = function(highestPlayedMatch) { }; $(function() { + // Set up the websocket back to the server. Used only for remote forcing of reloads. + websocket = new CheesyWebsocket("/displays/pit/websocket", {}); + updateStaticRankings(); }); diff --git a/templates/field.html b/templates/field.html index 9136275..d3ad6ed 100644 --- a/templates/field.html +++ b/templates/field.html @@ -23,6 +23,10 @@ {{end}} + Reload All Displays +
+ Force Reload of All Displays +
diff --git a/templates/pit_display.html b/templates/pit_display.html index e897fd6..7cabc4e 100644 --- a/templates/pit_display.html +++ b/templates/pit_display.html @@ -67,7 +67,10 @@ + + + diff --git a/web.go b/web.go index d706cf6..9be695a 100644 --- a/web.go +++ b/web.go @@ -123,6 +123,7 @@ func newHandler() http.Handler { router.HandleFunc("/setup/alliance_selection/finalize", AllianceSelectionFinalizeHandler).Methods("POST") router.HandleFunc("/setup/field", FieldGetHandler).Methods("GET") router.HandleFunc("/setup/field", FieldPostHandler).Methods("POST") + router.HandleFunc("/setup/field/reload_displays", FieldReloadDisplaysHandler).Methods("GET") router.HandleFunc("/setup/lower_thirds", LowerThirdsGetHandler).Methods("GET") router.HandleFunc("/setup/lower_thirds", LowerThirdsPostHandler).Methods("POST") router.HandleFunc("/match_play", MatchPlayHandler).Methods("GET") @@ -142,6 +143,7 @@ func newHandler() http.Handler { router.HandleFunc("/displays/audience", AudienceDisplayHandler).Methods("GET") router.HandleFunc("/displays/audience/websocket", AudienceDisplayWebsocketHandler).Methods("GET") router.HandleFunc("/displays/pit", PitDisplayHandler).Methods("GET") + router.HandleFunc("/displays/pit/websocket", PitDisplayWebsocketHandler).Methods("GET") router.HandleFunc("/displays/announcer", AnnouncerDisplayHandler).Methods("GET") router.HandleFunc("/displays/announcer/websocket", AnnouncerDisplayWebsocketHandler).Methods("GET") router.HandleFunc("/displays/scoring/{alliance}", ScoringDisplayHandler).Methods("GET")