From 9e73c8c6af42fc514d5224109a44ff4d18b0744d Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Mon, 30 Oct 2023 16:59:12 -0700 Subject: [PATCH 1/4] Don't use lineup from Nexus unless all positions are filled (fixes #162). --- partner/nexus.go | 7 +++++++ partner/nexus_test.go | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/partner/nexus.go b/partner/nexus.go index d00ef352..495288d2 100644 --- a/partner/nexus.go +++ b/partner/nexus.go @@ -61,6 +61,13 @@ func (client *NexusClient) GetLineup(tbaMatchKey model.TbaMatchKey) (*[6]int, er lineup[4], _ = strconv.Atoi(nexusLineup.Blue[1]) lineup[5], _ = strconv.Atoi(nexusLineup.Blue[2]) + // Check that each spot is filled with a valid team number; otherwise return an error. + for _, team := range lineup { + if team == 0 { + return nil, fmt.Errorf("Lineup not yet submitted") + } + } + return &lineup, err } diff --git a/partner/nexus_test.go b/partner/nexus_test.go index 22992743..0b9c5409 100644 --- a/partner/nexus_test.go +++ b/partner/nexus_test.go @@ -18,6 +18,10 @@ func TestGetLineup(t *testing.T) { assert.Contains(t, r.URL.String(), "/v1/my_event_code/") if strings.Contains(r.URL.String(), "/v1/my_event_code/p1/lineup") { w.Write([]byte("{\"red\":[\"101\",\"102\",\"103\"],\"blue\":[\"104\",\"105\",\"106\"]}")) + } else if strings.Contains(r.URL.String(), "/v1/my_event_code/p2/lineup") { + w.Write([]byte("{\"blue\":[\"104\",\"105\",\"106\"]}")) + } else if strings.Contains(r.URL.String(), "/v1/my_event_code/p3/lineup") { + w.Write([]byte("{}")) } else { http.Error(w, "Match not found", 404) } @@ -38,4 +42,18 @@ func TestGetLineup(t *testing.T) { if assert.NotNil(t, err) { assert.Contains(t, err.Error(), "Match not found") } + + tbaMatchKey = model.TbaMatchKey{CompLevel: "p", SetNumber: 0, MatchNumber: 2} + lineup, err = client.GetLineup(tbaMatchKey) + assert.Nil(t, lineup) + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "Lineup not yet submitted") + } + + tbaMatchKey = model.TbaMatchKey{CompLevel: "p", SetNumber: 0, MatchNumber: 3} + lineup, err = client.GetLineup(tbaMatchKey) + assert.Nil(t, lineup) + if assert.NotNil(t, err) { + assert.Contains(t, err.Error(), "Lineup not yet submitted") + } } From 87b03f27227f3e1a693ed7a841858c6b2e95c79d Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Sat, 4 Nov 2023 11:05:32 -0700 Subject: [PATCH 2/4] Don't redo network configuration after post-match preload. --- field/arena.go | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/field/arena.go b/field/arena.go index 4d6c7350..d0746ad9 100644 --- a/field/arena.go +++ b/field/arena.go @@ -8,6 +8,7 @@ package field import ( "fmt" "log" + "reflect" "time" "github.com/Team254/cheesy-arena/game" @@ -82,6 +83,7 @@ type Arena struct { matchAborted bool soundsPlayed map[*game.MatchSound]struct{} breakDescription string + preloadedTeams *[6]*model.Team } type AllianceStation struct { @@ -311,9 +313,17 @@ func (arena *Arena) LoadMatch(match *model.Match) error { return err } - arena.setupNetwork([6]*model.Team{arena.AllianceStations["R1"].Team, arena.AllianceStations["R2"].Team, - arena.AllianceStations["R3"].Team, arena.AllianceStations["B1"].Team, arena.AllianceStations["B2"].Team, - arena.AllianceStations["B3"].Team}) + arena.setupNetwork( + [6]*model.Team{ + arena.AllianceStations["R1"].Team, + arena.AllianceStations["R2"].Team, + arena.AllianceStations["R3"].Team, + arena.AllianceStations["B1"].Team, + arena.AllianceStations["B2"].Team, + arena.AllianceStations["B3"].Team, + }, + false, + ) } // Reset the arena state and realtime scores. @@ -404,9 +414,17 @@ func (arena *Arena) SubstituteTeams(red1, red2, red3, blue1, blue2, blue3 int) e arena.CurrentMatch.Blue1 = blue1 arena.CurrentMatch.Blue2 = blue2 arena.CurrentMatch.Blue3 = blue3 - arena.setupNetwork([6]*model.Team{arena.AllianceStations["R1"].Team, arena.AllianceStations["R2"].Team, - arena.AllianceStations["R3"].Team, arena.AllianceStations["B1"].Team, arena.AllianceStations["B2"].Team, - arena.AllianceStations["B3"].Team}) + arena.setupNetwork( + [6]*model.Team{ + arena.AllianceStations["R1"].Team, + arena.AllianceStations["R2"].Team, + arena.AllianceStations["R3"].Team, + arena.AllianceStations["B1"].Team, + arena.AllianceStations["B2"].Team, + arena.AllianceStations["B3"].Team, + }, + false, + ) arena.MatchLoadNotifier.Notify() if arena.CurrentMatch.Type != model.Test { @@ -790,11 +808,22 @@ func (arena *Arena) preLoadNextMatch() { log.Printf("Failed to get model for Team %d while pre-loading next match: %s", teamId, err.Error()) } } - arena.setupNetwork(teams) + arena.setupNetwork(teams, true) } // Asynchronously reconfigures the networking hardware for the new set of teams. -func (arena *Arena) setupNetwork(teams [6]*model.Team) { +func (arena *Arena) setupNetwork(teams [6]*model.Team, isPreload bool) { + if isPreload { + arena.preloadedTeams = &teams + } else if arena.preloadedTeams != nil { + preloadedTeams := *arena.preloadedTeams + arena.preloadedTeams = nil + if reflect.DeepEqual(teams, preloadedTeams) { + // Skip configuring the network; this is the same set of teams that was preloaded. + return + } + } + if arena.EventSettings.NetworkSecurityEnabled { if arena.EventSettings.Ap2TeamChannel == 0 { // Only one AP is being used. From ef2ac58429c4509c564a98e624e42bcf1315fd13 Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Sun, 5 Nov 2023 10:03:03 -0800 Subject: [PATCH 3/4] Distinguish AP numbers in logging. --- field/arena.go | 2 ++ network/access_point.go | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/field/arena.go b/field/arena.go index d0746ad9..a8aa7ed6 100644 --- a/field/arena.go +++ b/field/arena.go @@ -178,6 +178,7 @@ func (arena *Arena) LoadSettings() error { } arena.accessPoint.SetSettings( + 1, settings.ApType == "vivid", settings.ApAddress, settings.ApUsername, @@ -187,6 +188,7 @@ func (arena *Arena) LoadSettings() error { accessPoint1WifiStatuses, ) arena.accessPoint2.SetSettings( + 2, settings.ApType == "vivid", settings.Ap2Address, settings.Ap2Username, diff --git a/network/access_point.go b/network/access_point.go index f448259f..0d9aa230 100644 --- a/network/access_point.go +++ b/network/access_point.go @@ -31,6 +31,7 @@ const ( var accessPointInfoLines = []string{"ESSID: ", "Mode: ", "Tx-Power: ", "Signal: ", "Bit Rate: "} type AccessPoint struct { + apNumber int isVividType bool address string username string @@ -57,12 +58,14 @@ type sshOutput struct { } func (ap *AccessPoint) SetSettings( + apNumber int, isVividType bool, address, username, password string, teamChannel int, networkSecurityEnabled bool, wifiStatuses [6]*TeamWifiStatus, ) { + ap.apNumber = apNumber ap.isVividType = isVividType ap.address = address ap.username = username @@ -145,15 +148,15 @@ func (ap *AccessPoint) configureTeams(teams [6]*model.Team) { for teamIndex < 6 { config, err := ap.generateTeamAccessPointConfig(teams[teamIndex], teamIndex+1) if err != nil { - log.Printf("Failed to generate WiFi configuration: %v", err) + log.Printf("Failed to generate WiFi configuration for AP %d: %v", ap.apNumber, err) } command := addConfigurationHeader(config) - log.Printf("Configuring access point with command: %s\n", command) + log.Printf("Configuring AP %d with command: %s\n", ap.apNumber, command) _, err = ap.runCommand(command) if err != nil { - log.Printf("Error writing team configuration to AP: %v", err) + log.Printf("Error writing team configuration to AP %d: %v", ap.apNumber, err) retryCount++ time.Sleep(time.Second * accessPointConfigRetryIntervalSec) continue @@ -171,10 +174,12 @@ func (ap *AccessPoint) configureTeams(teams [6]*model.Team) { } err := ap.updateTeamWifiStatuses() if err == nil && ap.configIsCorrectForTeams(teams) { - log.Printf("Successfully configured WiFi after %d attempts.", retryCount) + log.Printf("Successfully configured AP %d Wi-Fi after %d attempts.", ap.apNumber, retryCount) break } - log.Printf("WiFi configuration still incorrect after %d attempts; trying again.", retryCount) + log.Printf( + "WiFi configuration still incorrect on AP %d after %d attempts; trying again.", ap.apNumber, retryCount, + ) } } @@ -209,7 +214,7 @@ func (ap *AccessPoint) updateTeamWifiStatuses() error { output, err := ap.runCommand("iwinfo") if err == nil { - logWifiInfo(output) + ap.logWifiInfo(output) err = ap.decodeWifiInfo(output) } @@ -291,7 +296,7 @@ func (ap *AccessPoint) generateTeamAccessPointConfig(team *model.Team, position } // Filters the given output from the "iwiinfo" command on the AP and logs the relevant parts. -func logWifiInfo(wifiInfo string) { +func (ap *AccessPoint) logWifiInfo(wifiInfo string) { lines := strings.Split(wifiInfo, "\n") var filteredLines []string for _, line := range lines { @@ -302,7 +307,7 @@ func logWifiInfo(wifiInfo string) { } } } - log.Printf("Access point status:\n%s\n", strings.Join(filteredLines, "\n")) + log.Printf("AP %d status:\n%s\n", ap.apNumber, strings.Join(filteredLines, "\n")) } // Parses the given output from the "iwinfo" command on the AP and updates the given status structure with the result. From f80283fa0de09b584c3e407d410d0927a3fba735 Mon Sep 17 00:00:00 2001 From: Ed Jordan Date: Sun, 5 Nov 2023 10:04:35 -0800 Subject: [PATCH 4/4] Add rio ping (#168) * Stack light Status Update stack light status to show disconnected robots during a match. * Rio Linked Add Rio ping to field monitor and logs to show no code status * fix Fixed Log viewer go. --- field/driver_station_connection.go | 3 +++ field/team_match_log.go | 10 ++++++---- static/css/field_monitor_display.css | 3 +++ static/js/field_monitor_display.js | 2 ++ templates/field_monitor_display.html | 4 ++-- templates/view_match_log.html | 4 +++- web/match_logs.go | 9 ++++++--- 7 files changed, 25 insertions(+), 10 deletions(-) diff --git a/field/driver_station_connection.go b/field/driver_station_connection.go index 0bc24e0a..2bd07a0e 100644 --- a/field/driver_station_connection.go +++ b/field/driver_station_connection.go @@ -37,6 +37,7 @@ type DriverStationConnection struct { Estop bool DsLinked bool RadioLinked bool + RioLinked bool RobotLinked bool BatteryVoltage float64 DsRobotTripTimeMs int @@ -100,6 +101,7 @@ func (arena *Arena) listenForDsUdpPackets() { dsConn.DsLinked = true dsConn.lastPacketTime = time.Now() + dsConn.RioLinked = data[3]&0x08 != 0 dsConn.RadioLinked = data[3]&0x10 != 0 dsConn.RobotLinked = data[3]&0x20 != 0 if dsConn.RobotLinked { @@ -122,6 +124,7 @@ func (dsConn *DriverStationConnection) update(arena *Arena) error { if time.Since(dsConn.lastPacketTime).Seconds() > driverStationUdpLinkTimeoutSec { dsConn.DsLinked = false dsConn.RadioLinked = false + dsConn.RioLinked = false dsConn.RobotLinked = false dsConn.BatteryVoltage = 0 } diff --git a/field/team_match_log.go b/field/team_match_log.go index 2071ca53..ed284e6f 100644 --- a/field/team_match_log.go +++ b/field/team_match_log.go @@ -7,12 +7,13 @@ package field import ( "fmt" - "github.com/Team254/cheesy-arena/model" - "github.com/Team254/cheesy-arena/network" "log" "os" "path/filepath" "time" + + "github.com/Team254/cheesy-arena/model" + "github.com/Team254/cheesy-arena/network" ) const logsDir = "static/logs" @@ -38,7 +39,7 @@ func NewTeamMatchLog(teamId int, match *model.Match, wifiStatus *network.TeamWif } log := TeamMatchLog{log.New(logFile, "", 0), logFile, wifiStatus} - log.logger.Println("matchTimeSec,packetType,teamId,allianceStation,dsLinked,radioLinked,robotLinked,auto,enabled," + + log.logger.Println("matchTimeSec,packetType,teamId,allianceStation,dsLinked,radioLinked,rioLinked,robotLinked,auto,enabled," + "emergencyStop,batteryVoltage,missedPacketCount,dsRobotTripTimeMs,rxRate,txRate,signalNoiseRatio") return &log, nil @@ -47,13 +48,14 @@ func NewTeamMatchLog(teamId int, match *model.Match, wifiStatus *network.TeamWif // Adds a line to the log when a packet is received. func (log *TeamMatchLog) LogDsPacket(matchTimeSec float64, packetType int, dsConn *DriverStationConnection) { log.logger.Printf( - "%f,%d,%d,%s,%v,%v,%v,%v,%v,%v,%f,%d,%d,%f,%f,%d", + "%f,%d,%d,%s,%v,%v,%v,%v,%v,%v,%v,%f,%d,%d,%f,%f,%d", matchTimeSec, packetType, dsConn.TeamId, dsConn.AllianceStation, dsConn.DsLinked, dsConn.RadioLinked, + dsConn.RioLinked, dsConn.RobotLinked, dsConn.Auto, dsConn.Enabled, diff --git a/static/css/field_monitor_display.css b/static/css/field_monitor_display.css index fcb49e91..afa1ca2b 100644 --- a/static/css/field_monitor_display.css +++ b/static/css/field_monitor_display.css @@ -94,6 +94,9 @@ body { background-color: #00ff00; color: #333; } +.team-id[data-status=rio-linked], .team-notes[data-status=rio-linked] { + background-color: #00cc00; +} .team-id[data-status=radio-linked], .team-notes[data-status=radio-linked] { background-color: #AA3377; } diff --git a/static/js/field_monitor_display.js b/static/js/field_monitor_display.js index e81ccef5..9ab7f801 100644 --- a/static/js/field_monitor_display.js +++ b/static/js/field_monitor_display.js @@ -51,6 +51,8 @@ var handleArenaStatus = function(data) { status = "wrong-station"; } else if (stationStatus.DsConn.RobotLinked) { status = "robot-linked"; + } else if (stationStatus.DsConn.RioLinked) { + status = "rio-linked"; } else if (stationStatus.DsConn.RadioLinked) { status = "radio-linked"; } else if (stationStatus.DsConn.DsLinked) { diff --git a/templates/field_monitor_display.html b/templates/field_monitor_display.html index dab23aaa..270c84d7 100644 --- a/templates/field_monitor_display.html +++ b/templates/field_monitor_display.html @@ -27,8 +27,8 @@ {{template "row" dict "leftPosition" "3" "rightPosition" "1"}}
-
-
+
+
diff --git a/templates/view_match_log.html b/templates/view_match_log.html index cb924ffd..7f1e9ca9 100644 --- a/templates/view_match_log.html +++ b/templates/view_match_log.html @@ -36,6 +36,7 @@

{{.Match.ShortName}} - {{ .MatchLogs.TeamId}}({{.MatchLogs.AllianceStation}} Match Time DS Linked Radio Linked + Rio Linked Robot Linked Mode Enabled @@ -54,7 +55,8 @@

{{.Match.ShortName}} - {{ .MatchLogs.TeamId}}({{.MatchLogs.AllianceStation}} {{printf "%.2f" $row.MatchTimeSec}} {{$row.DsLinked}} {{if $row.DsLinked}}{{$row.RadioLinked}}{{else}}*****{{end}} - {{if and $row.DsLinked $row.RadioLinked}}{{$row.RobotLinked}}{{else}}*****{{end}} + {{if and $row.DsLinked $row.RadioLinked}}{{$row.RioLinked}}{{else}}*****{{end}} + {{if and $row.DsLinked $row.RadioLinked $row.RioLinked}}{{$row.RobotLinked}}{{else}}*****{{end}} {{if $row.Auto}}Auto{{else}}Telop{{end}} {{$row.Enabled}} {{$row.EmergencyStop}} diff --git a/web/match_logs.go b/web/match_logs.go index 945fb3d9..56cbe43c 100644 --- a/web/match_logs.go +++ b/web/match_logs.go @@ -8,13 +8,14 @@ package web import ( "encoding/csv" "fmt" - "github.com/Team254/cheesy-arena/game" - "github.com/Team254/cheesy-arena/model" - "github.com/gorilla/mux" "net/http" "os" "path/filepath" "strconv" + + "github.com/Team254/cheesy-arena/game" + "github.com/Team254/cheesy-arena/model" + "github.com/gorilla/mux" ) type MatchLogsListItem struct { @@ -34,6 +35,7 @@ type MatchLogRow struct { AllianceStation string DsLinked bool RadioLinked bool + RioLinked bool RobotLinked bool Auto bool Enabled bool @@ -201,6 +203,7 @@ func (web *Web) getMatchLogFromRequest(r *http.Request) (*model.Match, *MatchLog curRow.AllianceStation = record[headerMap["allianceStation"]] curRow.DsLinked, _ = strconv.ParseBool(record[headerMap["dsLinked"]]) curRow.RadioLinked, _ = strconv.ParseBool(record[headerMap["radioLinked"]]) + curRow.RioLinked, _ = strconv.ParseBool(record[headerMap["rioLinked"]]) curRow.RobotLinked, _ = strconv.ParseBool(record[headerMap["robotLinked"]]) curRow.Auto, _ = strconv.ParseBool(record[headerMap["auto"]]) curRow.Enabled, _ = strconv.ParseBool(record[headerMap["enabled"]])