diff --git a/field/arena.go b/field/arena.go
index 278527f..bacb0db 100644
--- a/field/arena.go
+++ b/field/arena.go
@@ -22,6 +22,7 @@ const (
arenaLoopPeriodMs = 10
dsPacketPeriodMs = 250
matchEndScoreDwellSec = 3
+ postTimeoutSec = 4
)
// Progression of match states.
@@ -36,6 +37,8 @@ const (
TeleopPeriod
EndgamePeriod
PostMatch
+ TimeoutActive
+ PostTimeout
)
type Arena struct {
@@ -342,11 +345,18 @@ func (arena *Arena) StartMatch() error {
return err
}
-// Kills the current match if it is underway.
+// Kills the current match or timeout if it is underway.
func (arena *Arena) AbortMatch() error {
- if arena.MatchState == PreMatch || arena.MatchState == PostMatch {
+ if arena.MatchState == PreMatch || arena.MatchState == PostMatch || arena.MatchState == PostTimeout {
return fmt.Errorf("Cannot abort match when it is not in progress.")
}
+
+ if arena.MatchState == TimeoutActive {
+ // Handle by advancing the timeout clock to the end and letting the regular logic deal with it.
+ arena.MatchStartTime = time.Now().Add(-time.Second * time.Duration(game.MatchTiming.TimeoutDurationSec))
+ return nil
+ }
+
if !arena.MuteMatchSounds && arena.MatchState != WarmupPeriod {
arena.PlaySoundNotifier.NotifyWithMessage("match-abort")
}
@@ -376,6 +386,23 @@ func (arena *Arena) ResetMatch() error {
return nil
}
+// Starts a timeout of the given duration.
+func (arena *Arena) StartTimeout(durationSec int) error {
+ if arena.MatchState != PreMatch {
+ return fmt.Errorf("Cannot start timeout while there is a match still in progress or with results pending.")
+ }
+
+ game.MatchTiming.TimeoutDurationSec = durationSec
+ arena.MatchTimingNotifier.Notify()
+ arena.MatchState = TimeoutActive
+ arena.MatchStartTime = time.Now()
+ arena.LastMatchTimeSec = -1
+ arena.AudienceDisplayMode = "timeout"
+ arena.AudienceDisplayModeNotifier.Notify()
+
+ return nil
+}
+
// Returns the fractional number of seconds since the start of the match.
func (arena *Arena) MatchTimeSec() float64 {
if arena.MatchState == PreMatch || arena.MatchState == StartMatch || arena.MatchState == PostMatch {
@@ -483,6 +510,21 @@ func (arena *Arena) Update() {
arena.PlaySoundNotifier.NotifyWithMessage("match-end")
}
}
+ case TimeoutActive:
+ if matchTimeSec >= float64(game.MatchTiming.TimeoutDurationSec) {
+ arena.MatchState = PostTimeout
+ arena.PlaySoundNotifier.NotifyWithMessage("match-end")
+ go func() {
+ // Leave the timer on the screen briefly at the end of the timeout period.
+ time.Sleep(time.Second * matchEndScoreDwellSec)
+ arena.AudienceDisplayMode = "blank"
+ arena.AudienceDisplayModeNotifier.Notify()
+ }()
+ }
+ case PostTimeout:
+ if matchTimeSec >= float64(game.MatchTiming.TimeoutDurationSec+postTimeoutSec) {
+ arena.MatchState = PreMatch
+ }
}
// Send a match tick notification if passing an integer second threshold or if the match state changed.
@@ -679,7 +721,8 @@ func (arena *Arena) handlePlcInput() {
arena.handleEstop("B2", blueEstops[1])
arena.handleEstop("B3", blueEstops[2])
- if arena.MatchState == PreMatch || arena.MatchState == PostMatch {
+ if arena.MatchState == PreMatch || arena.MatchState == PostMatch || arena.MatchState == TimeoutActive ||
+ arena.MatchState == PostTimeout {
// Don't do anything if we're outside the match, otherwise we may overwrite manual edits.
return
}
@@ -746,6 +789,10 @@ func (arena *Arena) handlePlcInput() {
func (arena *Arena) handleLeds() {
switch arena.MatchState {
case PreMatch:
+ fallthrough
+ case TimeoutActive:
+ fallthrough
+ case PostTimeout:
// Set the stack light state -- blinking green if ready, or solid alliance color(s) if not.
redAllianceReady := arena.checkAllianceStationsReady("R1", "R2", "R3") == nil
blueAllianceReady := arena.checkAllianceStationsReady("B1", "B2", "B3") == nil
diff --git a/field/arena_notifiers.go b/field/arena_notifiers.go
index a4ec9d3..13b7a6a 100644
--- a/field/arena_notifiers.go
+++ b/field/arena_notifiers.go
@@ -25,6 +25,7 @@ type ArenaNotifiers struct {
LowerThirdNotifier *websocket.Notifier
MatchLoadNotifier *websocket.Notifier
MatchTimeNotifier *websocket.Notifier
+ MatchTimingNotifier *websocket.Notifier
PlaySoundNotifier *websocket.Notifier
RealtimeScoreNotifier *websocket.Notifier
ReloadDisplaysNotifier *websocket.Notifier
@@ -70,6 +71,7 @@ func (arena *Arena) configureNotifiers() {
arena.LowerThirdNotifier = websocket.NewNotifier("lowerThird", nil)
arena.MatchLoadNotifier = websocket.NewNotifier("matchLoad", arena.generateMatchLoadMessage)
arena.MatchTimeNotifier = websocket.NewNotifier("matchTime", arena.generateMatchTimeMessage)
+ arena.MatchTimingNotifier = websocket.NewNotifier("matchTiming", arena.generateMatchTimingMessage)
arena.PlaySoundNotifier = websocket.NewNotifier("playSound", nil)
arena.RealtimeScoreNotifier = websocket.NewNotifier("realtimeScore", arena.generateRealtimeScoreMessage)
arena.ReloadDisplaysNotifier = websocket.NewNotifier("reload", nil)
@@ -135,6 +137,10 @@ func (arena *Arena) generateMatchTimeMessage() interface{} {
return MatchTimeMessage{int(arena.MatchState), int(arena.MatchTimeSec())}
}
+func (arena *Arena) generateMatchTimingMessage() interface{} {
+ return &game.MatchTiming
+}
+
func (arena *Arena) generateRealtimeScoreMessage() interface{} {
fields := struct {
Red *audienceAllianceScoreFields
diff --git a/field/arena_test.go b/field/arena_test.go
index a6fcb33..da37793 100644
--- a/field/arena_test.go
+++ b/field/arena_test.go
@@ -547,3 +547,51 @@ func TestAstop(t *testing.T) {
assert.Equal(t, true, arena.AllianceStations["R1"].DsConn.Enabled)
assert.Equal(t, false, arena.AllianceStations["R2"].DsConn.Enabled)
}
+
+func TestArenaTimeout(t *testing.T) {
+ arena := setupTestArena(t)
+
+ // Test regular ending of timeout.
+ timeoutDurationSec := 9
+ assert.Nil(t, arena.StartTimeout(timeoutDurationSec))
+ assert.Equal(t, timeoutDurationSec, game.MatchTiming.TimeoutDurationSec)
+ assert.Equal(t, TimeoutActive, arena.MatchState)
+ arena.MatchStartTime = time.Now().Add(-time.Duration(timeoutDurationSec) * time.Second)
+ arena.Update()
+ assert.Equal(t, PostTimeout, arena.MatchState)
+ arena.MatchStartTime = time.Now().Add(-time.Duration(timeoutDurationSec+postTimeoutSec) * time.Second)
+ arena.Update()
+ assert.Equal(t, PreMatch, arena.MatchState)
+
+ // Test early cancellation of timeout.
+ timeoutDurationSec = 28
+ assert.Nil(t, arena.StartTimeout(timeoutDurationSec))
+ assert.Equal(t, timeoutDurationSec, game.MatchTiming.TimeoutDurationSec)
+ assert.Equal(t, TimeoutActive, arena.MatchState)
+ assert.Nil(t, arena.AbortMatch())
+ arena.Update()
+ assert.Equal(t, PostTimeout, arena.MatchState)
+ arena.MatchStartTime = time.Now().Add(-time.Duration(timeoutDurationSec+postTimeoutSec) * time.Second)
+ arena.Update()
+ assert.Equal(t, PreMatch, arena.MatchState)
+
+ // Test that timeout can't be started during a match.
+ arena.AllianceStations["R1"].Bypass = true
+ arena.AllianceStations["R2"].Bypass = true
+ arena.AllianceStations["R3"].Bypass = true
+ arena.AllianceStations["B1"].Bypass = true
+ arena.AllianceStations["B2"].Bypass = true
+ arena.AllianceStations["B3"].Bypass = true
+ assert.Nil(t, arena.StartMatch())
+ arena.Update()
+ assert.NotNil(t, arena.StartTimeout(1))
+ assert.NotEqual(t, TimeoutActive, arena.MatchState)
+ assert.Equal(t, timeoutDurationSec, game.MatchTiming.TimeoutDurationSec)
+ arena.MatchStartTime = time.Now().Add(-time.Duration(game.MatchTiming.WarmupDurationSec+
+ game.MatchTiming.AutoDurationSec+game.MatchTiming.PauseDurationSec+game.MatchTiming.TeleopDurationSec) *
+ time.Second)
+ for arena.MatchState != PostMatch {
+ arena.Update()
+ assert.NotNil(t, arena.StartTimeout(1))
+ }
+}
diff --git a/field/driver_station_connection.go b/field/driver_station_connection.go
index 3765488..fa81198 100644
--- a/field/driver_station_connection.go
+++ b/field/driver_station_connection.go
@@ -220,6 +220,10 @@ func (dsConn *DriverStationConnection) encodeControlPacket(arena *Arena) [22]byt
switch arena.MatchState {
case PreMatch:
fallthrough
+ case TimeoutActive:
+ fallthrough
+ case PostTimeout:
+ matchSecondsRemaining = game.MatchTiming.AutoDurationSec
case StartMatch:
fallthrough
case AutoPeriod:
diff --git a/game/match_timing.go b/game/match_timing.go
index 5207063..33d78c7 100644
--- a/game/match_timing.go
+++ b/game/match_timing.go
@@ -13,7 +13,8 @@ var MatchTiming = struct {
PauseDurationSec int
TeleopDurationSec int
EndgameTimeLeftSec int
-}{3, 15, 2, 135, 30}
+ TimeoutDurationSec int
+}{3, 15, 2, 135, 30, 0}
func GetAutoEndTime(matchStartTime time.Time) time.Time {
return matchStartTime.Add(time.Duration(MatchTiming.WarmupDurationSec+MatchTiming.AutoDurationSec) * time.Second)
diff --git a/static/css/alliance_station_display.css b/static/css/alliance_station_display.css
index 6626055..49f30b6 100644
--- a/static/css/alliance_station_display.css
+++ b/static/css/alliance_station_display.css
@@ -73,7 +73,8 @@ body[data-mode=fieldReset] .mode#fieldReset {
#preMatch, #inMatch {
display: none;
}
-#match[data-state=PRE_MATCH] #preMatch {
+#match[data-state=PRE_MATCH] #preMatch, #match[data-state=TIMEOUT_ACTIVE] #preMatch,
+ #match[data-state=POST_TIMEOUT] #preMatch {
display: block;
}
#match[data-state=WARMUP_PERIOD] #inMatch, #match[data-state=AUTO_PERIOD] #inMatch,
diff --git a/static/css/audience_display.css b/static/css/audience_display.css
index 24ae5a1..97aa781 100644
--- a/static/css/audience_display.css
+++ b/static/css/audience_display.css
@@ -28,10 +28,11 @@ html {
.teams {
width: 40px;
height: 100%;
- line-height: 26px;
+ line-height: 29px;
text-align: center;
display: table;
font-family: "FuturaLT";
+ font-size: 21px;
}
.avatars {
display: none;
diff --git a/static/js/audience_display.js b/static/js/audience_display.js
index 58864e9..90c9937 100644
--- a/static/js/audience_display.js
+++ b/static/js/audience_display.js
@@ -13,6 +13,17 @@ var allianceSelectionTemplate = Handlebars.compile($("#allianceSelectionTemplate
var sponsorImageTemplate = Handlebars.compile($("#sponsorImageTemplate").html());
var sponsorTextTemplate = Handlebars.compile($("#sponsorTextTemplate").html());
+// Constants for overlay positioning. The CSS is the source of truth for the values that represent initial state.
+var centeringDown = $("#centering").css("bottom");
+var centeringUp = "0px";
+var logoUp = "10px";
+var logoDown = $("#logo").css("top");
+var scoreIn = $(".score").css("width");
+var scoreMid = "120px";
+var scoreOut = "275px";
+var teamsIn = $(".teams").css("width");
+var teamsOut = "65px";
+
// Handles a websocket message to change which screen is displayed.
var handleAudienceDisplayMode = function(targetScreen) {
if (targetScreen === currentScreen) {
@@ -175,11 +186,11 @@ var handleLowerThird = function(data) {
};
var transitionBlankToIntro = function(callback) {
- $(".avatars").show();
- $(".avatars").css("opacity", 1);
- $("#centering").transition({queue: false, bottom: "0px"}, 500, "ease", function() {
- $(".teams").transition({queue: false, width: "65px"}, 100, "linear", function() {
- $(".score").transition({queue: false, width: "120px"}, 500, "ease", function() {
+ $("#centering").transition({queue: false, bottom: centeringUp}, 500, "ease", function() {
+ $(".avatars").show();
+ $(".avatars").css("opacity", 1);
+ $(".teams").transition({queue: false, width: teamsOut}, 100, "linear", function() {
+ $(".score").transition({queue: false, width: scoreMid}, 500, "ease", function() {
$("#eventMatchInfo").show();
var height = -$("#eventMatchInfo").height();
$("#eventMatchInfo").transition({queue: false, bottom: height + "px"}, 500, "ease", callback);
@@ -192,8 +203,8 @@ var transitionIntroToInMatch = function(callback) {
$(".avatars").transition({queue: false, opacity: 0}, 500, "ease", function() {
$(".avatars").hide();
});
- $("#logo").transition({queue: false, top: "10px"}, 500, "ease");
- $(".score").transition({queue: false, width: "275px"}, 500, "ease", function() {
+ $("#logo").transition({queue: false, top: logoUp}, 500, "ease");
+ $(".score").transition({queue: false, width: scoreOut}, 500, "ease", function() {
$(".score-number").transition({queue: false, opacity: 1}, 750, "ease");
$(".score-fields").transition({queue: false, opacity: 1}, 750, "ease");
$(".seesaw-indicator").transition({queue: false, opacity: 1}, 750, "ease");
@@ -204,25 +215,25 @@ var transitionIntroToInMatch = function(callback) {
var transitionIntroToBlank = function(callback) {
$("#eventMatchInfo").transition({queue: false, bottom: "0px"}, 500, "ease", function() {
$("#eventMatchInfo").hide();
- $(".score").transition({queue: false, width: "0px"}, 500, "ease");
- $(".teams").transition({queue: false, width: "40px"}, 500, "ease", function() {
+ $(".score").transition({queue: false, width: scoreIn}, 500, "ease");
+ $(".teams").transition({queue: false, width: teamsIn}, 500, "ease", function() {
$(".avatars").css("opacity", 0);
$(".avatars").hide();
- $("#centering").transition({queue: false, bottom: "-340px"}, 1000, "ease", callback);
+ $("#centering").transition({queue: false, bottom: centeringDown}, 1000, "ease", callback);
});
});
};
var transitionBlankToInMatch = function(callback) {
- $("#centering").transition({queue: false, bottom: "0px"}, 500, "ease", function() {
- $(".teams").transition({queue: false, width: "65px"}, 100, "linear", function() {
- $("#logo").transition({queue: false, top: "10px"}, 500, "ease");
- $(".score").transition({queue: false, width: "275px"}, 500, "ease", function() {
+ $("#centering").transition({queue: false, bottom: centeringUp}, 500, "ease", function() {
+ $(".teams").transition({queue: false, width: teamsOut}, 100, "linear", function() {
+ $("#logo").transition({queue: false, top: logoUp}, 500, "ease");
+ $(".score").transition({queue: false, width: scoreOut}, 500, "ease", function() {
$("#eventMatchInfo").show();
$(".score-number").transition({queue: false, opacity: 1}, 750, "ease");
$(".score-fields").transition({queue: false, opacity: 1}, 750, "ease");
$(".seesaw-indicator").transition({queue: false, opacity: 1}, 750, "ease");
- $("#matchTime").transition({queue: false, opacity: 1}, 750, "ease", callback);
+ $("#matchTime").transition({queue: false, opacity: 1}, 750, "ease");
var height = -$("#eventMatchInfo").height();
$("#eventMatchInfo").transition({queue: false, bottom: height + "px"}, 500, "ease", callback);
});
@@ -235,9 +246,9 @@ var transitionInMatchToIntro = function(callback) {
$(".score-fields").transition({queue: false, opacity: 0}, 300, "linear");
$(".seesaw-indicator").transition({queue: false, opacity: 0}, 300, "linear");
$("#matchTime").transition({queue: false, opacity: 0}, 300, "linear", function() {
- $("#logo").transition({queue: false, top: "35px"}, 500, "ease");
- $(".score").transition({queue: false, width: "120px"}, 500, "ease");
- $(".teams").transition({queue: false, width: "65px"}, 500, "ease", function() {
+ $("#logo").transition({queue: false, top: logoDown}, 500, "ease");
+ $(".score").transition({queue: false, width: scoreMid}, 500, "ease");
+ $(".teams").transition({queue: false, width: teamsOut}, 500, "ease", function() {
$(".avatars").show();
$(".avatars").transition({queue: false, opacity: 1}, 500, "ease", callback);
});
@@ -251,10 +262,10 @@ var transitionInMatchToBlank = function(callback) {
$(".seesaw-indicator").transition({queue: false, opacity: 0}, 300, "linear");
$(".score-number").transition({queue: false, opacity: 0}, 300, "linear", function() {
$("#eventMatchInfo").hide();
- $("#logo").transition({queue: false, top: "35px"}, 500, "ease");
- $(".score").transition({queue: false, width: "0px"}, 500, "ease");
- $(".teams").transition({queue: false, width: "40px"}, 500, "ease", function() {
- $("#centering").transition({queue: false, bottom: "-340px"}, 1000, "ease", callback);
+ $("#logo").transition({queue: false, top: logoDown}, 500, "ease");
+ $(".score").transition({queue: false, width: scoreIn}, 500, "ease");
+ $(".teams").transition({queue: false, width: teamsIn}, 500, "ease", function() {
+ $("#centering").transition({queue: false, bottom: centeringDown}, 1000, "ease", callback);
});
});
};
@@ -384,6 +395,52 @@ var transitionSponsorToScore = function(callback) {
});
};
+var transitionBlankToTimeout = function(callback) {
+ $("#centering").transition({queue: false, bottom: centeringUp}, 500, "ease", function () {
+ $("#logo").transition({queue: false, top: logoUp}, 500, "ease", function() {
+ $("#matchTime").transition({queue: false, opacity: 1}, 750, "ease", callback);
+ });
+ });
+};
+
+var transitionIntroToTimeout = function(callback) {
+ $("#eventMatchInfo").transition({queue: false, bottom: "0px"}, 500, "ease", function() {
+ $("#eventMatchInfo").hide();
+ $(".score").transition({queue: false, width: scoreIn}, 500, "ease");
+ $(".teams").transition({queue: false, width: teamsIn}, 500, "ease", function() {
+ $(".avatars").css("opacity", 0);
+ $(".avatars").hide();
+ $("#logo").transition({queue: false, top: logoUp}, 500, "ease", function() {
+ $("#matchTime").transition({queue: false, opacity: 1}, 750, "ease", callback);
+ });
+ });
+ });
+};
+
+var transitionTimeoutToBlank = function(callback) {
+ $("#matchTime").transition({queue: false, opacity: 0}, 300, "linear", function() {
+ $("#logo").transition({queue: false, top: logoDown}, 500, "ease", function() {
+ $("#centering").transition({queue: false, bottom: centeringDown}, 1000, "ease", callback);
+ });
+ });
+};
+
+var transitionTimeoutToIntro = function(callback) {
+ $("#matchTime").transition({queue: false, opacity: 0}, 300, "linear", function() {
+ $("#logo").transition({queue: false, top: logoDown}, 500, "ease", function() {
+ $(".avatars").show();
+ $(".avatars").css("opacity", 1);
+ $(".teams").transition({queue: false, width: teamsOut}, 100, "linear", function () {
+ $(".score").transition({queue: false, width: scoreMid}, 500, "ease", function () {
+ $("#eventMatchInfo").show();
+ var height = -$("#eventMatchInfo").height();
+ $("#eventMatchInfo").transition({queue: false, bottom: height + "px"}, 500, "ease", callback);
+ });
+ });
+ });
+ });
+};
+
// Loads sponsor slide data and builds the slideshow HTML.
var initializeSponsorDisplay = function() {
$.getJSON("/api/sponsor_slides", function(slides) {
@@ -472,11 +529,13 @@ $(function() {
logo: transitionBlankToLogo,
sponsor: transitionBlankToSponsor,
allianceSelection: transitionBlankToAllianceSelection,
- lowerThird: transitionBlankToLowerThird
+ lowerThird: transitionBlankToLowerThird,
+ timeout: transitionBlankToTimeout
},
intro: {
blank: transitionIntroToBlank,
- match: transitionIntroToInMatch
+ match: transitionIntroToInMatch,
+ timeout: transitionIntroToTimeout
},
match: {
blank: transitionInMatchToBlank,
@@ -502,6 +561,10 @@ $(function() {
},
lowerThird: {
blank: transitionLowerThirdToBlank
+ },
+ timeout: {
+ blank: transitionTimeoutToBlank,
+ intro: transitionTimeoutToIntro
}
}
});
diff --git a/static/js/fta_display.js b/static/js/fta_display.js
index c989cb2..96441d8 100644
--- a/static/js/fta_display.js
+++ b/static/js/fta_display.js
@@ -27,7 +27,8 @@ var handleArenaStatus = function(data) {
$("#status" + station + " .robot-status").text("");
}
var lowBatteryThreshold = 6;
- if (matchStates[data.MatchState] === "PRE_MATCH") {
+ if (matchStates[data.MatchState] === "PRE_MATCH" || matchStates[data.MatchState] === "TIMEOUT_ACTIVE" ||
+ matchStates[data.MatchState] === "POST_TIMEOUT") {
lowBatteryThreshold = 12;
}
$("#status" + station + " .battery-status").attr("data-status-ok",
diff --git a/static/js/match_play.js b/static/js/match_play.js
index 7277dff..10d0682 100644
--- a/static/js/match_play.js
+++ b/static/js/match_play.js
@@ -47,6 +47,16 @@ var setAllianceStationDisplay = function() {
websocket.send("setAllianceStationDisplay", $("input[name=allianceStationDisplay]:checked").val());
};
+// Sends a websocket message to start the timeout.
+var startTimeout = function() {
+ var duration = $("#timeoutDuration").val().split(":");
+ var durationSec = parseFloat(duration[0]);
+ if (duration.length > 1) {
+ durationSec = durationSec * 60 + parseFloat(duration[1]);
+ }
+ websocket.send("startTimeout", durationSec);
+};
+
var confirmCommit = function(isReplay) {
if (isReplay || !scoreIsReady) {
// Show the appropriate message(s) in the confirmation dialog.
@@ -72,7 +82,8 @@ var handleArenaStatus = function(data) {
$("#status" + station + " .robot-status").text("");
}
var lowBatteryThreshold = 6;
- if (matchStates[data.MatchState] === "PRE_MATCH") {
+ if (matchStates[data.MatchState] === "PRE_MATCH" || matchStates[data.MatchState] === "TIMEOUT_ACTIVE" ||
+ matchStates[data.MatchState] === "POST_TIMEOUT") {
lowBatteryThreshold = 12;
}
$("#status" + station + " .battery-status").attr("data-status-ok",
@@ -107,6 +118,7 @@ var handleArenaStatus = function(data) {
$("#commitResults").prop("disabled", true);
$("#discardResults").prop("disabled", true);
$("#editResults").prop("disabled", true);
+ $("#startTimeout").prop("disabled", false);
break;
case "START_MATCH":
case "AUTO_PERIOD":
@@ -118,6 +130,7 @@ var handleArenaStatus = function(data) {
$("#commitResults").prop("disabled", true);
$("#discardResults").prop("disabled", true);
$("#editResults").prop("disabled", true);
+ $("#startTimeout").prop("disabled", true);
break;
case "POST_MATCH":
$("#startMatch").prop("disabled", true);
@@ -125,6 +138,23 @@ var handleArenaStatus = function(data) {
$("#commitResults").prop("disabled", false);
$("#discardResults").prop("disabled", false);
$("#editResults").prop("disabled", false);
+ $("#startTimeout").prop("disabled", true);
+ break;
+ case "TIMEOUT_ACTIVE":
+ $("#startMatch").prop("disabled", true);
+ $("#abortMatch").prop("disabled", false);
+ $("#commitResults").prop("disabled", true);
+ $("#discardResults").prop("disabled", true);
+ $("#editResults").prop("disabled", true);
+ $("#startTimeout").prop("disabled", true);
+ break;
+ case "POST_TIMEOUT":
+ $("#startMatch").prop("disabled", true);
+ $("#abortMatch").prop("disabled", true);
+ $("#commitResults").prop("disabled", true);
+ $("#discardResults").prop("disabled", true);
+ $("#editResults").prop("disabled", true);
+ $("#startTimeout").prop("disabled", true);
break;
}
diff --git a/static/js/match_timing.js b/static/js/match_timing.js
index a7b3ecb..a0e0ff4 100644
--- a/static/js/match_timing.js
+++ b/static/js/match_timing.js
@@ -11,7 +11,9 @@ var matchStates = {
4: "PAUSE_PERIOD",
5: "TELEOP_PERIOD",
6: "ENDGAME_PERIOD",
- 7: "POST_MATCH"
+ 7: "POST_MATCH",
+ 8: "TIMEOUT_ACTIVE",
+ 9: "POST_TIMEOUT"
};
var matchTiming;
@@ -45,6 +47,10 @@ var translateMatchTime = function(data, callback) {
case "POST_MATCH":
matchStateText = "POST-MATCH";
break;
+ case "TIMEOUT_ACTIVE":
+ case "POST_TIMEOUT":
+ matchStateText = "TIMEOUT";
+ break;
}
callback(matchStates[data.MatchState], matchStateText, getCountdown(data.MatchState, data.MatchTimeSec));
};
@@ -55,13 +61,15 @@ var getCountdown = function(matchState, matchTimeSec) {
case "PRE_MATCH":
case "START_MATCH":
case "WARMUP_PERIOD":
- return matchTiming.AutoDurationSec;
+ return matchTiming.AutoDurationSec;
case "AUTO_PERIOD":
return matchTiming.WarmupDurationSec + matchTiming.AutoDurationSec - matchTimeSec;
case "TELEOP_PERIOD":
case "ENDGAME_PERIOD":
return matchTiming.WarmupDurationSec + matchTiming.AutoDurationSec + matchTiming.TeleopDurationSec +
matchTiming.PauseDurationSec - matchTimeSec;
+ case "TIMEOUT_ACTIVE":
+ return matchTiming.TimeoutDurationSec - matchTimeSec;
default:
return 0;
}
diff --git a/templates/match_play.html b/templates/match_play.html
index 70aaa58..8368c89 100644
--- a/templates/match_play.html
+++ b/templates/match_play.html
@@ -110,8 +110,22 @@
Scoring Status
+Referee
+ Red Scoring
+ Blue Scoring
PLC Status
+
+
+ E-Stop
+
Scoring Status
-Referee
- Red Scoring
- Blue Scoring
Match Sounds
-Alliance Station Display
Game-Specific Data
-
-
- E-Stop
-
Game-Specific Data
+ +Match Sounds
+Timeout
+ +