// Copyright 2014 Team 254. All Rights Reserved. // Author: pat@patfairbank.com (Patrick Fairbank) // // Web handlers for scoring interface. package web import ( "fmt" "github.com/Team254/cheesy-arena/field" "github.com/Team254/cheesy-arena/model" "github.com/Team254/cheesy-arena/websocket" "github.com/gorilla/mux" "io" "log" "net/http" ) // Renders the scoring interface which enables input of scores in real-time. func (web *Web) scoringPanelHandler(w http.ResponseWriter, r *http.Request) { if !web.userIsAdmin(w, r) { return } vars := mux.Vars(r) alliance := vars["alliance"] if alliance != "red" && alliance != "blue" { handleWebErr(w, fmt.Errorf("Invalid alliance '%s'.", alliance)) return } template, err := web.parseFiles("templates/scoring_panel.html", "templates/base.html") if err != nil { handleWebErr(w, err) return } data := struct { *model.EventSettings Alliance string }{web.arena.EventSettings, alliance} err = template.ExecuteTemplate(w, "base_no_navbar", data) if err != nil { handleWebErr(w, err) return } } // The websocket endpoint for the scoring interface client to send control commands and receive status updates. func (web *Web) scoringPanelWebsocketHandler(w http.ResponseWriter, r *http.Request) { if !web.userIsAdmin(w, r) { return } vars := mux.Vars(r) alliance := vars["alliance"] if alliance != "red" && alliance != "blue" { handleWebErr(w, fmt.Errorf("Invalid alliance '%s'.", alliance)) return } var score **field.RealtimeScore if alliance == "red" { score = &web.arena.RedRealtimeScore } else { score = &web.arena.BlueRealtimeScore } ws, err := websocket.NewWebsocket(w, r) if err != nil { handleWebErr(w, err) return } defer ws.Close() // Subscribe the websocket to the notifiers whose messages will be passed on to the client, in a separate goroutine. go ws.HandleNotifiers(web.arena.MatchTimeNotifier, web.arena.RealtimeScoreNotifier, web.arena.ReloadDisplaysNotifier) // Loop, waiting for commands and responding to them, until the client closes the connection. for { messageType, _, err := ws.Read() if err != nil { if err == io.EOF { // Client has closed the connection; nothing to do here. return } log.Println(err) return } scoreChanged := false switch messageType { case "r": if !(*score).AutoCommitted { if (*score).CurrentScore.AutoRuns < 3 { (*score).CurrentScore.AutoRuns++ scoreChanged = true } } case "R": if !(*score).AutoCommitted { if (*score).CurrentScore.AutoRuns > 0 { (*score).CurrentScore.AutoRuns-- scoreChanged = true } } case "c": if (*score).AutoCommitted { if (*score).CurrentScore.Climbs+(*score).CurrentScore.Parks < 3 { (*score).CurrentScore.Climbs++ scoreChanged = true } } case "C": if (*score).AutoCommitted { if (*score).CurrentScore.Climbs > 0 { (*score).CurrentScore.Climbs-- scoreChanged = true } } case "p": if (*score).AutoCommitted { if (*score).CurrentScore.Climbs+(*score).CurrentScore.Parks < 3 { (*score).CurrentScore.Parks++ scoreChanged = true } } case "P": if (*score).AutoCommitted { if (*score).CurrentScore.Parks > 0 { (*score).CurrentScore.Parks-- scoreChanged = true } } case "\r": if (web.arena.MatchState != field.PreMatch && web.arena.MatchState != field.TimeoutActive && web.arena.MatchState != field.PostTimeout || web.arena.CurrentMatch.Type == "test") && !(*score).AutoCommitted { (*score).AutoCommitted = true scoreChanged = true } case "a": if (*score).AutoCommitted { (*score).AutoCommitted = false scoreChanged = true } case "commitMatch": if web.arena.MatchState != field.PostMatch { // Don't allow committing the score until the match is over. ws.WriteError("Cannot commit score: Match is not over.") continue } if !(*score).TeleopCommitted { (*score).AutoCommitted = true (*score).TeleopCommitted = true web.arena.ScoringStatusNotifier.Notify() scoreChanged = true } default: // Unknown keypress; just swallow the message without doing anything. continue } if scoreChanged { web.arena.RealtimeScoreNotifier.Notify() } } }