Add team number dialog, NT connected chip (#313)

Makes NT connection status visible from the UI
This commit is contained in:
Matt
2021-11-25 15:43:29 -05:00
committed by GitHub
parent 822811c853
commit a151f23319
9 changed files with 302 additions and 28 deletions

View File

@@ -91,25 +91,51 @@
</v-list-item-content>
</v-list-item>
<v-list-item style="position: absolute; bottom: 0; left: 0;">
<v-list-item-icon>
<v-icon v-if="$store.state.backendConnected">
mdi-wifi
</v-icon>
<v-icon
v-else
class="pulse"
style="border-radius: 100%;"
>
mdi-wifi-off
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>
{{ $store.state.backendConnected ? "Connected" : "Trying to connect..." }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<div style="position: absolute; bottom: 0; left: 0;">
<v-list-item>
<v-list-item-icon>
<v-icon v-if="$store.state.settings.networkSettings.runNTServer">mdi-server</v-icon>
<img v-else-if="$store.state.ntConnectionInfo.connected" src="@/assets/robot.svg" alt="">
<img v-else class="pulse" style="border-radius: 100%" src="@/assets/robot-off.svg" alt="">
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="text-wrap" v-if="$store.state.settings.networkSettings.runNTServer">
NetworkTables server running for {{$store.state.ntConnectionInfo.clients ? $store.state.ntConnectionInfo.clients : 'zero'}} clients!
</v-list-item-title>
<v-list-item-title class="text-wrap" v-else-if="$store.state.ntConnectionInfo.connected && $store.state.backendConnected">
Robot connected! {{$store.state.ntConnectionInfo.address}}
</v-list-item-title>
<v-list-item-title class="text-wrap" v-else>
Not connected to robot!
</v-list-item-title>
<a
href="/#/settings"
style="color:#FFD843"
>{{"Team: " + $store.state.settings.networkSettings.teamNumber}}</a>
</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon v-if="$store.state.backendConnected">
mdi-wifi
</v-icon>
<v-icon
v-else
class="pulse"
style="border-radius: 100%;"
>
mdi-wifi-off
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="text-wrap">
{{ $store.state.backendConnected ? "Backend Connected" : "Trying to connect..." }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</div>
</v-list>
</v-navigation-drawer>
<v-main>
@@ -132,13 +158,35 @@
>
<logs />
</v-dialog>
<v-dialog
v-model="needsTeamNumberSet"
width="500"
dark
persistent
>
<v-card
dark
color="primary"
flat
>
<v-card-title>No team number set!</v-card-title>
<v-card-text>
PhotonVision cannot connect to your robot! Please
<a
href="/#/settings"
style="color:#FFD843"
>head to the settings page</a> and set your team number.
</v-card-text>
</v-card>
</v-dialog>
</v-app>
</template>
<script>
import Logs from "./views/LogsView"
// import {mapState} from "vuex";
export default {
export default {
name: 'App',
components: {
Logs
@@ -147,8 +195,16 @@ import Logs from "./views/LogsView"
// Used so that we can switch back to the previously selected pipeline after camera calibration
previouslySelectedIndices: [],
timer: undefined,
teamNumberDialog: true
}),
computed: {
needsTeamNumberSet: {
get() {
return this.$store.state.settings.networkSettings.teamNumber < 1
&& this.teamNumberDialog && this.$store.state.backendConnected
&& !this.$route.name.toLowerCase().includes("settings");
}
},
compact: {
get() {
if (this.$store.state.compactMode === undefined) {
@@ -163,6 +219,12 @@ import Logs from "./views/LogsView"
localStorage.setItem("compactMode", value);
},
},
// ...mapState({
// ntServerMode: state => state.settings.networkSettings.runNTServer,
// ntClients: state => state.ntConnectionInfo.clients,
// ntConnected: state => state.ntConnectionInfo.connected,
// backendConnected: state => state.backendConnected
// })
},
created() {
document.addEventListener("keydown", e => {
@@ -197,12 +259,12 @@ import Logs from "./views/LogsView"
}
};
this.$options.sockets.onopen = () => {
this.$store.state.backendConnected = true;
this.$store.commit("backendConnected", true)
this.$store.state.connectedCallbacks.forEach(it => it())
};
let closed = () => {
this.$store.state.backendConnected = false;
this.$store.commit("backendConnected", false)
};
this.$options.sockets.onclose = closed;
this.$options.sockets.onerror = closed;

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M23 15V18C23 18.5 22.64 18.88 22.17 18.97L18.97 15.77C19 15.68 19 15.59 19 15.5C19 14.12 17.88 13 16.5 13C16.41 13 16.32 13 16.23 13.03L10.2 7H11V5.73C10.4 5.39 10 4.74 10 4C10 2.9 10.9 2 12 2S14 2.9 14 4C14 4.74 13.6 5.39 13 5.73V7H14C17.87 7 21 10.13 21 14H22C22.55 14 23 14.45 23 15M22.11 21.46L20.84 22.73L19.89 21.78C19.62 21.92 19.32 22 19 22H5C3.9 22 3 21.11 3 20V19H2C1.45 19 1 18.55 1 18V15C1 14.45 1.45 14 2 14H3C3 11.53 4.29 9.36 6.22 8.11L1.11 3L2.39 1.73L22.11 21.46M10 15.5C10 14.12 8.88 13 7.5 13S5 14.12 5 15.5 6.12 18 7.5 18 10 16.88 10 15.5M16.07 17.96L14.04 15.93C14.23 16.97 15.04 17.77 16.07 17.96Z" /></svg>

After

Width:  |  Height:  |  Size: 928 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M12,2C13.1,2 14,2.9 14,4C14,4.74 13.6,5.39 13,5.73V7H14C17.87,7 21,10.13 21,14H22C22.55,14 23,14.45 23,15V18C23,18.55 22.55,19 22,19H21V20C21,21.1 20.1,22 19,22H5C3.9,22 3,21.1 3,20V19H2C1.45,19 1,18.55 1,18V15C1,14.45 1.45,14 2,14H3C3,10.13 6.13,7 10,7H11V5.73C10.4,5.39 10,4.74 10,4C10,2.9 10.9,2 12,2M7.5,13C6.12,13 5,14.12 5,15.5C5,16.88 6.12,18 7.5,18C8.88,18 10,16.88 10,15.5C10,14.12 8.88,13 7.5,13M16.5,13C15.12,13 14,14.12 14,15.5C14,16.88 15.12,18 16.5,18C17.88,18 19,16.88 19,15.5C19,14.12 17.88,13 16.5,13Z" /></svg>

After

Width:  |  Height:  |  Size: 827 B

View File

@@ -15,6 +15,13 @@ export default new Vuex.Store({
},
state: {
backendConnected: false,
ntConnectionInfo: {
connected: false,
address: "",
clients: 0,
possibleRios: ["Loading..."],
deviceips: ["Loading..."],
},
connectedCallbacks: [],
colorPicking: false,
logsOverlay: false,
@@ -143,6 +150,8 @@ export default new Vuex.Store({
settings: set('settings'),
calibrationData: set('calibrationData'),
metrics: set('metrics'),
ntConnectionInfo: set('ntConnectionInfo'),
backendConnected: set('backendConnected'),
logString: (state, newStr) => {
const str = state.logMessages;
str.push(newStr);

View File

@@ -38,6 +38,13 @@
<span v-else-if="!$store.getters.currentPipelineSettings.inputShouldShow">HSV thresholds are too broad; narrow them for better performance</span>
<span v-else>stop viewing the color stream for better performance</span>
</v-chip>
<v-chip small label color="red" text-color="white" v-if="!$store.state.ntConnectionInfo.connected || $store.state.settings.networkSettings.runNTServer">
<span>
{{ $store.state.settings.networkSettings.runNTServer ?
"NetworkTables Server Enabled! Photonlib may not work" :
"NetworkTables not connected!" }}
</span>
</v-chip>
<v-switch
v-model="driverMode"
label="Driver Mode"
@@ -83,6 +90,7 @@
>
<v-card
color="primary"
class="mt-3"
>
<!-- <v-btn @click="onCamNameChange">-->
<!-- Reload-->
@@ -91,7 +99,7 @@
</v-card>
<v-card
:disabled="$store.getters.isDriverMode || $store.state.colorPicking"
class="mt-3"
class="mt-6 mb-3"
color="primary"
>
<v-row
@@ -421,6 +429,12 @@ export default {
.some(e => e.width === resolution.width && e.height === resolution.height)
}
},
isRobotConnected: {
get() {
// return this.$store.state.ntConnectionInfo.connected && this.$store.state.backendConnected;
return true;
}
}
},
created() {
this.$store.state.connectedCallbacks.push(this.reloadStreams)

View File

@@ -11,9 +11,13 @@
:rules="[v => (v > 0) || 'Team number must be greater than zero', v => (v < 10000) || 'Team number must have fewer than five digits']"
class="mb-4"
/>
<span v-if="parseInt(teamNumber) < 1 && !runNTServer" class="red font-weight-bold">Team number not set! NetworkTables cannot connect.</span>
<v-chip label color="red" text-color="white" v-if="parseInt(teamNumber) < 1 && !runNTServer">
<span>
Team number not set! NetworkTables cannot connect.
</span>
</v-chip>
<CVradio
v-model="connectionType"
v-model="connectionType"
:list="['DHCP','Static']"
:disabled="!$store.state.settings.networkSettings.supported"
/>
@@ -37,8 +41,13 @@
v-model="runNTServer"
name="Run NetworkTables Server (Debugging Only!)"
tooltip="If enabled, this device will create a NT server. This is useful for home debugging, but should be disabled on-robot."
class="mt-3 mb-3"
/>
<span v-if="runNTServer" class="red font-weight-bold">Disable this switch if you're on a robot! Photonlib will NOT work.</span>
<v-chip label color="red" text-color="white" v-if="runNTServer">
<span>
Disable this switch if you're on a robot! Photonlib will NOT work.
</span>
</v-chip>
</v-form>
<v-btn
color="accent"
@@ -49,6 +58,60 @@
>
Save
</v-btn>
<v-divider class="mt-4 mb-4"/>
<v-row>
<v-col cols="6">
<v-simple-table
fixed-header
height="100%"
dense
>
<template v-slot:default>
<thead style="font-size: 1.25rem;">
<tr>
<th>
Device IPs
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(value, index) in $store.state.ntConnectionInfo.deviceips"
:key="index"
>
<td>{{ value }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
<v-col cols="6">
<v-simple-table
fixed-header
height="100%"
dense
>
<template v-slot:default>
<thead style="font-size: 1.25rem;">
<tr>
<th>
Possible RoboRIOs
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(value, index) in $store.state.ntConnectionInfo.possibleRios"
:key="index"
>
<td>{{ value }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
</v-row>
</div>
</template>
@@ -184,6 +247,24 @@ export default {
}
</script>
<style lang="" scoped>
<style scoped>
.v-data-table {
/*text-align: center;*/
background-color: transparent !important;
width: 100%;
height: 100%;
overflow-y: auto;
}
.v-data-table th {
background-color: #006492 !important;
}
.v-data-table th, td {
font-size: 1rem !important;
}
.v-data-table td {
font-family: monospace !important;
}
</style>

View File

@@ -16,16 +16,28 @@
*/
package org.photonvision.common.dataflow.networktables;
import edu.wpi.first.cscore.CameraServerJNI;
import edu.wpi.first.networktables.LogMessage;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import org.photonvision.PhotonVersion;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.configuration.NetworkConfig;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.scripting.ScriptEventType;
import org.photonvision.common.scripting.ScriptManager;
import org.photonvision.common.util.TimedTaskManager;
public class NetworkTablesManager {
private final NetworkTableInstance ntInstance = NetworkTableInstance.getDefault();
@@ -54,6 +66,7 @@ public class NetworkTablesManager {
if (!hasReportedConnectionFailure && logMessage.message.contains("timed out")) {
logger.error("NT Connection has failed! Will retry in background.");
hasReportedConnectionFailure = true;
getInstance().broadcastConnectedStatus();
} else if (logMessage.message.contains("connected")
&& System.currentTimeMillis() - lastConnectMessageMillis > 125) {
logger.info("NT Connected!");
@@ -61,10 +74,99 @@ public class NetworkTablesManager {
lastConnectMessageMillis = System.currentTimeMillis();
ScriptManager.queueEvent(ScriptEventType.kNTConnected);
getInstance().broadcastVersion();
getInstance().broadcastConnectedStatus();
}
}
}
public void broadcastConnectedStatus() {
TimedTaskManager.getInstance().addOneShotTask(this::broadcastConnectedStatusImpl, 1000L);
}
private void broadcastConnectedStatusImpl() {
HashMap<String, Object> map = new HashMap<>();
var subMap = new HashMap<String, Object>();
subMap.put("connected", ntInstance.isConnected());
if (ntInstance.isConnected()) {
var connections = getInstance().ntInstance.getConnections();
if (connections.length > 0) {
subMap.put("address", connections[0].remote_ip + ":" + connections[0].remote_port);
}
subMap.put("clients", connections.length);
}
map.put("ntConnectionInfo", subMap);
DataChangeService.getInstance()
.publishEvent(new OutgoingUIEvent<>("networkTablesConnected", map));
// Seperate from the above so we don't hold stuff up
System.setProperty("java.net.preferIPv4Stack", "true");
subMap.put(
"deviceips",
Arrays.stream(CameraServerJNI.getNetworkInterfaces())
.filter(it -> !it.equals("0.0.0.0"))
.toArray());
logger.info("Searching for rios");
List<String> possibleRioList = new ArrayList<>();
for (var ip : CameraServerJNI.getNetworkInterfaces()) {
logger.info("Trying " + ip);
var possibleRioAddr = getPossibleRioAddress(ip);
if (possibleRioAddr != null) {
logger.info("Maybe found " + ip);
searchForHost(possibleRioList, possibleRioAddr);
} else {
logger.info("Didn't match RIO IP");
}
}
String name =
"roboRIO-"
+ ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
+ "-FRC.local";
searchForHost(possibleRioList, name);
name =
"roboRIO-"
+ ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
+ "-FRC.lan";
searchForHost(possibleRioList, name);
name =
"roboRIO-"
+ ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber
+ "-FRC.frc-field.local";
searchForHost(possibleRioList, name);
subMap.put("possibleRios", possibleRioList.toArray());
DataChangeService.getInstance()
.publishEvent(new OutgoingUIEvent<>("networkTablesConnected", map));
}
String getPossibleRioAddress(String ip) {
try {
InetAddress addr = InetAddress.getByName(ip);
var address = addr.getAddress();
if (address[0] != (byte) (10 & 0xff)) return null;
address[3] = (byte) (2 & 0xff);
return InetAddress.getByAddress(address).getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return null;
}
void searchForHost(List<String> list, String hostname) {
try {
logger.info("Looking up " + hostname);
InetAddress testAddr = InetAddress.getByName(hostname);
logger.info("Pinging " + hostname);
var canContact = testAddr.isReachable(500);
if (canContact) {
logger.info("Was able to connect to " + hostname);
if (!list.contains(hostname)) list.add(hostname);
} else {
logger.info("Unable to reach " + hostname);
}
} catch (IOException ignored) {
}
}
private void broadcastVersion() {
kRootTable.getEntry("version").setString(PhotonVersion.versionString);
kRootTable.getEntry("buildDate").setString(PhotonVersion.buildDate);
@@ -83,7 +185,8 @@ public class NetworkTablesManager {
logger.info("Starting NT Client");
ntInstance.stopServer();
ntInstance.startClientTeam(teamNumber);
// ntInstance.startClientTeam(teamNumber);
ntInstance.startClient("localhost");
ntInstance.startDSClient();
if (ntInstance.isConnected()) {
logger.info("[NetworkTablesManager] Connected to the robot!");

View File

@@ -369,6 +369,7 @@ public class VisionModule {
visionSource.getSettables().setVideoModeInternal(config.cameraVideoModeIndex);
visionSource.getSettables().setBrightness(config.cameraBrightness);
visionSource.getSettables().setExposure(config.cameraExposure);
visionSource.getSettables().setGain(config.cameraGain);
if (!cameraQuirks.hasQuirk(CameraQuirk.Gain)) {
config.cameraGain = -1;

View File

@@ -25,6 +25,7 @@ import org.photonvision.common.dataflow.DataChangeSubscriber;
import org.photonvision.common.dataflow.events.DataChangeEvent;
import org.photonvision.common.dataflow.events.IncomingWebSocketEvent;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.logging.Logger;
public class UIInboundSubscriber extends DataChangeSubscriber {
@@ -46,6 +47,7 @@ public class UIInboundSubscriber extends DataChangeSubscriber {
new OutgoingUIEvent<>("fullsettings", settings, incomingWSEvent.originContext);
DataChangeService.getInstance().publishEvent(message);
Logger.sendConnectedBacklog();
NetworkTablesManager.getInstance().broadcastConnectedStatus();
}
}
}