// Copyright 2014 Team 254. All Rights Reserved. // Author: pat@patfairbank.com (Patrick Fairbank) // // Client-side logic for the match play page. var websocket; var currentMatchId; var lowBatteryThreshold = 8; // Sends a websocket message to load a team into an alliance station. var substituteTeam = function(team, position) { websocket.send("substituteTeam", { team: parseInt(team), position: position }) }; // Sends a websocket message to toggle the bypass status for an alliance station. var toggleBypass = function(station) { websocket.send("toggleBypass", station); }; // Sends a websocket message to start the match. var startMatch = function() { websocket.send("startMatch", { muteMatchSounds: $("#muteMatchSounds").prop("checked") }); }; // Sends a websocket message to abort the match. var abortMatch = function() { websocket.send("abortMatch"); }; // Sends a websocket message to commit the match score and load the next match. var commitResults = function() { websocket.send("commitResults"); }; // Sends a websocket message to discard the match score and load the next match. var discardResults = function() { websocket.send("discardResults"); }; // Sends a websocket message to change what the audience display is showing. var setAudienceDisplay = function() { websocket.send("setAudienceDisplay", $("input[name=audienceDisplay]:checked").val()); }; // Sends a websocket message to change what the alliance station display is showing. 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); }; // Sends a websocket message to update the realtime score var updateRealtimeScore = function() { websocket.send("updateRealtimeScore", { blueAuto: parseInt($("#blueAutoScore").val()), redAuto: parseInt($("#redAutoScore").val()), blueTeleop: parseInt($("#blueTeleopScore").val()), redTeleop: parseInt($("#redTeleopScore").val()), blueEndgame: parseInt($("#blueEndgameScore").val()), redEndgame: parseInt($("#redEndgameScore").val()) }) }; var scoreKeyHandler = function(e) { var keycode = (event.keyCode ? event.keyCode : event.which); if (keycode == 13) { $('#' + $(event.target).attr("data-next")).focus().select(); } }; var confirmCommit = function(isReplay) { if (isReplay) { // Show the appropriate message(s) in the confirmation dialog. $("#confirmCommitReplay").css("display", isReplay ? "block" : "none"); $("#confirmCommitResults").modal("show"); } else { commitResults(); } }; // Handles a websocket message to update the team connection status. var handleArenaStatus = function(data) { // If getting data for the wrong match (e.g. after a server restart), reload the page. if (currentMatchId == null) { currentMatchId = data.MatchId; } else if (currentMatchId !== data.MatchId) { location.reload(); } // Update the team status view. $.each(data.AllianceStations, function(station, stationStatus) { var wifiStatus = data.TeamWifiStatuses[station]; $("#status" + station + " .radio-status").text(wifiStatus.TeamId); if (stationStatus.DsConn) { // Format the driver station status box. var dsConn = stationStatus.DsConn; $("#status" + station + " .ds-status").attr("data-status-ok", dsConn.DsLinked); // Format the radio status box according to the connection status of the robot radio. var radioOkay = stationStatus.Team && stationStatus.Team.Id === wifiStatus.TeamId && wifiStatus.RadioLinked; $("#status" + station + " .radio-status").attr("data-status-ok", radioOkay); // Format the robot status box. var robotOkay = dsConn.BatteryVoltage > lowBatteryThreshold && dsConn.RobotLinked; $("#status" + station + " .robot-status").attr("data-status-ok", robotOkay); if (stationStatus.DsConn.SecondsSinceLastRobotLink > 1 && stationStatus.DsConn.SecondsSinceLastRobotLink < 1000) { $("#status" + station + " .robot-status").text(stationStatus.DsConn.SecondsSinceLastRobotLink.toFixed()); } else { $("#status" + station + " .robot-status").text(dsConn.BatteryVoltage.toFixed(1) + "V"); } } else { $("#status" + station + " .ds-status").attr("data-status-ok", ""); $("#status" + station + " .robot-status").attr("data-status-ok", ""); $("#status" + station + " .robot-status").text(""); // Format the robot status box according to whether the AP is configured with the correct SSID. var expectedTeamId = stationStatus.Team ? stationStatus.Team.Id : 0; if (wifiStatus.TeamId === expectedTeamId) { if (wifiStatus.RadioLinked) { $("#status" + station + " .radio-status").attr("data-status-ok", true); } else { $("#status" + station + " .radio-status").attr("data-status-ok", ""); } } else { $("#status" + station + " .radio-status").attr("data-status-ok", false); } } if (stationStatus.Estop) { $("#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); $("#editResults").prop("disabled", true); $("#startTimeout").prop("disabled", false); $("#blueAutoScore").val("0"); $("#redAutoScore").val("0"); $("#blueTeleopScore").val("0"); $("#redTeleopScore").val("0"); $("#blueEndgameScore").val("0"); $("#redEndgameScore").val("0"); $("#blueAutoScore").prop("disabled", true); $("#redAutoScore").prop("disabled", true); $("#blueTeleopScore").prop("disabled", true); $("#redTeleopScore").prop("disabled", true); $("#blueEndgameScore").prop("disabled", true); $("#redEndgameScore").prop("disabled", true); break; case "START_MATCH": case "WARMUP_PERIOD": case "AUTO_PERIOD": case "PAUSE_PERIOD": case "TELEOP_PERIOD": $("#startMatch").prop("disabled", true); $("#abortMatch").prop("disabled", false); $("#commitResults").prop("disabled", true); $("#discardResults").prop("disabled", true); $("#editResults").prop("disabled", true); $("#startTimeout").prop("disabled", true); $("#blueAutoScore").prop("disabled", false); $("#redAutoScore").prop("disabled", false); $("#blueTeleopScore").prop("disabled", false); $("#redTeleopScore").prop("disabled", false); $("#blueEndgameScore").prop("disabled", false); $("#redEndgameScore").prop("disabled", false); break; case "POST_MATCH": $("#startMatch").prop("disabled", true); $("#abortMatch").prop("disabled", true); $("#commitResults").prop("disabled", false); $("#discardResults").prop("disabled", false); $("#editResults").prop("disabled", false); $("#startTimeout").prop("disabled", true); $("#blueAutoScore").prop("disabled", false); $("#redAutoScore").prop("disabled", false); $("#blueTeleopScore").prop("disabled", false); $("#redTeleopScore").prop("disabled", false); $("#blueEndgameScore").prop("disabled", false); $("#redEndgameScore").prop("disabled", false); 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); $("#blueAutoScore").prop("disabled", false); $("#redAutoScore").prop("disabled", false); $("#blueTeleopScore").prop("disabled", false); $("#redTeleopScore").prop("disabled", false); $("#blueEndgameScore").prop("disabled", false); $("#redEndgameScore").prop("disabled", false); 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); $("#blueAutoScore").prop("disabled", false); $("#redAutoScore").prop("disabled", false); $("#blueTeleopScore").prop("disabled", false); $("#redTeleopScore").prop("disabled", false); $("#blueEndgameScore").prop("disabled", false); $("#redEndgameScore").prop("disabled", false); break; } if (data.PlcIsHealthy) { $("#plcStatus").text("Connected"); $("#plcStatus").attr("data-ready", true); } else { $("#plcStatus").text("Not Connected"); $("#plcStatus").attr("data-ready", false); } $("#fieldEstop").attr("data-ready", !data.FieldEstop); $.each(data.PlcArmorBlockStatuses, function(name, status) { $("#plc" + name + "Status").attr("data-ready", status); }); }; // Handles a websocket message to update the match time countdown. var handleMatchTime = function(data) { translateMatchTime(data, function(matchState, matchStateText, countdownSec) { $("#matchState").text(matchStateText); $("#matchTime").text(countdownSec); }); }; // Handles a websocket message to update the match score. var handleRealtimeScore = function(data) { $("#redScore").text(data.Red.ScoreSummary.Score); $("#blueScore").text(data.Blue.ScoreSummary.Score); if (parseInt($("#redAutoScore").val()) != data.Red.Score.AutoPoints) { $("#redAutoScore").val(data.Red.Score.AutoPoints); } if (parseInt($("#redTeleopScore").val()) != data.Red.Score.TeleopPoints) { $("#redTeleopScore").val(data.Red.Score.TeleopPoints); } if (parseInt($("#redEndgameScore").val()) != data.Red.Score.EndgamePoints) { $("#redEndgameScore").val(data.Red.Score.EndgamePoints); } if (parseInt($("#blueAutoScore").val()) != data.Blue.Score.AutoPoints) { $("#blueAutoScore").val(data.Blue.Score.AutoPoints); } if (parseInt($("#blueTeleopScore").val()) != data.Blue.Score.TeleopPoints) { $("#blueTeleopScore").val(data.Blue.Score.TeleopPoints); } if (parseInt($("#blueEndgameScore").val()) != data.Blue.Score.EndgamePoints) { $("#blueEndgameScore").val(data.Blue.Score.EndgamePoints); } } // Handles a websocket message to update the audience display screen selector. var handleAudienceDisplayMode = function(data) { $("input[name=audienceDisplay]:checked").prop("checked", false); $("input[name=audienceDisplay][value=" + data + "]").prop("checked", true); }; // Handles a websocket message to update the alliance station display screen selector. var handleAllianceStationDisplayMode = function(data) { $("input[name=allianceStationDisplay]:checked").prop("checked", false); $("input[name=allianceStationDisplay][value=" + data + "]").prop("checked", true); }; // Handles a websocket message to update the event status message. var handleEventStatus = function(data) { if (data.CycleTime === "") { $("#cycleTimeMessage").text("Last cycle time: Unknown"); } else { $("#cycleTimeMessage").text("Last cycle time: " + data.CycleTime); } $("#earlyLateMessage").text(data.EarlyLateMessage); }; $(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", { allianceStationDisplayMode: function(event) { handleAllianceStationDisplayMode(event.data); }, arenaStatus: function(event) { handleArenaStatus(event.data); }, audienceDisplayMode: function(event) { handleAudienceDisplayMode(event.data); }, eventStatus: function(event) { handleEventStatus(event.data); }, matchTime: function(event) { handleMatchTime(event.data); }, matchTiming: function(event) { handleMatchTiming(event.data); }, realtimeScore: function(event) { handleRealtimeScore(event.data); }, }); });