WIP: Austin's changes at beta testing day.

This commit is contained in:
Nick Eyre
2014-08-23 21:01:43 -07:00
parent 777eef3723
commit fe660e9b4f
11 changed files with 297 additions and 15 deletions

View File

@@ -14,7 +14,7 @@ import (
const (
arenaLoopPeriodMs = 10
dsPacketPeriodMs = 250
dsPacketPeriodMs = 20
matchEndScoreDwellSec = 3
)
@@ -335,6 +335,14 @@ func (arena *Arena) StartMatch() error {
db.SaveMatch(arena.currentMatch)
}
// At the beginning of the match, save the missed packet count.
for _, allianceStation := range arena.AllianceStations {
if allianceStation.DsConn != nil {
allianceStation.DsConn.DriverStationStatus.MissedOffset =
allianceStation.DsConn.DriverStationStatus.MissedPacketCount
}
}
arena.MatchState = START_MATCH
}
return err

View File

@@ -32,19 +32,21 @@ type DriverStationStatus struct {
PacketCount int
MissedPacketCount int
DsRobotTripTimeMs int
MissedOffset int
}
type DriverStationConnection struct {
TeamId int
AllianceStation string
Auto bool
Enabled bool
EmergencyStop bool
DriverStationStatus *DriverStationStatus
LastPacketTime time.Time
LastRobotLinkedTime time.Time
conn net.Conn
packetCount int
TeamId int
AllianceStation string
Auto bool
Enabled bool
EmergencyStop bool
DriverStationStatus *DriverStationStatus
LastPacketTime time.Time
LastRobotLinkedTime time.Time
SecondsSinceLastRobotConnection float64
conn net.Conn
packetCount int
}
// Opens a UDP connection for communicating to the driver station.
@@ -105,6 +107,7 @@ func ListenForDsPackets(listener *net.UDPConn) {
if dsStatus.RobotLinked {
dsConn.LastRobotLinkedTime = time.Now()
}
dsConn.SecondsSinceLastRobotConnection = time.Since(dsConn.LastRobotLinkedTime).Seconds()
}
}
}

1
font
View File

@@ -1 +0,0 @@
../../../code.google.com/p/gofpdf/font

View File

@@ -87,6 +87,58 @@ func MatchPlayHandler(w http.ResponseWriter, r *http.Request) {
}
}
// Shows the match play control interface.
func MatchFTAHandler(w http.ResponseWriter, r *http.Request) {
practiceMatches, err := buildMatchPlayList("practice")
if err != nil {
handleWebErr(w, err)
return
}
qualificationMatches, err := buildMatchPlayList("qualification")
if err != nil {
handleWebErr(w, err)
return
}
eliminationMatches, err := buildMatchPlayList("elimination")
if err != nil {
handleWebErr(w, err)
return
}
template := template.New("").Funcs(templateHelpers)
_, err = template.ParseFiles("templates/match_fta.html", "templates/base.html")
if err != nil {
handleWebErr(w, err)
return
}
matchesByType := map[string]MatchPlayList{"practice": practiceMatches,
"qualification": qualificationMatches, "elimination": eliminationMatches}
if currentMatchType == "" {
currentMatchType = "practice"
}
allowSubstitution := mainArena.currentMatch.Type != "qualification"
matchResult, err := db.GetMatchResultForMatch(mainArena.currentMatch.Id)
if err != nil {
handleWebErr(w, err)
return
}
isReplay := matchResult != nil
data := struct {
*EventSettings
MatchesByType map[string]MatchPlayList
CurrentMatchType string
Match *Match
AllowSubstitution bool
IsReplay bool
}{eventSettings, matchesByType, currentMatchType, mainArena.currentMatch, allowSubstitution, isReplay}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
return
}
}
func MatchPlayLoadHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
matchId, _ := strconv.Atoi(vars["matchId"])

Binary file not shown.

View File

