mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 21:56:50 -04:00
Refined alliance station display.
This commit is contained in:
52
arena.go
52
arena.go
@@ -56,30 +56,32 @@ type RealtimeScore struct {
|
||||
}
|
||||
|
||||
type Arena struct {
|
||||
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
|
||||
audienceDisplayScreen string
|
||||
allianceStationDisplays map[string]string
|
||||
lastMatchState int
|
||||
lastMatchTimeSec float64
|
||||
savedMatch *Match
|
||||
savedMatchResult *MatchResult
|
||||
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
|
||||
audienceDisplayScreen string
|
||||
allianceStationDisplays map[string]string
|
||||
allianceStationDisplayScreen string
|
||||
lastMatchState int
|
||||
lastMatchTimeSec float64
|
||||
savedMatch *Match
|
||||
savedMatchResult *MatchResult
|
||||
}
|
||||
|
||||
var mainArena Arena // Named thusly to avoid polluting the global namespace with something more generic.
|
||||
@@ -108,6 +110,7 @@ func (arena *Arena) Setup() {
|
||||
arena.scorePostedNotifier = NewNotifier()
|
||||
arena.audienceDisplayNotifier = NewNotifier()
|
||||
arena.playSoundNotifier = NewNotifier()
|
||||
arena.allianceStationDisplayNotifier = NewNotifier()
|
||||
|
||||
// Load empty match as current.
|
||||
arena.MatchState = PRE_MATCH
|
||||
@@ -120,6 +123,7 @@ func (arena *Arena) Setup() {
|
||||
arena.savedMatch = &Match{}
|
||||
arena.savedMatchResult = &MatchResult{}
|
||||
arena.allianceStationDisplays = make(map[string]string)
|
||||
arena.allianceStationDisplayScreen = "blank"
|
||||
}
|
||||
|
||||
// Loads a team into an alliance station, cleaning up the previous team there if there is one.
|
||||
|
||||
29
displays.go
29
displays.go
@@ -772,9 +772,14 @@ func AllianceStationDisplayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
displayId := ""
|
||||
if _, ok := r.URL.Query()["displayId"]; ok {
|
||||
displayId = r.URL.Query()["displayId"][0]
|
||||
}
|
||||
data := struct {
|
||||
*EventSettings
|
||||
}{eventSettings}
|
||||
DisplayId string
|
||||
}{eventSettings, displayId}
|
||||
err = template.ExecuteTemplate(w, "alliance_station_display.html", data)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
@@ -797,10 +802,13 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque
|
||||
station = ""
|
||||
mainArena.allianceStationDisplays[displayId] = station
|
||||
}
|
||||
defer delete(mainArena.allianceStationDisplays, displayId)
|
||||
|
||||
allianceStationDisplayListener := mainArena.allianceStationDisplayNotifier.Listen()
|
||||
defer close(allianceStationDisplayListener)
|
||||
matchLoadTeamsListener := mainArena.matchLoadTeamsNotifier.Listen()
|
||||
defer close(matchLoadTeamsListener)
|
||||
robotStatusListener := mainArena.robotStatusNotifier.Listen()
|
||||
defer close(robotStatusListener)
|
||||
matchTimeListener := mainArena.matchTimeNotifier.Listen()
|
||||
defer close(matchTimeListener)
|
||||
realtimeScoreListener := mainArena.realtimeScoreNotifier.Listen()
|
||||
@@ -808,6 +816,11 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// Send the various notifications immediately upon connection.
|
||||
var data interface{}
|
||||
err = websocket.Write("setAllianceStationDisplay", mainArena.allianceStationDisplayScreen)
|
||||
if err != nil {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
return
|
||||
}
|
||||
err = websocket.Write("matchTiming", mainArena.matchTiming)
|
||||
if err != nil {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
@@ -851,6 +864,12 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque
|
||||
var messageType string
|
||||
var message interface{}
|
||||
select {
|
||||
case _, ok := <-allianceStationDisplayListener:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
messageType = "setAllianceStationDisplay"
|
||||
message = mainArena.allianceStationDisplayScreen
|
||||
case _, ok := <-matchLoadTeamsListener:
|
||||
if !ok {
|
||||
return
|
||||
@@ -864,6 +883,12 @@ func AllianceStationDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reque
|
||||
"R2": mainArena.AllianceStations["R2"].team, "R3": mainArena.AllianceStations["R3"].team,
|
||||
"B1": mainArena.AllianceStations["B1"].team, "B2": mainArena.AllianceStations["B2"].team,
|
||||
"B3": mainArena.AllianceStations["B3"].team}}
|
||||
case _, ok := <-robotStatusListener:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
messageType = "status"
|
||||
message = mainArena
|
||||
case matchTimeSec, ok := <-matchTimeListener:
|
||||
if !ok {
|
||||
return
|
||||
|
||||
@@ -161,6 +161,8 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer close(audienceDisplayListener)
|
||||
scoringStatusListener := mainArena.scoringStatusNotifier.Listen()
|
||||
defer close(scoringStatusListener)
|
||||
allianceStationDisplayListener := mainArena.allianceStationDisplayNotifier.Listen()
|
||||
defer close(allianceStationDisplayListener)
|
||||
|
||||
// Send the various notifications immediately upon connection.
|
||||
var data interface{}
|
||||
@@ -196,6 +198,11 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
return
|
||||
}
|
||||
err = websocket.Write("setAllianceStationDisplay", mainArena.allianceStationDisplayScreen)
|
||||
if err != nil {
|
||||
log.Printf("Websocket error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Spin off a goroutine to listen for notifications and pass them on through the websocket.
|
||||
go func() {
|
||||
@@ -232,6 +239,12 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
BlueScoreReady bool
|
||||
}{mainArena.redRealtimeScore.FoulsCommitted && mainArena.blueRealtimeScore.FoulsCommitted,
|
||||
mainArena.redRealtimeScore.TeleopCommitted, mainArena.blueRealtimeScore.TeleopCommitted}
|
||||
case _, ok := <-allianceStationDisplayListener:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
messageType = "setAllianceStationDisplay"
|
||||
message = mainArena.allianceStationDisplayScreen
|
||||
}
|
||||
err = websocket.Write(messageType, message)
|
||||
if err != nil {
|
||||
@@ -340,6 +353,15 @@ func MatchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
mainArena.audienceDisplayScreen = screen
|
||||
mainArena.audienceDisplayNotifier.Notify(nil)
|
||||
continue
|
||||
case "setAllianceStationDisplay":
|
||||
screen, ok := data.(string)
|
||||
if !ok {
|
||||
websocket.WriteError(fmt.Sprintf("Failed to parse '%s' message.", messageType))
|
||||
continue
|
||||
}
|
||||
mainArena.allianceStationDisplayScreen = screen
|
||||
mainArena.allianceStationDisplayNotifier.Notify(nil)
|
||||
continue
|
||||
default:
|
||||
websocket.WriteError(fmt.Sprintf("Invalid message type '%s'.", messageType))
|
||||
continue
|
||||
|
||||
@@ -8,28 +8,60 @@ body {
|
||||
background-color: #000;
|
||||
font-family: "FuturaLTBold";
|
||||
}
|
||||
#match {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#displayId {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto auto;
|
||||
height: 1px;
|
||||
line-height: 1px;
|
||||
text-align: center;
|
||||
color: #ff0;
|
||||
font-size: 500px;
|
||||
}
|
||||
#teamId {
|
||||
position: absolute;
|
||||
top: 7%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
font-size: 500px;
|
||||
line-height: 500px;
|
||||
text-align: center;
|
||||
margin: 50px 0px;
|
||||
color: #fff;
|
||||
}
|
||||
#teamName {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
height: 200px;
|
||||
line-height: 200px;
|
||||
text-align: center;
|
||||
font-family: "FuturaLT";
|
||||
font-size: 120px;
|
||||
}
|
||||
[data-alliance=R] {
|
||||
color: #f00;
|
||||
}
|
||||
[data-alliance=B] {
|
||||
color: #00f;
|
||||
}
|
||||
.match-info {
|
||||
color: #fff;
|
||||
}
|
||||
#matchInfo {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
height: 200px;
|
||||
line-height: 200px;
|
||||
text-align: center;
|
||||
font-size: 190px;
|
||||
color: #fff;
|
||||
}
|
||||
#redScore {
|
||||
color: #f00;
|
||||
@@ -37,3 +69,21 @@ body {
|
||||
#blueScore {
|
||||
color: #00f;
|
||||
}
|
||||
#logo {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 90%;
|
||||
margin: auto auto;
|
||||
}
|
||||
[data-alliance=R], [data-status=R] {
|
||||
background-color: #f00;
|
||||
}
|
||||
[data-alliance=B], [data-status=B] {
|
||||
background-color: #00f;
|
||||
}
|
||||
[data-status=bypass] {
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
BIN
static/img/logo-black.png
Normal file
BIN
static/img/logo-black.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -3,11 +3,27 @@
|
||||
//
|
||||
// Client-side methods for the alliance station display.
|
||||
|
||||
// A unique id to differentiate this station's display from its peers.
|
||||
var displayId;
|
||||
var allianceStation = "";
|
||||
var blinkInterval;
|
||||
var websocket;
|
||||
|
||||
var handleSetAllianceStationDisplay = function(targetScreen) {
|
||||
switch (targetScreen) {
|
||||
case "logo":
|
||||
$("#match").hide();
|
||||
$("#logo").show();
|
||||
break;
|
||||
case "blank":
|
||||
$("#match").hide();
|
||||
$("#logo").hide();
|
||||
break;
|
||||
case "match":
|
||||
$("#match").show();
|
||||
$("#logo").hide();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var handleSetMatch = function(data) {
|
||||
if (allianceStation != "" && data.AllianceStation == "") {
|
||||
// The client knows better what display this should be; let the server know.
|
||||
@@ -22,22 +38,49 @@ var handleSetMatch = function(data) {
|
||||
if (team == null) {
|
||||
$("#teamId").text("");
|
||||
$("#teamName").text("");
|
||||
$("#teamName").attr("data-alliance", "");
|
||||
} else {
|
||||
$("#teamId").attr("data-alliance", allianceStation[0]);
|
||||
$("#teamName").attr("data-alliance", allianceStation[0]);
|
||||
$("#teamId").text(data.Teams[allianceStation].Id);
|
||||
$("#teamName").text(data.Teams[allianceStation].Nickname);
|
||||
}
|
||||
$("#displayIdRow").hide();
|
||||
$("#teamIdRow").show();
|
||||
$("#teamNameRow").show();
|
||||
$("#displayId").hide();
|
||||
$("#teamId").show();
|
||||
$("#teamName").show();
|
||||
} else {
|
||||
// Show the display ID so that someone can assign it to a station from the configuration interface.
|
||||
$("#teamId").text("");
|
||||
$("#teamName").text("");
|
||||
$("#displayIdRow").show();
|
||||
$("#teamIdRow").hide();
|
||||
$("#teamNameRow").hide();
|
||||
$("#displayId").show();
|
||||
$("#teamId").hide();
|
||||
$("#teamName").hide();
|
||||
}
|
||||
};
|
||||
|
||||
var handleStatus = function(data) {
|
||||
stationStatus = data.AllianceStations[allianceStation];
|
||||
var blink = false;
|
||||
if (stationStatus.Bypass) {
|
||||
$("#match").attr("data-status", "bypass");
|
||||
} else if (stationStatus.DsConn) {
|
||||
if (!stationStatus.DsConn.DriverStationStatus.DsLinked) {
|
||||
$("#match").attr("data-status", allianceStation[0]);
|
||||
} else if (!stationStatus.DsConn.DriverStationStatus.RobotLinked) {
|
||||
blink = true;
|
||||
if (!blinkInterval) {
|
||||
blinkInterval = setInterval(function() {
|
||||
var status = $("#match").attr("data-status");
|
||||
$("#match").attr("data-status", (status == "") ? allianceStation[0] : "");
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$("#match").attr("data-status", "");
|
||||
}
|
||||
|
||||
if (!blink && blinkInterval) {
|
||||
clearInterval(blinkInterval);
|
||||
blinkInterval = null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,11 +94,11 @@ var handleMatchTime = function(data) {
|
||||
$("#matchTime").text(countdownString);
|
||||
|
||||
if (matchState == "PRE_MATCH" || matchState == "POST_MATCH") {
|
||||
$("#teamNameRow").show();
|
||||
$("#matchInfoRow").hide();
|
||||
$("#teamName").show();
|
||||
$("#matchInfo").hide();
|
||||
} else {
|
||||
$("#teamNameRow").hide();
|
||||
$("#matchInfoRow").show();
|
||||
$("#teamName").hide();
|
||||
$("#matchInfo").show();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -66,12 +109,17 @@ var handleRealtimeScore = function(data) {
|
||||
};
|
||||
|
||||
$(function() {
|
||||
displayId = Math.floor(Math.random() * 10000);
|
||||
if (displayId == "") {
|
||||
displayId = Math.floor(Math.random() * 10000);
|
||||
window.location = "/displays/alliance_station?displayId=" + displayId;
|
||||
}
|
||||
$("#displayId").text(displayId);
|
||||
|
||||
// Set up the websocket back to the server.
|
||||
websocket = new CheesyWebsocket("/displays/alliance_station/websocket?displayId=" + displayId, {
|
||||
setAllianceStationDisplay: function(event) { handleSetAllianceStationDisplay(event.data); },
|
||||
setMatch: function(event) { handleSetMatch(event.data); },
|
||||
status: function(event) { handleStatus(event.data); },
|
||||
matchTiming: function(event) { handleMatchTiming(event.data); },
|
||||
matchTime: function(event) { handleMatchTime(event.data); },
|
||||
realtimeScore: function(event) { handleRealtimeScore(event.data); }
|
||||
|
||||
@@ -34,6 +34,10 @@ 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.
|
||||
@@ -118,6 +122,10 @@ var handleScoringStatus = function(data) {
|
||||
$("#blueScoreStatus").attr("data-ready", data.BlueScoreReady);
|
||||
};
|
||||
|
||||
var handleSetAllianceStationDisplay = function(data) {
|
||||
$("input[name=allianceStationDisplay][value=" + data + "]").prop("checked", true);
|
||||
};
|
||||
|
||||
$(function() {
|
||||
// Activate tooltips above the status headers.
|
||||
$("[data-toggle=tooltip]").tooltip({"placement": "top"});
|
||||
@@ -128,6 +136,7 @@ $(function() {
|
||||
matchTiming: function(event) { handleMatchTiming(event.data); },
|
||||
matchTime: function(event) { handleMatchTime(event.data); },
|
||||
setAudienceDisplay: function(event) { handleSetAudienceDisplay(event.data); },
|
||||
scoringStatus: function(event) { handleScoringStatus(event.data); }
|
||||
scoringStatus: function(event) { handleScoringStatus(event.data); },
|
||||
setAllianceStationDisplay: function(event) { handleSetAllianceStationDisplay(event.data); }
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,20 +8,21 @@
|
||||
<link rel="stylesheet" href="/static/css/alliance_station_display.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="row" id="displayIdRow" style="display: none;">>
|
||||
<div class="col-lg-12 text-center" id="displayId">1718</div>
|
||||
</div>
|
||||
<div class="row" id="teamIdRow" style="display: none;">>
|
||||
<div class="col-lg-12 text-center" id="teamId">254</div>
|
||||
</div>
|
||||
<div class="row" id="teamNameRow" style="display: none;">
|
||||
<div class="col-lg-12 text-center" id="teamName">Buchanan Bird Brains</div>
|
||||
</div>
|
||||
<div class="row" id="matchInfoRow" style="display: none;">
|
||||
<div class="col-lg-4 text-center match-info" id="redScore"></div>
|
||||
<div class="col-lg-4 text-center match-info" id="matchTime"></div>
|
||||
<div class="col-lg-4 text-center match-info" id="blueScore"></div>
|
||||
<div id="displayId" style="display: none;"></div>
|
||||
<div id="match" style="display: none;">
|
||||
<div id="teamId" style="display: none;"></div>
|
||||
<div id="teamName" style="display: none;"></div>
|
||||
<div class="row" id="matchInfo" style="display: none;">
|
||||
<div class="col-lg-4 text-center" id="redScore"></div>
|
||||
<div class="col-lg-4 text-center" id="matchTime"></div>
|
||||
<div class="col-lg-4 text-center" id="blueScore"></div>
|
||||
</div>
|
||||
</div>
|
||||
<img id="logo" src="/static/img/logo-black.png" style="display: none;"/>
|
||||
<script>
|
||||
// A unique id to differentiate this station's display from its peers.
|
||||
var displayId = "{{.DisplayId}}";
|
||||
</script>
|
||||
<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>
|
||||
|
||||
@@ -97,8 +97,8 @@
|
||||
</div>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-lg-6 well">
|
||||
<div class="col-lg-6">
|
||||
<div class="col-lg-9 well">
|
||||
<div class="col-lg-4">
|
||||
Audience Display
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
@@ -128,12 +128,35 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="col-lg-4">
|
||||
<p>Scoring Status</p>
|
||||
<p><span class="label label-scoring" id="refereeScoreStatus">Referee</span><br />
|
||||
<span class="label label-scoring" id="redScoreStatus">Red Scoring</span><br />
|
||||
<span class="label label-scoring" id="blueScoreStatus">Blue Scoring</span></p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
Alliance Station Display
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="allianceStationDisplay" value="blank"
|
||||
onclick="setAllianceStationDisplay();">Blank
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="allianceStationDisplay" value="match"
|
||||
onclick="setAllianceStationDisplay();">Match
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="allianceStationDisplay" value="logo"
|
||||
onclick="setAllianceStationDisplay();">Logo
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user