mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Cleaned up the code and added comments.
This commit is contained in:
@@ -75,6 +75,7 @@ func ConfigureTeamWifi(red1, red2, red3, blue1, blue2, blue3 *Team) error {
|
||||
removeSsidsCommand += fmt.Sprintf("no dot11 ssid %s\n", ssid)
|
||||
}
|
||||
|
||||
// Build and run the overall command to do everything in a single telnet session.
|
||||
command := removeSsidsCommand + addSsidsCommand + associateSsidsCommand
|
||||
if len(command) > 0 {
|
||||
_, err = runAironetConfigCommand(removeSsidsCommand + addSsidsCommand + associateSsidsCommand)
|
||||
|
||||
@@ -24,8 +24,10 @@ func AllianceStationDisplayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
displayId := ""
|
||||
if _, ok := r.URL.Query()["displayId"]; ok {
|
||||
// Register the display in memory by its ID so that it can be configured to a certain station.
|
||||
displayId = r.URL.Query()["displayId"][0]
|
||||
}
|
||||
|
||||
data := struct {
|
||||
*EventSettings
|
||||
DisplayId string
|
||||
@@ -202,6 +204,7 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
switch messageType {
|
||||
case "setAllianceStation":
|
||||
// The client knows what station it is (e.g. across a server restart) and is informing the server.
|
||||
station, ok := data.(string)
|
||||
if !ok {
|
||||
websocket.WriteError(fmt.Sprintf("Failed to parse '%s' message.", messageType))
|
||||
|
||||
@@ -184,6 +184,7 @@ func AnnouncerDisplayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
switch messageType {
|
||||
case "setAudienceDisplay":
|
||||
// The announcer can make the final score screen show when they are ready to announce the score.
|
||||
screen, ok := data.(string)
|
||||
if !ok {
|
||||
websocket.WriteError(fmt.Sprintf("Failed to parse '%s' message.", messageType))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
! Baseline configuration for the Cisco Aironet AP1252AG access point. Load this into the AP prior to
|
||||
! configuring Cheesy Arena to connect to it. Default user/pass is cheesyarena/1234Five.
|
||||
!
|
||||
version 15.2
|
||||
no service pad
|
||||
|
||||
3
api.go
3
api.go
@@ -55,6 +55,7 @@ func MatchesApiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a JSON dump of the sponsor slides for use by the audience display.
|
||||
func SponsorSlidesApiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sponsors, err := db.GetAllSponsorSlides()
|
||||
if err != nil {
|
||||
@@ -76,7 +77,7 @@ func SponsorSlidesApiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a JSON dump of the qualification rankings.
|
||||
// Generates a JSON dump of the qualification rankings, primarily for use by the pit display.
|
||||
func RankingsApiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
rankings, err := db.GetAllRankings()
|
||||
if err != nil {
|
||||
|
||||
21
arena.go
21
arena.go
@@ -20,13 +20,13 @@ const (
|
||||
|
||||
// Progression of match states.
|
||||
const (
|
||||
PRE_MATCH = iota
|
||||
START_MATCH
|
||||
AUTO_PERIOD
|
||||
PAUSE_PERIOD
|
||||
TELEOP_PERIOD
|
||||
ENDGAME_PERIOD
|
||||
POST_MATCH
|
||||
PRE_MATCH = 0
|
||||
START_MATCH = 1
|
||||
AUTO_PERIOD = 2
|
||||
PAUSE_PERIOD = 3
|
||||
TELEOP_PERIOD = 4
|
||||
ENDGAME_PERIOD = 5
|
||||
POST_MATCH = 6
|
||||
)
|
||||
|
||||
type AllianceStation struct {
|
||||
@@ -229,16 +229,16 @@ func (arena *Arena) LoadMatch(match *Match) error {
|
||||
arena.redRealtimeScore = NewRealtimeScore()
|
||||
arena.blueRealtimeScore = NewRealtimeScore()
|
||||
|
||||
// Notify any listeners about the new match.
|
||||
arena.matchLoadTeamsNotifier.Notify(nil)
|
||||
arena.realtimeScoreNotifier.Notify(nil)
|
||||
|
||||
arena.allianceStationDisplayScreen = "match"
|
||||
arena.allianceStationDisplayNotifier.Notify(nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets a new test match as the current match.
|
||||
// Sets a new test match containing no teams as the current match.
|
||||
func (arena *Arena) LoadTestMatch() error {
|
||||
return arena.LoadMatch(&Match{Type: "test"})
|
||||
}
|
||||
@@ -488,8 +488,6 @@ func (arena *Arena) Update() {
|
||||
// Send a packet if at a period transition point or if it's been long enough since the last one.
|
||||
if sendDsPacket || time.Since(arena.lastDsPacketTime).Seconds()*1000 >= dsPacketPeriodMs {
|
||||
arena.sendDsPacket(auto, enabled)
|
||||
|
||||
// TODO(pat): Come up with better criteria for sending robot status updates.
|
||||
arena.robotStatusNotifier.Notify(nil)
|
||||
}
|
||||
|
||||
@@ -519,6 +517,7 @@ func (arena *Arena) sendDsPacket(auto bool, enabled bool) {
|
||||
arena.lastDsPacketTime = time.Now()
|
||||
}
|
||||
|
||||
// Calculates the integer score value for the given realtime snapshot.
|
||||
func (realtimeScore *RealtimeScore) Score(opponentFouls []Foul) int {
|
||||
score := scoreSummary(&realtimeScore.CurrentScore, opponentFouls).Score
|
||||
if realtimeScore.CurrentCycle.Truss {
|
||||
|
||||
@@ -58,6 +58,7 @@ func ConfigureTeamEthernet(red1, red2, red3, blue1, blue2, blue3 *Team) error {
|
||||
removeTeamVlansCommand += fmt.Sprintf("interface Vlan%d\nno ip address\nno access-list 1%d\n", vlan, vlan)
|
||||
}
|
||||
|
||||
// Build and run the overall command to do everything in a single telnet session.
|
||||
command := removeTeamVlansCommand + addTeamVlansCommand
|
||||
if len(command) > 0 {
|
||||
_, err = runCatalystConfigCommand(removeTeamVlansCommand + addTeamVlansCommand)
|
||||
|
||||
@@ -123,7 +123,9 @@ func ListenForDsPackets(listener *net.UDPConn) {
|
||||
}
|
||||
}
|
||||
|
||||
// Called at the start of the match to allow for driver station initialization.
|
||||
func (dsConn *DriverStationConnection) signalMatchStart(match *Match) error {
|
||||
// Zero out missed packet count and begin logging.
|
||||
dsConn.missedPacketOffset = dsConn.DriverStationStatus.MissedPacketCount
|
||||
var err error
|
||||
dsConn.log, err = NewTeamMatchLog(dsConn.TeamId, match)
|
||||
|
||||
@@ -210,6 +210,7 @@ func (database *Database) buildEliminationMatchSet(round int, group int, numAlli
|
||||
return []AllianceTeam{}, nil
|
||||
}
|
||||
|
||||
// Creates a match at the given point in the elimination bracket and populates the teams.
|
||||
func createMatch(roundName string, round int, group int, instance int, redAlliance []AllianceTeam, blueAlliance []AllianceTeam) *Match {
|
||||
match := Match{Type: "elimination", DisplayName: fmt.Sprintf("%s-%d", roundName, instance),
|
||||
ElimRound: round, ElimGroup: group, ElimInstance: instance}
|
||||
@@ -218,6 +219,7 @@ func createMatch(roundName string, round int, group int, instance int, redAllian
|
||||
return &match
|
||||
}
|
||||
|
||||
// Assigns the first three teams from the alliance randomly into the red team slots for the match.
|
||||
func shuffleRedTeams(match *Match, alliance []AllianceTeam) {
|
||||
shuffle := rand.Perm(3)
|
||||
match.Red1 = alliance[shuffle[0]].TeamId
|
||||
@@ -225,6 +227,7 @@ func shuffleRedTeams(match *Match, alliance []AllianceTeam) {
|
||||
match.Red3 = alliance[shuffle[2]].TeamId
|
||||
}
|
||||
|
||||
// Assigns the first three teams from the alliance randomly into the blue team slots for the match.
|
||||
func shuffleBlueTeams(match *Match, alliance []AllianceTeam) {
|
||||
shuffle := rand.Perm(3)
|
||||
match.Blue1 = alliance[shuffle[0]].TeamId
|
||||
@@ -232,6 +235,7 @@ func shuffleBlueTeams(match *Match, alliance []AllianceTeam) {
|
||||
match.Blue3 = alliance[shuffle[2]].TeamId
|
||||
}
|
||||
|
||||
// Returns true if the given team is part of the given alliance.
|
||||
func teamInAlliance(teamId int, alliance []AllianceTeam) bool {
|
||||
for _, allianceTeam := range alliance {
|
||||
if teamId == allianceTeam.TeamId {
|
||||
|
||||
14
lights.go
14
lights.go
@@ -23,6 +23,7 @@ type Lights struct {
|
||||
animationCount int
|
||||
}
|
||||
|
||||
// Sets the color by name and transition time for the given LED channel.
|
||||
func (lightPacket *LightPacket) setColorFade(channel int, color string, fade byte) {
|
||||
switch color {
|
||||
case "off":
|
||||
@@ -44,10 +45,12 @@ func (lightPacket *LightPacket) setColorFade(channel int, color string, fade byt
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the color by name with instant transition for the given LED channel.
|
||||
func (lightPacket *LightPacket) setColor(channel int, color string) {
|
||||
lightPacket.setColorFade(channel, color, 0)
|
||||
}
|
||||
|
||||
// Sets the color by RGB values and transition time for the given LED channel.
|
||||
func (lightPacket *LightPacket) setRgbFade(channel int, red byte, green byte, blue byte, fade byte) {
|
||||
lightPacket[channel*4] = red
|
||||
lightPacket[channel*4+1] = green
|
||||
@@ -55,10 +58,12 @@ func (lightPacket *LightPacket) setRgbFade(channel int, red byte, green byte, bl
|
||||
lightPacket[channel*4+3] = fade
|
||||
}
|
||||
|
||||
// Sets the color by name with instant transition for all LED channels.
|
||||
func (lightPacket *LightPacket) setAllColor(color string) {
|
||||
lightPacket.setAllColorFade(color, 0)
|
||||
}
|
||||
|
||||
// Sets the color by name and transition time for all LED channels.
|
||||
func (lightPacket *LightPacket) setAllColorFade(color string, fade byte) {
|
||||
for i := 0; i < 8; i++ {
|
||||
lightPacket.setColorFade(i, color, fade)
|
||||
@@ -118,6 +123,7 @@ func (lights *Lights) SetupConnections() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Makes a goal for the given alliance hot.
|
||||
func (lights *Lights) SetHotGoal(alliance string, leftSide bool) {
|
||||
if leftSide {
|
||||
lights.packets[alliance].setColor(0, "off")
|
||||
@@ -139,6 +145,7 @@ func (lights *Lights) SetHotGoal(alliance string, leftSide bool) {
|
||||
lights.sendLights()
|
||||
}
|
||||
|
||||
// Lights up the given alliance's goal for the given number of assists.
|
||||
func (lights *Lights) SetAssistGoal(alliance string, numAssists int) {
|
||||
lights.packets[alliance].setColor(0, "off")
|
||||
lights.packets[alliance].setColor(1, "off")
|
||||
@@ -162,6 +169,7 @@ func (lights *Lights) SetAssistGoal(alliance string, numAssists int) {
|
||||
lights.sendLights()
|
||||
}
|
||||
|
||||
// Turns off all lights for the given alliance's goal.
|
||||
func (lights *Lights) ClearGoal(alliance string) {
|
||||
lights.packets[alliance].setColorFade(0, "off", 10)
|
||||
lights.packets[alliance].setColorFade(1, "off", 10)
|
||||
@@ -171,6 +179,7 @@ func (lights *Lights) ClearGoal(alliance string) {
|
||||
lights.packets[alliance].setColorFade(5, "off", 10)
|
||||
}
|
||||
|
||||
// Turns on the given alliance's pedestal.
|
||||
func (lights *Lights) SetPedestal(alliance string) {
|
||||
if alliance == "red" {
|
||||
lights.packets["blue"].setColor(6, alliance)
|
||||
@@ -180,6 +189,7 @@ func (lights *Lights) SetPedestal(alliance string) {
|
||||
lights.sendLights()
|
||||
}
|
||||
|
||||
// Turns off the given alliance's pedestal.
|
||||
func (lights *Lights) ClearPedestal(alliance string) {
|
||||
if alliance == "red" {
|
||||
lights.packets["blue"].setColorFade(6, "off", 10)
|
||||
@@ -189,12 +199,14 @@ func (lights *Lights) ClearPedestal(alliance string) {
|
||||
lights.sendLights()
|
||||
}
|
||||
|
||||
// Turns all lights green to signal that the field is safe to enter.
|
||||
func (lights *Lights) SetFieldReset() {
|
||||
lights.packets["red"].setAllColor("green")
|
||||
lights.packets["blue"].setAllColor("green")
|
||||
lights.sendLights()
|
||||
}
|
||||
|
||||
// Sets the lights to the given non-match mode for show or testing.
|
||||
func (lights *Lights) SetMode(mode string) {
|
||||
lights.currentMode = mode
|
||||
lights.animationCount = 0
|
||||
@@ -219,6 +231,7 @@ func (lights *Lights) SetMode(mode string) {
|
||||
lights.sendLights()
|
||||
}
|
||||
|
||||
// Sends a control packet to the LED controllers only if their state needs to be updated.
|
||||
func (lights *Lights) sendLights() {
|
||||
for alliance, connection := range lights.connections {
|
||||
if lights.newConnections || *lights.packets[alliance] != *lights.oldPackets[alliance] {
|
||||
@@ -235,6 +248,7 @@ func (lights *Lights) sendLights() {
|
||||
lights.newConnections = false
|
||||
}
|
||||
|
||||
// State machine for controlling light sequences in the non-match modes.
|
||||
func (lights *Lights) animate() {
|
||||
lights.animationCount += 1
|
||||
|
||||
|
||||
4
main.go
4
main.go
@@ -14,10 +14,12 @@ const eventDbPath = "./event.db"
|
||||
var db *Database
|
||||
var eventSettings *EventSettings
|
||||
|
||||
// Main entry point for the application.
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
initDb()
|
||||
|
||||
// Run the webserver and DS packet listener in goroutines and use the main one for the arena state machine.
|
||||
go ServeWebInterface()
|
||||
listener, err := DsPacketListener()
|
||||
checkErr(err)
|
||||
@@ -26,6 +28,7 @@ func main() {
|
||||
mainArena.Run()
|
||||
}
|
||||
|
||||
// Opens the database and stores a handle to it in a global variable.
|
||||
func initDb() {
|
||||
var err error
|
||||
db, err = OpenDatabase(eventDbPath)
|
||||
@@ -34,6 +37,7 @@ func initDb() {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
// Logs and exits the application if the given error is not nil.
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
log.Fatalln("Error: ", err)
|
||||
|
||||
@@ -87,6 +87,7 @@ func MatchPlayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Loads the given match onto the arena in preparation for playing it.
|
||||
func MatchPlayLoadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
matchId, _ := strconv.Atoi(vars["matchId"])
|
||||
@@ -435,6 +436,7 @@ func CommitMatchScore(match *Match, matchResult *MatchResult) error {
|
||||
}
|
||||
|
||||
if match.Type != "practice" {
|
||||
// Regenerate the residual yellow cards that teams may carry.
|
||||
db.CalculateTeamCards(match.Type)
|
||||
}
|
||||
|
||||
@@ -488,18 +490,22 @@ func CommitCurrentMatchScore() error {
|
||||
return CommitMatchScore(mainArena.currentMatch, &matchResult)
|
||||
}
|
||||
|
||||
// Helper function to implement the required interface for Sort.
|
||||
func (list MatchPlayList) Len() int {
|
||||
return len(list)
|
||||
}
|
||||
|
||||
// Helper function to implement the required interface for Sort.
|
||||
func (list MatchPlayList) Less(i, j int) bool {
|
||||
return list[i].Status != "complete" && list[j].Status == "complete"
|
||||
}
|
||||
|
||||
// Helper function to implement the required interface for Sort.
|
||||
func (list MatchPlayList) Swap(i, j int) {
|
||||
list[i], list[j] = list[j], list[i]
|
||||
}
|
||||
|
||||
// Constructs the list of matches to display on the side of the match play interface.
|
||||
func buildMatchPlayList(matchType string) (MatchPlayList, error) {
|
||||
matches, err := db.GetMatchesByType(matchType)
|
||||
if err != nil {
|
||||
|
||||
@@ -124,6 +124,7 @@ func MatchReviewEditPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/match_review", 302)
|
||||
}
|
||||
|
||||
// Load the match result for the match referenced in the HTTP query string.
|
||||
func getMatchResultFromRequest(r *http.Request) (*Match, *MatchResult, error) {
|
||||
vars := mux.Vars(r)
|
||||
matchId, _ := strconv.Atoi(vars["matchId"])
|
||||
@@ -146,6 +147,7 @@ func getMatchResultFromRequest(r *http.Request) (*Match, *MatchResult, error) {
|
||||
return match, matchResult, nil
|
||||
}
|
||||
|
||||
// Constructs the list of matches to display in the match review interface.
|
||||
func buildMatchReviewList(matchType string) ([]MatchReviewListItem, error) {
|
||||
matches, err := db.GetMatchesByType(matchType)
|
||||
if err != nil {
|
||||
|
||||
@@ -47,7 +47,7 @@ func (notifier *Notifier) notifyListener(listener chan interface{}, message inte
|
||||
}()
|
||||
|
||||
// Do a non-blocking send. This guarantees that sending notifications won't interrupt the main event loop,
|
||||
// at the risk of clients missing some messages.
|
||||
// at the risk of clients missing some messages if they don't read them all promptly.
|
||||
select {
|
||||
case listener <- message:
|
||||
// The notification was sent and received successfully.
|
||||
|
||||
@@ -99,7 +99,7 @@ func (database *Database) CalculateRankings() error {
|
||||
}
|
||||
sortedRankings := sortRankings(rankings)
|
||||
|
||||
// Stuff the match results into the database in an atomic operation.
|
||||
// Stuff the rankings into the database in an atomic operation to prevent messing them up halfway.
|
||||
transaction, err := database.rankingMap.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -174,6 +174,7 @@ func (database *Database) CalculateTeamCards(matchType string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Incrementally accounts for the given match result in the set of rankings that are being built.
|
||||
func addMatchResultToRankings(rankings map[int]*Ranking, teamId int, matchResult *MatchResult, isRed bool) {
|
||||
ranking := rankings[teamId]
|
||||
if ranking == nil {
|
||||
@@ -233,10 +234,12 @@ func sortRankings(rankings map[int]*Ranking) Rankings {
|
||||
return sortedRankings
|
||||
}
|
||||
|
||||
// Helper function to implement the required interface for Sort.
|
||||
func (rankings Rankings) Len() int {
|
||||
return len(rankings)
|
||||
}
|
||||
|
||||
// Helper function to implement the required interface for Sort.
|
||||
func (rankings Rankings) Less(i, j int) bool {
|
||||
a := rankings[i]
|
||||
b := rankings[j]
|
||||
@@ -259,6 +262,7 @@ func (rankings Rankings) Less(i, j int) bool {
|
||||
return a.QualificationScore*b.Played > b.QualificationScore*a.Played
|
||||
}
|
||||
|
||||
// Helper function to implement the required interface for Sort.
|
||||
func (rankings Rankings) Swap(i, j int) {
|
||||
rankings[i], rankings[j] = rankings[j], rankings[i]
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ func BuildRandomSchedule(teams []Team, scheduleBlocks []ScheduleBlock, matchType
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// Returns the total number of matches that can be run within the given schedule blocks.
|
||||
func countMatches(scheduleBlocks []ScheduleBlock) int {
|
||||
numMatches := 0
|
||||
for _, block := range scheduleBlocks {
|
||||
|
||||
@@ -249,6 +249,7 @@ func getOfficialTeamInfo(teamId int) (*Team, error) {
|
||||
re := regexp.MustCompile("(?s).*<PRE>(.*)</PRE>.*")
|
||||
teamsCsv := re.FindStringSubmatch(string(body))[1]
|
||||
|
||||
// Parse the tab-separated data.
|
||||
reader := csv.NewReader(strings.NewReader(teamsCsv))
|
||||
reader.Comma = '\t'
|
||||
reader.FieldsPerRecord = -1
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
Copyright 2014 Team 254. All Rights Reserved.
|
||||
Author: nick@team254.com (Nick Eyre)
|
||||
*/
|
||||
|
||||
html {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
Copyright 2014 Team 254. All Rights Reserved.
|
||||
Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
*/
|
||||
|
||||
html {
|
||||
cursor: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
Copyright 2014 Team 254. All Rights Reserved.
|
||||
Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
*/
|
||||
|
||||
/* Bootstrap overrides. */
|
||||
.form-control[disabled] {
|
||||
cursor: default;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
Copyright 2014 Team 254. All Rights Reserved.
|
||||
Author: nick@team254.com (Nick Eyre)
|
||||
Copyright 2014 Team 254. All Rights Reserved.
|
||||
Author: nick@team254.com (Nick Eyre)
|
||||
Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
*/
|
||||
|
||||
html {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
Copyright 2014 Team 254. All Rights Reserved.
|
||||
Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
*/
|
||||
|
||||
html {
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
|
||||
@@ -8,6 +8,7 @@ var blinkInterval;
|
||||
var currentScreen = "blank";
|
||||
var websocket;
|
||||
|
||||
// Handles a websocket message to change which screen is displayed.
|
||||
var handleSetAllianceStationDisplay = function(targetScreen) {
|
||||
currentScreen = targetScreen;
|
||||
if (allianceStation == "") {
|
||||
@@ -28,6 +29,7 @@ var handleSetAllianceStationDisplay = function(targetScreen) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the team to display.
|
||||
var handleSetMatch = function(data) {
|
||||
if (allianceStation != "" && data.AllianceStation == "") {
|
||||
// The client knows better what display this should be; let the server know.
|
||||
@@ -52,6 +54,7 @@ var handleSetMatch = function(data) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the team connection status.
|
||||
var handleStatus = function(data) {
|
||||
stationStatus = data.AllianceStations[allianceStation];
|
||||
var blink = false;
|
||||
@@ -79,6 +82,7 @@ var handleStatus = function(data) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the match time countdown.
|
||||
var handleMatchTime = function(data) {
|
||||
translateMatchTime(data, function(matchState, matchStateText, countdownSec) {
|
||||
var countdownString = String(countdownSec % 60);
|
||||
@@ -91,11 +95,13 @@ var handleMatchTime = function(data) {
|
||||
});
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the match score.
|
||||
var handleRealtimeScore = function(data) {
|
||||
$("#redScore").text(data.RedScore);
|
||||
$("#blueScore").text(data.BlueScore);
|
||||
};
|
||||
|
||||
// Handles a websocket message to show or hide the hot goal indication.
|
||||
var handleHotGoalLight = function(side) {
|
||||
if (allianceStation != "" && (side == "left" && allianceStation[1] == "3" ||
|
||||
side == "right" && allianceStation[1] == "1")) {
|
||||
|
||||
@@ -7,6 +7,7 @@ var websocket;
|
||||
var teamTemplate = Handlebars.compile($("#teamTemplate").html());
|
||||
var matchResultTemplate = Handlebars.compile($("#matchResultTemplate").html());
|
||||
|
||||
// Handles a websocket message to hide the score dialog once the next match is being introduced.
|
||||
var handleSetAudienceDisplay = function(targetScreen) {
|
||||
// Hide the final results so that they aren't blocking the current teams when the announcer needs them most.
|
||||
if (targetScreen == "intro" || targetScreen == "match") {
|
||||
@@ -14,6 +15,7 @@ var handleSetAudienceDisplay = function(targetScreen) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the teams for the current match.
|
||||
var handleSetMatch = function(data) {
|
||||
$("#matchName").text(data.MatchType + " Match " + data.MatchDisplayName);
|
||||
$("#red1").html(teamTemplate(formatTeam(data.Red1)));
|
||||
@@ -24,6 +26,7 @@ var handleSetMatch = function(data) {
|
||||
$("#blue3").html(teamTemplate(formatTeam(data.Blue3)));
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the match time countdown.
|
||||
var handleMatchTime = function(data) {
|
||||
translateMatchTime(data, function(matchState, matchStateText, countdownSec) {
|
||||
$("#matchState").text(matchStateText);
|
||||
@@ -31,11 +34,13 @@ var handleMatchTime = function(data) {
|
||||
});
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the match score.
|
||||
var handleRealtimeScore = function(data) {
|
||||
$("#redScore").text(data.RedScore);
|
||||
$("#blueScore").text(data.BlueScore);
|
||||
};
|
||||
|
||||
// Handles a websocket message to populate the final score data.
|
||||
var handleSetFinalScore = function(data) {
|
||||
console.log(data);
|
||||
$("#scoreMatchName").text(data.MatchType + " Match " + data.MatchDisplayName);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2014 Team 254. All Rights Reserved.
|
||||
// Authors: pat@patfairbank.com (Patrick Fairbank)
|
||||
// nick@team254.com (Nick Eyre)
|
||||
// Author: pat@patfairbank.com (Patrick Fairbank)
|
||||
// Author: nick@team254.com (Nick Eyre)
|
||||
//
|
||||
// Client-side methods for the audience display.
|
||||
|
||||
@@ -9,6 +9,7 @@ var transitionMap;
|
||||
var currentScreen = "blank";
|
||||
var allianceSelectionTemplate = Handlebars.compile($("#allianceSelectionTemplate").html());
|
||||
|
||||
// Handles a websocket message to change which screen is displayed.
|
||||
var handleSetAudienceDisplay = function(targetScreen) {
|
||||
if (targetScreen == currentScreen) {
|
||||
return;
|
||||
@@ -26,6 +27,7 @@ var handleSetAudienceDisplay = function(targetScreen) {
|
||||
currentScreen = targetScreen;
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the teams for the current match.
|
||||
var handleSetMatch = function(data) {
|
||||
$("#redTeam1").text(data.Match.Red1)
|
||||
$("#redTeam2").text(data.Match.Red2)
|
||||
@@ -36,6 +38,7 @@ var handleSetMatch = function(data) {
|
||||
$("#matchName").text(data.MatchName + " " + data.Match.DisplayName);
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the match time countdown.
|
||||
var handleMatchTime = function(data) {
|
||||
translateMatchTime(data, function(matchState, matchStateText, countdownSec) {
|
||||
var countdownString = String(countdownSec % 60);
|
||||
@@ -47,6 +50,7 @@ var handleMatchTime = function(data) {
|
||||
});
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the match score.
|
||||
var handleRealtimeScore = function(data) {
|
||||
$("#redScoreNumber").text(data.RedScore);
|
||||
$("#redAssist1").attr("data-on", data.RedCycle.Assists >= 1);
|
||||
@@ -62,6 +66,7 @@ var handleRealtimeScore = function(data) {
|
||||
$("#blueCatch").attr("data-on", data.BlueCycle.Catch);
|
||||
};
|
||||
|
||||
// Handles a websocket message to populate the final score data.
|
||||
var handleSetFinalScore = function(data) {
|
||||
$("#redFinalScore").text(data.RedScore.Score);
|
||||
$("#redFinalTeam1").text(data.Match.Red1);
|
||||
@@ -80,6 +85,7 @@ var handleSetFinalScore = function(data) {
|
||||
$("#finalMatchName").text(data.MatchName + " " + data.Match.DisplayName);
|
||||
};
|
||||
|
||||
// Handles a websocket message to play a sound to signal match start/stop/etc.
|
||||
var handlePlaySound = function(sound) {
|
||||
$("audio").each(function(k, v) {
|
||||
// Stop and reset any sounds that are still playing.
|
||||
@@ -89,6 +95,7 @@ var handlePlaySound = function(sound) {
|
||||
$("#" + sound)[0].play();
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the alliance selection screen.
|
||||
var handleAllianceSelection = function(alliances) {
|
||||
if (alliances) {
|
||||
$.each(alliances, function(k, v) {
|
||||
@@ -98,6 +105,7 @@ var handleAllianceSelection = function(alliances) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a websocket message to populate and/or show/hide a lower third.
|
||||
var handleLowerThird = function(data) {
|
||||
if (data.BottomText == "") {
|
||||
$("#lowerThirdTop").hide();
|
||||
@@ -314,7 +322,7 @@ var transitionSponsorToScore = function(callback) {
|
||||
});
|
||||
};
|
||||
|
||||
// Load and Prioritize Sponsor Data
|
||||
// Loads sponsor slide data and builds the slideshow HTML.
|
||||
var initializeSponsorDisplay = function() {
|
||||
$.getJSON("/api/sponsor_slides", function(sponsors) {
|
||||
|
||||
@@ -355,7 +363,6 @@ var initializeSponsorDisplay = function() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$(function() {
|
||||
// Set up the websocket back to the server.
|
||||
websocket = new CheesyWebsocket("/displays/audience/websocket", {
|
||||
|
||||
@@ -20,7 +20,7 @@ var CheesyWebsocket = function(path, events) {
|
||||
}
|
||||
}
|
||||
|
||||
// Insert an event to allow the server to force-reload the client.
|
||||
// Insert an event to allow the server to force-reload the client for any display.
|
||||
events.reload = function(event) {
|
||||
location.reload();
|
||||
};
|
||||
@@ -43,4 +43,4 @@ var CheesyWebsocket = function(path, events) {
|
||||
};
|
||||
|
||||
this.connect();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
var websocket;
|
||||
|
||||
// Handles a websocket message to update the team connection status.
|
||||
var handleStatus = function(data) {
|
||||
// Update the team status view.
|
||||
$.each(data.AllianceStations, function(station, stationStatus) {
|
||||
|
||||
@@ -6,34 +6,42 @@
|
||||
var websocket;
|
||||
var scoreIsReady;
|
||||
|
||||
// Sends a websocket message to load a team into an alliance station.
|
||||
var substituteTeam = function(team, position) {
|
||||
websocket.send("substituteTeam", { team: parseInt(team), position: position })
|
||||
};
|
||||
|
||||
// Sends a websocket message to toggle the bypass status for an alliance station.
|
||||
var toggleBypass = function(station) {
|
||||
websocket.send("toggleBypass", station);
|
||||
};
|
||||
|
||||
// Sends a websocket message to start the match.
|
||||
var startMatch = function() {
|
||||
websocket.send("startMatch");
|
||||
};
|
||||
|
||||
// Sends a websocket message to abort the match.
|
||||
var abortMatch = function() {
|
||||
websocket.send("abortMatch");
|
||||
};
|
||||
|
||||
// Sends a websocket message to commit the match score and load the next match.
|
||||
var commitResults = function() {
|
||||
websocket.send("commitResults");
|
||||
};
|
||||
|
||||
// Sends a websocket message to discard the match score and load the next match.
|
||||
var discardResults = function() {
|
||||
websocket.send("discardResults");
|
||||
};
|
||||
|
||||
// Sends a websocket message to change what the audience display is showing.
|
||||
var setAudienceDisplay = function() {
|
||||
websocket.send("setAudienceDisplay", $("input[name=audienceDisplay]:checked").val());
|
||||
};
|
||||
|
||||
// Sends a websocket message to change what the alliance station display is showing.
|
||||
var setAllianceStationDisplay = function() {
|
||||
websocket.send("setAllianceStationDisplay", $("input[name=allianceStationDisplay]:checked").val());
|
||||
};
|
||||
@@ -49,6 +57,7 @@ var confirmCommit = function(isReplay) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the team connection status.
|
||||
var handleStatus = function(data) {
|
||||
// Update the team status view.
|
||||
$.each(data.AllianceStations, function(station, stationStatus) {
|
||||
@@ -115,6 +124,7 @@ var handleStatus = function(data) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the match time countdown.
|
||||
var handleMatchTime = function(data) {
|
||||
translateMatchTime(data, function(matchState, matchStateText, countdownSec) {
|
||||
$("#matchState").text(matchStateText);
|
||||
@@ -122,11 +132,13 @@ var handleMatchTime = function(data) {
|
||||
});
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the audience display screen selector.
|
||||
var handleSetAudienceDisplay = function(data) {
|
||||
$("input[name=audienceDisplay]:checked").prop("checked", false);
|
||||
$("input[name=audienceDisplay][value=" + data + "]").prop("checked", true);
|
||||
};
|
||||
|
||||
// Handles a websocket message to signal whether the referee and scorers have committed after the match.
|
||||
var handleScoringStatus = function(data) {
|
||||
scoreIsReady = data.RefereeScoreReady && data.RedScoreReady && data.BlueScoreReady;
|
||||
$("#refereeScoreStatus").attr("data-ready", data.RefereeScoreReady);
|
||||
@@ -134,6 +146,7 @@ var handleScoringStatus = function(data) {
|
||||
$("#blueScoreStatus").attr("data-ready", data.BlueScoreReady);
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the alliance station display screen selector.
|
||||
var handleSetAllianceStationDisplay = function(data) {
|
||||
$("input[name=allianceStationDisplay]:checked").prop("checked", false);
|
||||
$("input[name=allianceStationDisplay][value=" + data + "]").prop("checked", true);
|
||||
|
||||
@@ -14,10 +14,13 @@ var matchStates = {
|
||||
};
|
||||
var matchTiming;
|
||||
|
||||
// Handles a websocket message containing the length of each period in the match.
|
||||
var handleMatchTiming = function(data) {
|
||||
matchTiming = data;
|
||||
};
|
||||
|
||||
// Converts the raw match state and time into a human-readable state and per-period time. Calls the provided
|
||||
// callback with the result.
|
||||
var translateMatchTime = function(data, callback) {
|
||||
var matchStateText;
|
||||
switch (matchStates[data.MatchState]) {
|
||||
@@ -42,6 +45,7 @@ var translateMatchTime = function(data, callback) {
|
||||
callback(matchStates[data.MatchState], matchStateText, getCountdown(data.MatchState, data.MatchTimeSec));
|
||||
};
|
||||
|
||||
// Returns the per-period countdown for the given match state and overall time into the match.
|
||||
var getCountdown = function(matchState, matchTimeSec) {
|
||||
switch (matchStates[matchState]) {
|
||||
case "PRE_MATCH":
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
var websocket;
|
||||
|
||||
// Handles a websocket message to update the realtime scoring fields.
|
||||
var handleScore = function(data) {
|
||||
// Update autonomous period values.
|
||||
var score = data.CurrentScore;
|
||||
@@ -59,6 +60,7 @@ var handleScore = function(data) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a keyboard event and sends the appropriate websocket message.
|
||||
var handleKeyPress = function(event) {
|
||||
var key = String.fromCharCode(event.keyCode);
|
||||
switch(key) {
|
||||
@@ -110,6 +112,7 @@ var handleKeyPress = function(event) {
|
||||
}
|
||||
};
|
||||
|
||||
// Sends a websocket message to indicate that the score for this alliance is ready.
|
||||
var commitMatchScore = function() {
|
||||
websocket.send("commitMatch");
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
! Baseline configuration for the Catalyst 3500-series switch. Load this into the switch prior to configuring
|
||||
! Cheesy Arena to connect to it. Default password is 1234Five.
|
||||
!
|
||||
version 12.1
|
||||
no service pad
|
||||
|
||||
@@ -19,6 +19,7 @@ type TeamMatchLog struct {
|
||||
logFile *os.File
|
||||
}
|
||||
|
||||
// Creates a file to log to for the given match and team.
|
||||
func NewTeamMatchLog(teamId int, match *Match) (*TeamMatchLog, error) {
|
||||
err := os.MkdirAll(logsDir, 0755)
|
||||
if err != nil {
|
||||
@@ -39,6 +40,7 @@ func NewTeamMatchLog(teamId int, match *Match) (*TeamMatchLog, error) {
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// Adds a line to the log when a packet is received.
|
||||
func (log *TeamMatchLog) LogDsStatus(matchTimeSec float64, dsStatus *DriverStationStatus) {
|
||||
log.logger.Printf("%f,%d,%s,%v,%v,%v,%v,%f,%s,%d,%d,%d", matchTimeSec, dsStatus.TeamId,
|
||||
dsStatus.AllianceStation, dsStatus.RobotLinked, dsStatus.Auto, dsStatus.Enabled,
|
||||
|
||||
@@ -116,7 +116,8 @@
|
||||
<div class="col-lg-4 col-lg-offset-1 scoring-comment">Unscored balls</div>
|
||||
<div class="col-lg-2 scoring-comment" id="autoUnscoredBalls">0</div>
|
||||
</div>
|
||||
<h3 class="text-center scoring-message">Press Enter to commit autonomous score.<br />This cannot be undone.</h3>
|
||||
<h3 class="text-center scoring-message">Press Enter to commit autonomous score.<br />
|
||||
This cannot be undone.</h3>
|
||||
</div>
|
||||
<div id="teleopScore" style="display: none;">
|
||||
<h2>Current Cycle</h2>
|
||||
|
||||
@@ -109,14 +109,14 @@
|
||||
{{end}}
|
||||
{{define "script"}}
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("form.existing").each(function(index){
|
||||
$(function() {
|
||||
// Set up the toggling between specifying an image and specifying two lines of text.
|
||||
$("form.existing").each(function(index) {
|
||||
if (!$(this).find("input[name=image]").val().length && ($(this).find("input[name=line2]").val().length
|
||||
|| $(this).find("input[name=line1]").val().length)) {
|
||||
$(this).find(".imagetoggle").toggleClass("hidden");
|
||||
}
|
||||
});
|
||||
|
||||
$("button[name=toggleImage]").click(function(event) {
|
||||
event.preventDefault();
|
||||
$(this).parents("form").find(".imagetoggle").toggleClass("hidden");
|
||||
|
||||
7
web.go
7
web.go
@@ -37,7 +37,7 @@ var templateHelpers = template.FuncMap{
|
||||
},
|
||||
}
|
||||
|
||||
// Wraps the Gorilla Websocket module for convenience.
|
||||
// Wraps the Gorilla Websocket module so that we can define additional functions on it.
|
||||
type Websocket struct {
|
||||
conn *websocket.Conn
|
||||
}
|
||||
@@ -47,6 +47,7 @@ type WebsocketMessage struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// Upgrades the given HTTP request to a websocket connection.
|
||||
func NewWebsocket(w http.ResponseWriter, r *http.Request) (*Websocket, error) {
|
||||
conn, err := websocketUpgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
@@ -73,6 +74,7 @@ func (websocket *Websocket) WriteError(errorMessage string) error {
|
||||
return websocket.conn.WriteJSON(WebsocketMessage{"error", errorMessage})
|
||||
}
|
||||
|
||||
// Serves the root page of Cheesy Arena.
|
||||
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
template, err := template.ParseFiles("templates/index.html", "templates/base.html")
|
||||
if err != nil {
|
||||
@@ -89,6 +91,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Starts the webserver and blocks, waiting on requests. Does not return until the application exits.
|
||||
func ServeWebInterface() {
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/"))))
|
||||
http.Handle("/", newHandler())
|
||||
@@ -98,6 +101,7 @@ func ServeWebInterface() {
|
||||
http.ListenAndServe(fmt.Sprintf(":%d", httpPort), nil)
|
||||
}
|
||||
|
||||
// Sets up the mapping between URLs and handlers.
|
||||
func newHandler() http.Handler {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/setup/settings", SettingsGetHandler).Methods("GET")
|
||||
@@ -163,6 +167,7 @@ func newHandler() http.Handler {
|
||||
return router
|
||||
}
|
||||
|
||||
// Writes the given error out as plain text with a status code of 500.
|
||||
func handleWebErr(w http.ResponseWriter, err error) {
|
||||
http.Error(w, "Internal server error: "+err.Error(), 500)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user