@@ -41,18 +41,21 @@
.modal-large {
width: 60%;
}
.ds-status, .robot-status, .battery-status, .bypass-status {
.ds-status, .robot-status, .battery-status, .bypass-status, .trip-time, .packet-loss {
background-color: #aaa;
color: #000;
border: 1px solid #999;
border-radius: 4px;
padding: 0px;
width: 40px;
width: 45px;
height: 27px;
line-height: 25px;
font-size: 14px;
margin: 0 auto;
}
.trip-time, .packet-loss {
width:90px;
}
.bypass-status {
cursor: pointer;
}

152
static/js/match_fta.js Normal file
View File

@@ -0,0 +1,152 @@
// Copyright 2014 Team 254. All Rights Reserved.
// Author: pat@patfairbank.com (Patrick Fairbank)
//
// Client-side logic for the match play page.
var websocket;
var scoreIsReady;
var substituteTeam = function(team, position) {
websocket.send("substituteTeam", { team: parseInt(team), position: position })
};
var toggleBypass = function(station) {
websocket.send("toggleBypass", station);
};
var startMatch = function() {
websocket.send("startMatch");
};
var abortMatch = function() {
websocket.send("abortMatch");
};
var setAudienceDisplay = function() {
websocket.send("setAudienceDisplay", $("input[name=audienceDisplay]:checked").val());
};
var setAllianceStationDisplay = function() {
websocket.send("setAllianceStationDisplay", $("input[name=allianceStationDisplay]:checked").val());
};
var confirmCommit = function(isReplay) {
if (isReplay || !scoreIsReady) {
// Show the appropriate message(s) in the confirmation dialog.
$("#confirmCommitReplay").css("display", isReplay ? "block" : "none");
$("#confirmCommitNotReady").css("display", scoreIsReady ? "none" : "block");
$("#confirmCommitResults").modal("show");
} else {
commitResults();
}
};
var handleStatus = function(data) {
// Update the team status view.
$.each(data.AllianceStations, function(station, stationStatus) {
if (stationStatus.DsConn) {
var dsStatus = stationStatus.DsConn.DriverStationStatus;
$("#status" + station + " .ds-status").attr("data-status-ok", dsStatus.DsLinked);
$("#status" + station + " .robot-status").attr("data-status-ok", dsStatus.RobotLinked);
if (stationStatus.DsConn.SecondsSinceLastRobotConnection > 1 && stationStatus.DsConn.SecondsSinceLastRobotConnection < 1000) {
$("#status" + station + " .robot-status").text(
stationStatus.DsConn.SecondsSinceLastRobotConnection.toFixed());
} else {
$("#status" + station + " .robot-status").text("");
}
$("#status" + station + " .battery-status").attr("data-status-ok",
dsStatus.BatteryVoltage > 6 && dsStatus.RobotLinked);
$("#status" + station + " .battery-status").text(dsStatus.BatteryVoltage.toFixed(1) + "V");
$("#status" + station + " .trip-time").attr("data-status-ok", true);
$("#status" + station + " .trip-time").text(dsStatus.DsRobotTripTimeMs.toFixed(1) + "ms");
$("#status" + station + " .packet-loss").attr("data-status-ok", true);
$("#status" + station + " .packet-loss").text((dsStatus.MissedPacketCount - dsStatus.MissedOffset).toFixed() + "p");
} else {
$("#status" + station + " .ds-status").attr("data-status-ok", "");
$("#status" + station + " .robot-status").attr("data-status-ok", "");
$("#status" + station + " .robot-status").text("");
$("#status" + station + " .battery-status").attr("data-status-ok", "");
$("#status" + station + " .battery-status").text("");
$("#status" + station + " .trip-time").attr("data-status-ok", "");
$("#status" + station + " .trip-time").text("");
$("#status" + station + " .packet-loss").attr("data-status-ok", "");
$("#status" + station + " .packet-loss").text("");
}
if (stationStatus.EmergencyStop) {
$("#status" + station + " .bypass-status").attr("data-status-ok", false);
$("#status" + station + " .bypass-status").text("ES");
} else if (stationStatus.Bypass) {
$("#status" + station + " .bypass-status").attr("data-status-ok", false);
$("#status" + station + " .bypass-status").text("B");
} else {
$("#status" + station + " .bypass-status").attr("data-status-ok", true);
$("#status" + station + " .bypass-status").text("");
}
});
// Enable/disable the buttons based on the current match state.
switch (matchStates[data.MatchState]) {
case "PRE_MATCH":
$("#startMatch").prop("disabled", !data.CanStartMatch);
$("#abortMatch").prop("disabled", true);
$("#commitResults").prop("disabled", true);
$("#discardResults").prop("disabled", true);
break;
case "START_MATCH":
case "AUTO_PERIOD":
case "PAUSE_PERIOD":
case "TELEOP_PERIOD":
case "ENDGAME_PERIOD":
$("#startMatch").prop("disabled", true);
$("#abortMatch").prop("disabled", false);
$("#commitResults").prop("disabled", true);
$("#discardResults").prop("disabled", true);
break;
case "POST_MATCH":
$("#startMatch").prop("disabled", true);
$("#abortMatch").prop("disabled", true);
$("#commitResults").prop("disabled", false);
$("#discardResults").prop("disabled", false);
break;
}
};
var handleMatchTime = function(data) {
translateMatchTime(data, function(matchState, matchStateText, countdownSec) {
$("#matchState").text(matchStateText);
$("#matchTime").text(countdownSec);
});
};
var handleSetAudienceDisplay = function(data) {
$("input[name=audienceDisplay]:checked").prop("checked", false);
$("input[name=audienceDisplay][value=" + data + "]").prop("checked", true);
};
var handleScoringStatus = function(data) {
scoreIsReady = data.RefereeScoreReady && data.RedScoreReady && data.BlueScoreReady;
$("#refereeScoreStatus").attr("data-ready", data.RefereeScoreReady);
$("#redScoreStatus").attr("data-ready", data.RedScoreReady);
$("#blueScoreStatus").attr("data-ready", data.BlueScoreReady);
};
var handleSetAllianceStationDisplay = function(data) {
$("input[name=allianceStationDisplay]:checked").prop("checked", false);
$("input[name=allianceStationDisplay][value=" + data + "]").prop("checked", true);
};
$(function() {
// Activate tooltips above the status headers.
$("[data-toggle=tooltip]").tooltip({"placement": "top"});
// Set up the websocket back to the server.
websocket = new CheesyWebsocket("/match_play/websocket", {
status: function(event) { handleStatus(event.data); },
matchTiming: function(event) { handleMatchTiming(event.data); },
matchTime: function(event) { handleMatchTime(event.data); },
setAudienceDisplay: function(event) { handleSetAudienceDisplay(event.data); },
scoringStatus: function(event) { handleScoringStatus(event.data); },
setAllianceStationDisplay: function(event) { handleSetAllianceStationDisplay(event.data); }
});
});

View File

@@ -123,7 +123,7 @@
<audio id="match-end" src="/static/audio/match_end.wav" type="audio/wav" preload="auto" />
<audio id="match-abort" src="/static/audio/match_abort.mp3" type="audio/mp3" preload="auto" />
<audio id="match-resume" src="/static/audio/match_resume.wav" type="audio/wav" preload="auto" />
<audio id="match-endgame" src="/static/audio/match_endgame.wav" type="audio/wav" preload="auto" />
<audio id="match-endgame" src="/static/audio/GetToTheChoppa.wav" type="audio/wav" preload="auto" />
<script src="/static/js/lib/jquery.min.js"></script>
<script src="/static/js/lib/jquery.json-2.4.min.js"></script>
<script src="/static/js/lib/jquery.websocket-0.0.1.js"></script>

View File

@@ -32,6 +32,7 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Run</a>
<ul class="dropdown-menu">
<li><a href="/match_play">Match Play</a></li>
<li><a href="/match_fta">Match FTA</a></li>
<li><a href="/match_review">Match Review</a></li>
</ul>
</li>

62
templates/match_fta.html Normal file
View File

@@ -0,0 +1,62 @@
{{define "title"}}Match FTA{{end}}
{{define "body"}}
<div class="row">
<div class="col-lg-12">
<div class="row text-center">
<div id="matchState" class="col-lg-2 col-lg-offset-4 well well-sm">&nbsp;</div>
<div id="matchTime" class="col-lg-2 well well-sm">&nbsp;</div>
</div>
<div class="row text-center">
<div class="col-lg-6 well well-darkblue">
<div class="row form-group">
<div class="col-lg-4">Blue Teams</div>
<div class="col-lg-1" data-toggle="tooltip" title="Driver Station">DS</div>
<div class="col-lg-1" data-toggle="tooltip" title="Robot Status and time since last seen">R</div>
<div class="col-lg-1" data-toggle="tooltip" title="Battery">Bat</div>
<div class="col-lg-2" data-toggle="tooltip" title="Trip Time">Trip Time</div>
<div class="col-lg-2" data-toggle="tooltip" title="Packet Loss">Lost Pack</div>
<div class="col-lg-1" data-toggle="tooltip" title="Bypass/Disable">Byp</div>
</div>
{{template "matchPlayTeam" dict "team" .Match.Blue1 "color" "B" "position" 1 "data" .}}
{{template "matchPlayTeam" dict "team" .Match.Blue2 "color" "B" "position" 2 "data" .}}
{{template "matchPlayTeam" dict "team" .Match.Blue3 "color" "B" "position" 3 "data" .}}
</div>
<div class="col-lg-6 well well-darkred">
<div class="row form-group">
<div class="col-lg-4">Red Teams</div>
<div class="col-lg-1" data-toggle="tooltip" title="Driver Station">DS</div>
<div class="col-lg-1" data-toggle="tooltip" title="Robot Status and time since last seen">R</div>
<div class="col-lg-1" data-toggle="tooltip" title="Battery">Bat</div>
<div class="col-lg-2" data-toggle="tooltip" title="Trip Time">Trip Time</div>
<div class="col-lg-2" data-toggle="tooltip" title="Packet Loss">Lost Pack</div>
<div class="col-lg-1" data-toggle="tooltip" title="Bypass/Disable">Byp</div>
</div>
{{template "matchPlayTeam" dict "team" .Match.Red3 "color" "R" "position" 3 "data" .}}
{{template "matchPlayTeam" dict "team" .Match.Red2 "color" "R" "position" 2 "data" .}}
{{template "matchPlayTeam" dict "team" .Match.Red1 "color" "R" "position" 1 "data" .}}
</div>
</div>
<br />
</div>
</div>
{{end}}
{{define "script"}}
<script src="/static/js/match_timing.js"></script>
<script src="/static/js/match_fta.js"></script>
{{end}}
{{define "matchPlayTeam"}}
<div class="row form-group" id="status{{.color}}{{.position}}">
<div class="col-lg-1">{{.position}} </div>
<div class="col-lg-3">
<input type="text" disabled class="form-control input-sm" value="{{if ne 0 .team}}{{.team}}{{end}}"
{{if not .data.AllowSubstitution}}disabled{{end}}>
</div>
<div class="col-lg-1 col-no-padding"><div class="ds-status"></div></div>
<div class="col-lg-1 col-no-padding"><div class="robot-status"></div></div>
<div class="col-lg-1 col-no-padding"><div class="battery-status"></div></div>
<div class="col-lg-2 col-no-padding"><div class="trip-time" ></div></div>
<div class="col-lg-2 col-no-padding"><div class="packet-loss" ></div></div>
<div class="col-lg-1 col-no-padding"><div class="bypass-status"></div>
</div>
</div>
{{end}}

2
web.go
View File

@@ -132,6 +132,8 @@ func newHandler() http.Handler {
router.HandleFunc("/match_review", MatchReviewHandler).Methods("GET")
router.HandleFunc("/match_review/{matchId}/edit", MatchReviewEditGetHandler).Methods("GET")
router.HandleFunc("/match_review/{matchId}/edit", MatchReviewEditPostHandler).Methods("POST")
router.HandleFunc("/match_fta", MatchFTAHandler).Methods("GET")
router.HandleFunc("/match_fta/websocket", MatchPlayWebsocketHandler).Methods("GET")
router.HandleFunc("/reports/csv/rankings", RankingsCsvReportHandler).Methods("GET")
router.HandleFunc("/reports/pdf/rankings", RankingsPdfReportHandler).Methods("GET")
router.HandleFunc("/reports/csv/schedule/{type}", ScheduleCsvReportHandler).Methods("GET")