2014-07-02 00:11:57 -07:00
|
|
|
// Copyright 2014 Team 254. All Rights Reserved.
|
|
|
|
|
// Author: pat@patfairbank.com (Patrick Fairbank)
|
|
|
|
|
//
|
|
|
|
|
// Functions for controlling the arena and match play.
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
2014-07-04 16:55:29 -07:00
|
|
|
import (
|
|
|
|
|
"fmt"
|
2014-07-10 23:03:03 -07:00
|
|
|
"log"
|
2014-08-18 20:23:30 -07:00
|
|
|
"math/rand"
|
2014-07-04 16:55:29 -07:00
|
|
|
"time"
|
|
|
|
|
)
|
2014-07-02 00:11:57 -07:00
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
const (
|
2014-08-08 14:50:40 -06:00
|
|
|
arenaLoopPeriodMs = 10
|
|
|
|
|
dsPacketPeriodMs = 250
|
|
|
|
|
matchEndScoreDwellSec = 3
|
2014-07-06 00:34:40 -07:00
|
|
|
)
|
2014-07-04 16:55:29 -07:00
|
|
|
|
|
|
|
|
// Progression of match states.
|
|
|
|
|
const (
|
|
|
|
|
PRE_MATCH = iota
|
|
|
|
|
START_MATCH
|
|
|
|
|
AUTO_PERIOD
|
|
|
|
|
PAUSE_PERIOD
|
|
|
|
|
TELEOP_PERIOD
|
|
|
|
|
ENDGAME_PERIOD
|
|
|
|
|
POST_MATCH
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type AllianceStation struct {
|
2014-07-06 00:34:40 -07:00
|
|
|
DsConn *DriverStationConnection
|
|
|
|
|
EmergencyStop bool
|
|
|
|
|
Bypass bool
|
|
|
|
|
team *Team
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
|
2014-07-10 23:03:03 -07:00
|
|
|
// Match period timings.
|
|
|
|
|
type MatchTiming struct {
|
|
|
|
|
AutoDurationSec int
|
|
|
|
|
PauseDurationSec int
|
|
|
|
|
TeleopDurationSec int
|
|
|
|
|
EndgameTimeLeftSec int
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-29 17:07:30 -07:00
|
|
|
type RealtimeScore struct {
|
|
|
|
|
CurrentScore Score
|
|
|
|
|
CurrentCycle Cycle
|
|
|
|
|
AutoPreloadedBalls int
|
2014-08-02 19:43:45 -07:00
|
|
|
AutoLeftoverBalls int
|
2014-07-31 19:43:02 -07:00
|
|
|
Fouls []Foul
|
2014-08-20 03:07:34 -07:00
|
|
|
Cards map[string]string
|
2014-07-29 17:07:30 -07:00
|
|
|
AutoCommitted bool
|
|
|
|
|
TeleopCommitted bool
|
2014-07-31 19:43:02 -07:00
|
|
|
FoulsCommitted bool
|
2014-07-29 17:07:30 -07:00
|
|
|
undoAutoScores []Score
|
|
|
|
|
undoCycles []Cycle
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-04 16:55:29 -07:00
|
|
|
type Arena struct {
|
2014-08-08 12:39:08 -07:00
|
|
|
AllianceStations map[string]*AllianceStation
|
|
|
|
|
MatchState int
|
|
|
|
|
CanStartMatch bool
|
|
|
|
|
matchTiming MatchTiming
|
|
|
|
|
currentMatch *Match
|
|
|
|
|
redRealtimeScore *RealtimeScore
|
|
|
|
|
blueRealtimeScore *RealtimeScore
|
|
|
|
|
matchStartTime time.Time
|
|
|
|
|
lastDsPacketTime time.Time
|
|
|
|
|
matchStateNotifier *Notifier
|
|
|
|
|
matchTimeNotifier *Notifier
|
|
|
|
|
robotStatusNotifier *Notifier
|
|
|
|
|
matchLoadTeamsNotifier *Notifier
|
|
|
|
|
scoringStatusNotifier *Notifier
|
|
|
|
|
realtimeScoreNotifier *Notifier
|
|
|
|
|
scorePostedNotifier *Notifier
|
|
|
|
|
audienceDisplayNotifier *Notifier
|
|
|
|
|
playSoundNotifier *Notifier
|
|
|
|
|
allianceStationDisplayNotifier *Notifier
|
2014-08-10 22:05:31 -06:00
|
|
|
allianceSelectionNotifier *Notifier
|
2014-08-15 20:13:02 -07:00
|
|
|
lowerThirdNotifier *Notifier
|
2014-08-08 12:39:08 -07:00
|
|
|
audienceDisplayScreen string
|
|
|
|
|
allianceStationDisplays map[string]string
|
|
|
|
|
allianceStationDisplayScreen string
|
|
|
|
|
lastMatchState int
|
|
|
|
|
lastMatchTimeSec float64
|
|
|
|
|
savedMatch *Match
|
|
|
|
|
savedMatchResult *MatchResult
|
2014-08-18 20:23:30 -07:00
|
|
|
leftGoalHotFirst bool
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mainArena Arena // Named thusly to avoid polluting the global namespace with something more generic.
|
|
|
|
|
|
2014-08-20 03:07:34 -07:00
|
|
|
func NewRealtimeScore() *RealtimeScore {
|
|
|
|
|
realtimeScore := new(RealtimeScore)
|
|
|
|
|
realtimeScore.Cards = make(map[string]string)
|
|
|
|
|
return realtimeScore
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-04 16:55:29 -07:00
|
|
|
// Sets the arena to its initial state.
|
|
|
|
|
func (arena *Arena) Setup() {
|
2014-07-10 23:03:03 -07:00
|
|
|
arena.matchTiming.AutoDurationSec = 10
|
|
|
|
|
arena.matchTiming.PauseDurationSec = 1
|
|
|
|
|
arena.matchTiming.TeleopDurationSec = 140
|
|
|
|
|
arena.matchTiming.EndgameTimeLeftSec = 30
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.AllianceStations = make(map[string]*AllianceStation)
|
|
|
|
|
arena.AllianceStations["R1"] = new(AllianceStation)
|
|
|
|
|
arena.AllianceStations["R2"] = new(AllianceStation)
|
|
|
|
|
arena.AllianceStations["R3"] = new(AllianceStation)
|
|
|
|
|
arena.AllianceStations["B1"] = new(AllianceStation)
|
|
|
|
|
arena.AllianceStations["B2"] = new(AllianceStation)
|
|
|
|
|
arena.AllianceStations["B3"] = new(AllianceStation)
|
2014-07-04 16:55:29 -07:00
|
|
|
|
2014-07-27 16:41:09 -07:00
|
|
|
arena.matchStateNotifier = NewNotifier()
|
|
|
|
|
arena.matchTimeNotifier = NewNotifier()
|
|
|
|
|
arena.robotStatusNotifier = NewNotifier()
|
|
|
|
|
arena.matchLoadTeamsNotifier = NewNotifier()
|
2014-08-03 17:59:59 -07:00
|
|
|
arena.scoringStatusNotifier = NewNotifier()
|
2014-08-02 19:43:45 -07:00
|
|
|
arena.realtimeScoreNotifier = NewNotifier()
|
2014-07-27 16:41:09 -07:00
|
|
|
arena.scorePostedNotifier = NewNotifier()
|
2014-08-02 19:43:45 -07:00
|
|
|
arena.audienceDisplayNotifier = NewNotifier()
|
2014-08-02 23:23:22 -07:00
|
|
|
arena.playSoundNotifier = NewNotifier()
|
2014-08-08 12:39:08 -07:00
|
|
|
arena.allianceStationDisplayNotifier = NewNotifier()
|
2014-08-10 22:05:31 -06:00
|
|
|
arena.allianceSelectionNotifier = NewNotifier()
|
2014-08-15 20:13:02 -07:00
|
|
|
arena.lowerThirdNotifier = NewNotifier()
|
2014-07-27 16:41:09 -07:00
|
|
|
|
2014-07-04 16:55:29 -07:00
|
|
|
// Load empty match as current.
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.MatchState = PRE_MATCH
|
|
|
|
|
arena.LoadTestMatch()
|
2014-07-10 23:03:03 -07:00
|
|
|
arena.lastMatchState = -1
|
|
|
|
|
arena.lastMatchTimeSec = 0
|
2014-08-02 19:43:45 -07:00
|
|
|
|
|
|
|
|
// Initialize display parameters.
|
|
|
|
|
arena.audienceDisplayScreen = "blank"
|
|
|
|
|
arena.savedMatch = &Match{}
|
|
|
|
|
arena.savedMatchResult = &MatchResult{}
|
2014-08-04 00:52:46 -07:00
|
|
|
arena.allianceStationDisplays = make(map[string]string)
|
2014-08-08 12:39:08 -07:00
|
|
|
arena.allianceStationDisplayScreen = "blank"
|
2014-08-18 20:23:30 -07:00
|
|
|
|
|
|
|
|
SetupLights()
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loads a team into an alliance station, cleaning up the previous team there if there is one.
|
|
|
|
|
func (arena *Arena) AssignTeam(teamId int, station string) error {
|
|
|
|
|
// Reject invalid station values.
|
2014-07-06 00:34:40 -07:00
|
|
|
if _, ok := arena.AllianceStations[station]; !ok {
|
2014-07-04 16:55:29 -07:00
|
|
|
return fmt.Errorf("Invalid alliance station '%s'.", station)
|
|
|
|
|
}
|
2014-07-04 17:47:28 -07:00
|
|
|
|
2014-07-04 16:55:29 -07:00
|
|
|
// Do nothing if the station is already assigned to the requested team.
|
2014-07-06 00:34:40 -07:00
|
|
|
dsConn := arena.AllianceStations[station].DsConn
|
2014-07-04 16:55:29 -07:00
|
|
|
if dsConn != nil && dsConn.TeamId == teamId {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if dsConn != nil {
|
|
|
|
|
err := dsConn.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.AllianceStations[station].team = nil
|
|
|
|
|
arena.AllianceStations[station].DsConn = nil
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Leave the station empty if the team number is zero.
|
|
|
|
|
if teamId == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load the team model. Raise an error if a team doesn't exist.
|
|
|
|
|
team, err := db.GetTeamById(teamId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if team == nil {
|
|
|
|
|
return fmt.Errorf("Invalid team number '%d'.", teamId)
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.AllianceStations[station].team = team
|
|
|
|
|
arena.AllianceStations[station].DsConn, err = NewDriverStationConnection(team.Id, station)
|
2014-07-04 16:55:29 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sets up the arena for the given match.
|
|
|
|
|
func (arena *Arena) LoadMatch(match *Match) error {
|
2014-07-06 00:34:40 -07:00
|
|
|
if arena.MatchState != PRE_MATCH {
|
2014-07-04 16:55:29 -07:00
|
|
|
return fmt.Errorf("Cannot load match while there is a match still in progress or with results pending.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
arena.currentMatch = match
|
|
|
|
|
err := arena.AssignTeam(match.Red1, "R1")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = arena.AssignTeam(match.Red2, "R2")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = arena.AssignTeam(match.Red3, "R3")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = arena.AssignTeam(match.Blue1, "B1")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = arena.AssignTeam(match.Blue2, "B2")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = arena.AssignTeam(match.Blue3, "B3")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2014-07-29 17:07:30 -07:00
|
|
|
|
2014-08-15 22:47:12 -07:00
|
|
|
arena.SetupNetwork()
|
|
|
|
|
|
2014-07-29 17:07:30 -07:00
|
|
|
// Reset the realtime scores.
|
2014-08-20 03:07:34 -07:00
|
|
|
arena.redRealtimeScore = NewRealtimeScore()
|
|
|
|
|
arena.blueRealtimeScore = NewRealtimeScore()
|
2014-07-29 17:07:30 -07:00
|
|
|
|
2014-07-27 16:41:09 -07:00
|
|
|
arena.matchLoadTeamsNotifier.Notify(nil)
|
2014-08-02 19:43:45 -07:00
|
|
|
arena.realtimeScoreNotifier.Notify(nil)
|
2014-07-04 16:55:29 -07:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
// Sets a new test match as the current match.
|
|
|
|
|
func (arena *Arena) LoadTestMatch() error {
|
|
|
|
|
return arena.LoadMatch(&Match{Type: "test"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loads the first unplayed match of the current match type.
|
|
|
|
|
func (arena *Arena) LoadNextMatch() error {
|
|
|
|
|
if arena.currentMatch.Type == "test" {
|
|
|
|
|
return arena.LoadTestMatch()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
matches, err := db.GetMatchesByType(arena.currentMatch.Type)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for _, match := range matches {
|
|
|
|
|
if match.Status != "complete" {
|
|
|
|
|
err = arena.LoadMatch(&match)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Assigns the given team to the given station, also substituting it into the match record.
|
|
|
|
|
func (arena *Arena) SubstituteTeam(teamId int, station string) error {
|
2014-08-20 23:13:19 -07:00
|
|
|
if arena.currentMatch.Type == "qualification" {
|
|
|
|
|
return fmt.Errorf("Can't substitute teams for qualification matches.")
|
2014-07-06 00:34:40 -07:00
|
|
|
}
|
|
|
|
|
err := arena.AssignTeam(teamId, station)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
switch station {
|
|
|
|
|
case "R1":
|
|
|
|
|
arena.currentMatch.Red1 = teamId
|
|
|
|
|
case "R2":
|
|
|
|
|
arena.currentMatch.Red2 = teamId
|
|
|
|
|
case "R3":
|
|
|
|
|
arena.currentMatch.Red3 = teamId
|
|
|
|
|
case "B1":
|
|
|
|
|
arena.currentMatch.Blue1 = teamId
|
|
|
|
|
case "B2":
|
|
|
|
|
arena.currentMatch.Blue2 = teamId
|
|
|
|
|
case "B3":
|
|
|
|
|
arena.currentMatch.Blue3 = teamId
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
2014-08-15 22:47:12 -07:00
|
|
|
arena.SetupNetwork()
|
2014-07-27 16:41:09 -07:00
|
|
|
arena.matchLoadTeamsNotifier.Notify(nil)
|
2014-07-06 00:34:40 -07:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-15 22:47:12 -07:00
|
|
|
// Asynchronously reconfigures the networking hardware for the new set of teams.
|
|
|
|
|
func (arena *Arena) SetupNetwork() {
|
|
|
|
|
if eventSettings.NetworkSecurityEnabled {
|
|
|
|
|
go func() {
|
|
|
|
|
err := ConfigureTeamWifi(arena.AllianceStations["R1"].team, arena.AllianceStations["R2"].team,
|
|
|
|
|
arena.AllianceStations["R3"].team, arena.AllianceStations["B1"].team,
|
|
|
|
|
arena.AllianceStations["B2"].team, arena.AllianceStations["B3"].team)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Failed to configure team WiFi: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
}()
|
2014-08-16 12:31:46 -07:00
|
|
|
go func() {
|
|
|
|
|
err := ConfigureTeamEthernet(arena.AllianceStations["R1"].team, arena.AllianceStations["R2"].team,
|
|
|
|
|
arena.AllianceStations["R3"].team, arena.AllianceStations["B1"].team,
|
|
|
|
|
arena.AllianceStations["B2"].team, arena.AllianceStations["B3"].team)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Failed to configure team Ethernet: %s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
}()
|
2014-08-15 22:47:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
// Returns nil if the match can be started, and an error otherwise.
|
|
|
|
|
func (arena *Arena) CheckCanStartMatch() error {
|
|
|
|
|
if arena.MatchState != PRE_MATCH {
|
|
|
|
|
return fmt.Errorf("Cannot start match while there is a match still in progress or with results pending.")
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
2014-07-06 00:34:40 -07:00
|
|
|
for _, allianceStation := range arena.AllianceStations {
|
|
|
|
|
if allianceStation.EmergencyStop {
|
2014-07-04 17:47:28 -07:00
|
|
|
return fmt.Errorf("Cannot start match while an emergency stop is active.")
|
|
|
|
|
}
|
2014-07-06 00:34:40 -07:00
|
|
|
if !allianceStation.Bypass {
|
|
|
|
|
if allianceStation.DsConn == nil || !allianceStation.DsConn.DriverStationStatus.RobotLinked {
|
2014-07-04 17:47:28 -07:00
|
|
|
return fmt.Errorf("Cannot start match until all robots are connected or bypassed.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-07-06 00:34:40 -07:00
|
|
|
return nil
|
|
|
|
|
}
|
2014-07-04 17:47:28 -07:00
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
// Starts the match if all conditions are met.
|
|
|
|
|
func (arena *Arena) StartMatch() error {
|
|
|
|
|
err := arena.CheckCanStartMatch()
|
|
|
|
|
if err == nil {
|
2014-08-21 00:18:32 -07:00
|
|
|
// Save the match start time to the database for posterity.
|
|
|
|
|
arena.currentMatch.StartedAt = time.Now()
|
|
|
|
|
if arena.currentMatch.Type != "test" {
|
|
|
|
|
db.SaveMatch(arena.currentMatch)
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.MatchState = START_MATCH
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Kills the current match if it is underway.
|
|
|
|
|
func (arena *Arena) AbortMatch() error {
|
|
|
|
|
if arena.MatchState == PRE_MATCH || arena.MatchState == POST_MATCH {
|
|
|
|
|
return fmt.Errorf("Cannot abort match when it is not in progress.")
|
|
|
|
|
}
|
|
|
|
|
arena.MatchState = POST_MATCH
|
2014-08-02 19:43:45 -07:00
|
|
|
arena.audienceDisplayScreen = "blank"
|
|
|
|
|
arena.audienceDisplayNotifier.Notify(nil)
|
2014-08-02 23:23:22 -07:00
|
|
|
arena.playSoundNotifier.Notify("match-abort")
|
2014-07-04 16:55:29 -07:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clears out the match and resets the arena state unless there is a match underway.
|
|
|
|
|
func (arena *Arena) ResetMatch() error {
|
2014-07-06 00:34:40 -07:00
|
|
|
if arena.MatchState != POST_MATCH && arena.MatchState != PRE_MATCH {
|
2014-07-04 16:55:29 -07:00
|
|
|
return fmt.Errorf("Cannot reset match while it is in progress.")
|
|
|
|
|
}
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.MatchState = PRE_MATCH
|
|
|
|
|
arena.AllianceStations["R1"].Bypass = false
|
|
|
|
|
arena.AllianceStations["R2"].Bypass = false
|
|
|
|
|
arena.AllianceStations["R3"].Bypass = false
|
|
|
|
|
arena.AllianceStations["B1"].Bypass = false
|
|
|
|
|
arena.AllianceStations["B2"].Bypass = false
|
|
|
|
|
arena.AllianceStations["B3"].Bypass = false
|
2014-07-04 16:55:29 -07:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-10 23:03:03 -07:00
|
|
|
// Returns the fractional number of seconds since the start of the match.
|
|
|
|
|
func (arena *Arena) MatchTimeSec() float64 {
|
|
|
|
|
if arena.MatchState == PRE_MATCH || arena.MatchState == START_MATCH || arena.MatchState == POST_MATCH {
|
|
|
|
|
return 0
|
|
|
|
|
} else {
|
|
|
|
|
return time.Since(arena.matchStartTime).Seconds()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-04 16:55:29 -07:00
|
|
|
// Performs a single iteration of checking inputs and timers and setting outputs accordingly to control the
|
|
|
|
|
// flow of a match.
|
|
|
|
|
func (arena *Arena) Update() {
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.CanStartMatch = arena.CheckCanStartMatch() == nil
|
|
|
|
|
|
2014-07-04 16:55:29 -07:00
|
|
|
// Decide what state the robots need to be in, depending on where we are in the match.
|
|
|
|
|
auto := false
|
|
|
|
|
enabled := false
|
|
|
|
|
sendDsPacket := false
|
|
|
|
|
matchTimeSec := arena.MatchTimeSec()
|
2014-07-06 00:34:40 -07:00
|
|
|
switch arena.MatchState {
|
2014-07-04 16:55:29 -07:00
|
|
|
case PRE_MATCH:
|
|
|
|
|
auto = true
|
|
|
|
|
enabled = false
|
|
|
|
|
case START_MATCH:
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.MatchState = AUTO_PERIOD
|
2014-07-04 16:55:29 -07:00
|
|
|
arena.matchStartTime = time.Now()
|
2014-07-10 23:03:03 -07:00
|
|
|
arena.lastMatchTimeSec = -1
|
2014-08-18 20:23:30 -07:00
|
|
|
arena.leftGoalHotFirst = rand.Intn(2) == 1
|
2014-07-04 16:55:29 -07:00
|
|
|
auto = true
|
|
|
|
|
enabled = true
|
|
|
|
|
sendDsPacket = true
|
2014-08-02 19:43:45 -07:00
|
|
|
arena.audienceDisplayScreen = "match"
|
|
|
|
|
arena.audienceDisplayNotifier.Notify(nil)
|
2014-08-02 23:23:22 -07:00
|
|
|
arena.playSoundNotifier.Notify("match-start")
|
2014-07-04 16:55:29 -07:00
|
|
|
case AUTO_PERIOD:
|
|
|
|
|
auto = true
|
|
|
|
|
enabled = true
|
2014-07-10 23:03:03 -07:00
|
|
|
if matchTimeSec >= float64(arena.matchTiming.AutoDurationSec) {
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.MatchState = PAUSE_PERIOD
|
2014-07-04 16:55:29 -07:00
|
|
|
auto = false
|
|
|
|
|
enabled = false
|
|
|
|
|
sendDsPacket = true
|
2014-08-02 23:23:22 -07:00
|
|
|
arena.playSoundNotifier.Notify("match-end")
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
case PAUSE_PERIOD:
|
|
|
|
|
auto = false
|
|
|
|
|
enabled = false
|
2014-07-10 23:03:03 -07:00
|
|
|
if matchTimeSec >= float64(arena.matchTiming.AutoDurationSec+arena.matchTiming.PauseDurationSec) {
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.MatchState = TELEOP_PERIOD
|
2014-07-04 16:55:29 -07:00
|
|
|
auto = false
|
|
|
|
|
enabled = true
|
|
|
|
|
sendDsPacket = true
|
2014-08-02 23:23:22 -07:00
|
|
|
arena.playSoundNotifier.Notify("match-resume")
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
case TELEOP_PERIOD:
|
|
|
|
|
auto = false
|
|
|
|
|
enabled = true
|
2014-07-10 23:03:03 -07:00
|
|
|
if matchTimeSec >= float64(arena.matchTiming.AutoDurationSec+arena.matchTiming.PauseDurationSec+
|
|
|
|
|
arena.matchTiming.TeleopDurationSec-arena.matchTiming.EndgameTimeLeftSec) {
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.MatchState = ENDGAME_PERIOD
|
2014-07-04 16:55:29 -07:00
|
|
|
sendDsPacket = false
|
2014-08-02 23:23:22 -07:00
|
|
|
arena.playSoundNotifier.Notify("match-endgame")
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
case ENDGAME_PERIOD:
|
|
|
|
|
auto = false
|
|
|
|
|
enabled = true
|
2014-07-10 23:03:03 -07:00
|
|
|
if matchTimeSec >= float64(arena.matchTiming.AutoDurationSec+arena.matchTiming.PauseDurationSec+
|
|
|
|
|
arena.matchTiming.TeleopDurationSec) {
|
2014-07-06 00:34:40 -07:00
|
|
|
arena.MatchState = POST_MATCH
|
2014-07-04 16:55:29 -07:00
|
|
|
auto = false
|
|
|
|
|
enabled = false
|
|
|
|
|
sendDsPacket = true
|
2014-08-08 14:50:40 -06:00
|
|
|
go func() {
|
|
|
|
|
// Leave the scores on the screen briefly at the end of the match.
|
|
|
|
|
time.Sleep(time.Second * matchEndScoreDwellSec)
|
|
|
|
|
arena.audienceDisplayScreen = "blank"
|
|
|
|
|
arena.audienceDisplayNotifier.Notify(nil)
|
|
|
|
|
}()
|
2014-08-02 23:23:22 -07:00
|
|
|
arena.playSoundNotifier.Notify("match-end")
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-10 23:03:03 -07:00
|
|
|
// Send a notification if the match state has changed.
|
|
|
|
|
if arena.MatchState != arena.lastMatchState {
|
|
|
|
|
arena.matchStateNotifier.Notify(arena.MatchState)
|
|
|
|
|
}
|
|
|
|
|
arena.lastMatchState = arena.MatchState
|
|
|
|
|
|
|
|
|
|
// Send a match tick notification if passing an integer second threshold.
|
|
|
|
|
if int(matchTimeSec) != int(arena.lastMatchTimeSec) {
|
|
|
|
|
arena.matchTimeNotifier.Notify(int(matchTimeSec))
|
|
|
|
|
}
|
|
|
|
|
arena.lastMatchTimeSec = matchTimeSec
|
|
|
|
|
|
2014-07-04 16:55:29 -07:00
|
|
|
// 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)
|
2014-07-10 23:03:03 -07:00
|
|
|
|
|
|
|
|
// TODO(pat): Come up with better criteria for sending robot status updates.
|
|
|
|
|
arena.robotStatusNotifier.Notify(nil)
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
2014-08-18 20:23:30 -07:00
|
|
|
|
|
|
|
|
arena.handleLighting("red", arena.redRealtimeScore)
|
|
|
|
|
arena.handleLighting("blue", arena.blueRealtimeScore)
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loops indefinitely to track and update the arena components.
|
|
|
|
|
func (arena *Arena) Run() {
|
|
|
|
|
for {
|
|
|
|
|
arena.Update()
|
|
|
|
|
time.Sleep(time.Millisecond * arenaLoopPeriodMs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (arena *Arena) sendDsPacket(auto bool, enabled bool) {
|
2014-07-06 00:34:40 -07:00
|
|
|
for _, allianceStation := range arena.AllianceStations {
|
|
|
|
|
if allianceStation.DsConn != nil {
|
|
|
|
|
allianceStation.DsConn.Auto = auto
|
|
|
|
|
allianceStation.DsConn.Enabled = enabled && !allianceStation.EmergencyStop && !allianceStation.Bypass
|
|
|
|
|
err := allianceStation.DsConn.Update()
|
2014-07-04 16:55:29 -07:00
|
|
|
if err != nil {
|
2014-07-10 23:03:03 -07:00
|
|
|
log.Printf("Unable to send driver station packet for team %d.", allianceStation.team.Id)
|
2014-07-04 16:55:29 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
arena.lastDsPacketTime = time.Now()
|
|
|
|
|
}
|
2014-08-02 19:43:45 -07:00
|
|
|
|
2014-08-03 21:19:24 -07:00
|
|
|
func (realtimeScore *RealtimeScore) Score(opponentFouls []Foul) int {
|
|
|
|
|
score := scoreSummary(&realtimeScore.CurrentScore, opponentFouls).Score
|
2014-08-02 19:43:45 -07:00
|
|
|
if realtimeScore.CurrentCycle.Truss {
|
|
|
|
|
score += 10
|
|
|
|
|
if realtimeScore.CurrentCycle.Catch {
|
|
|
|
|
score += 10
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return score
|
|
|
|
|
}
|
2014-08-18 20:23:30 -07:00
|
|
|
|
|
|
|
|
// Manipulates the arena LED lighting based on the current state of the match.
|
|
|
|
|
func (arena *Arena) handleLighting(alliance string, score *RealtimeScore) {
|
|
|
|
|
switch arena.MatchState {
|
|
|
|
|
case AUTO_PERIOD:
|
|
|
|
|
leftSide := arena.MatchTimeSec() < float64(arena.matchTiming.AutoDurationSec)/2 == arena.leftGoalHotFirst
|
|
|
|
|
SetHotGoalLights(alliance, leftSide)
|
|
|
|
|
case TELEOP_PERIOD:
|
|
|
|
|
if score.AutoLeftoverBalls == 0 && score.CurrentCycle.Assists == 0 {
|
|
|
|
|
SetPedestalLight(alliance)
|
|
|
|
|
} else {
|
|
|
|
|
ClearPedestalLight(alliance)
|
|
|
|
|
}
|
|
|
|
|
SetAssistGoalLights(alliance, score.CurrentCycle.Assists)
|
|
|
|
|
default:
|
|
|
|
|
ClearGoalLights(alliance)
|
|
|
|
|
ClearPedestalLight(alliance)
|
|
|
|
|
}
|
|
|
|
|
}
|