Add keyed NT Data and listeners (#27)

* Add most of the missing NT data to NTDataConsumer

* Clean up unused data classes, refactor DataConsumer to CVPipelineResultConsumer

* Fix root table name and reference

* Update NTDataPublisher and VisionModule for listeners

* NPE fix, attempt to remove old NT Table on name change

* Fix NT connected log spam

* Apply spotless

* Move NT flush, fix raw data name
This commit is contained in:
Banks T
2020-07-12 12:29:30 -04:00
committed by GitHub
parent afc52815e7
commit 30b4b8d111
14 changed files with 234 additions and 268 deletions

View File

@@ -17,4 +17,7 @@
package org.photonvision.common.dataflow;
public interface DataProvider {}
import java.util.function.Consumer;
import org.photonvision.vision.pipeline.result.CVPipelineResult;
public interface CVPipelineResultConsumer extends Consumer<CVPipelineResult> {}

View File

@@ -48,7 +48,7 @@ public class DataChangeService {
private DataChangeService() {
subscribers = new CopyOnWriteArrayList<>();
dispatchThread = new Thread(this::dispatchFromQueue);
dispatchThread.setName("EventDispatchThread");
dispatchThread.setName("DataChangeEventDispatchThread");
dispatchThread.start();
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright (C) 2020 Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.camera;
import org.photonvision.common.dataflow.DataChangeSubscriber;
import org.photonvision.common.dataflow.events.DataChangeEvent;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.processes.VisionModuleManager;
public class IncomingCameraCommandSubscriber extends DataChangeSubscriber {
private static final Logger logger =
new Logger(IncomingCameraCommandSubscriber.class, LogGroup.Camera);
private final VisionModuleManager vmm;
public IncomingCameraCommandSubscriber(VisionModuleManager instance) {
this.vmm = instance;
}
@Override
public void onDataChangeEvent(DataChangeEvent event) {
// logger.trace("Got event from [" + event.sourceType + "] and dest [" + event.destType
// + "] with property name [" + event.propertyName
// + "] and value [" + event.data + "]");
}
}

View File

@@ -15,9 +15,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.datatransfer;
package org.photonvision.common.dataflow.networktables;
import edu.wpi.first.networktables.EntryListenerFlags;
import edu.wpi.first.networktables.EntryNotification;
import edu.wpi.first.networktables.NetworkTableEntry;
import java.util.function.Consumer;
import org.photonvision.vision.processes.Data;
public interface DataConsumer extends Consumer<Data> {}
public class NTDataChangeListener {
private final NetworkTableEntry watchedEntry;
private final int listenerID;
public NTDataChangeListener(
NetworkTableEntry watchedEntry, Consumer<EntryNotification> dataChangeConsumer) {
this.watchedEntry = watchedEntry;
listenerID = watchedEntry.addListener(dataChangeConsumer, EntryListenerFlags.kUpdate);
}
public void remove() {
watchedEntry.removeListener(listenerID);
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (C) 2020 Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.networktables;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import org.photonvision.common.datatransfer.DataConsumer;
import org.photonvision.vision.pipeline.result.SimplePipelineResult;
import org.photonvision.vision.processes.Data;
public class NTDataConsumer implements DataConsumer {
private final NetworkTable rootTable;
NetworkTable subTable;
NetworkTableEntry rawData;
public NTDataConsumer(NetworkTable root, String camName) {
this.rootTable = root;
this.subTable = root.getSubTable(camName);
rawData = subTable.getEntry("rawData");
}
public void setCameraName(String camName) {
this.subTable = rootTable.getSubTable(camName);
rawData = subTable.getEntry("rawData");
}
@Override
public void accept(Data data) {
var simplified = new SimplePipelineResult(data.result);
var bytes = simplified.toByteArray();
rawData.setRaw(bytes);
rootTable.getInstance().flush();
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (C) 2020 Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.dataflow.networktables;
import edu.wpi.first.networktables.EntryNotification;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableEntry;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.photonvision.common.dataflow.CVPipelineResultConsumer;
import org.photonvision.vision.pipeline.result.CVPipelineResult;
import org.photonvision.vision.pipeline.result.SimplePipelineResult;
public class NTDataPublisher implements CVPipelineResultConsumer {
private final NetworkTable rootTable = NetworkTablesManager.kRootTable;
private NetworkTable subTable;
private NetworkTableEntry rawBytesEntry;
private NetworkTableEntry pipelineIndexEntry;
private final Consumer<Integer> pipelineIndexConsumer;
private NTDataChangeListener pipelineIndexListener;
private NetworkTableEntry driverModeEntry;
private final Consumer<Boolean> driverModeConsumer;
private NTDataChangeListener driverModeListener;
private NetworkTableEntry latencyMillisEntry;
private NetworkTableEntry hasTargetEntry;
private NetworkTableEntry targetPitchEntry;
private NetworkTableEntry targetYawEntry;
private NetworkTableEntry targetAreaEntry;
private NetworkTableEntry targetPoseEntry;
private final Supplier<Integer> pipelineIndexSupplier;
private final BooleanSupplier driverModeSupplier;
private String currentCameraNickname;
public NTDataPublisher(
String cameraNickname,
Supplier<Integer> pipelineIndexSupplier,
Consumer<Integer> pipelineIndexConsumer,
BooleanSupplier driverModeSupplier,
Consumer<Boolean> driverModeConsumer) {
this.pipelineIndexSupplier = pipelineIndexSupplier;
this.pipelineIndexConsumer = pipelineIndexConsumer;
this.driverModeSupplier = driverModeSupplier;
this.driverModeConsumer = driverModeConsumer;
currentCameraNickname = cameraNickname;
updateCameraNickname(cameraNickname);
updateEntries();
}
private void onPipelineIndexChange(EntryNotification entryNotification) {
var newIndex = (int) entryNotification.value.getDouble();
var originalIndex = pipelineIndexSupplier.get();
if (newIndex == originalIndex) {
// TODO: Log
return;
}
pipelineIndexConsumer.accept(newIndex);
var setIndex = pipelineIndexSupplier.get();
if (newIndex != setIndex) { // set failed
pipelineIndexEntry.forceSetNumber(setIndex);
// TODO: Log
}
// TODO: Log
}
private void onDriverModeChange(EntryNotification entryNotification) {
var newDriverMode = entryNotification.value.getBoolean();
var originalDriverMode = driverModeSupplier.getAsBoolean();
if (newDriverMode == originalDriverMode) {
// TODO: Log
return;
}
driverModeConsumer.accept(newDriverMode);
// TODO: Log
}
private void updateEntries() {
rawBytesEntry = subTable.getEntry("rawBytes");
if (pipelineIndexListener != null) {
pipelineIndexListener.remove();
}
pipelineIndexEntry = subTable.getEntry("pipelineIndex");
pipelineIndexListener =
new NTDataChangeListener(pipelineIndexEntry, this::onPipelineIndexChange);
if (driverModeListener != null) {
driverModeListener.remove();
}
driverModeEntry = subTable.getEntry("driverMode");
driverModeListener = new NTDataChangeListener(driverModeEntry, this::onDriverModeChange);
latencyMillisEntry = subTable.getEntry("latencyMillis");
hasTargetEntry = subTable.getEntry("hasTarget");
targetPitchEntry = subTable.getEntry("targetPitch");
targetAreaEntry = subTable.getEntry("targetArea");
targetYawEntry = subTable.getEntry("targetYaw");
targetPoseEntry = subTable.getEntry("targetPose");
}
public void updateCameraNickname(String newCameraNickname) {
rootTable.delete(currentCameraNickname); // TODO: make this actually work (if possible)
subTable = rootTable.getSubTable(newCameraNickname);
updateEntries();
currentCameraNickname = newCameraNickname;
}
@Override
public void accept(CVPipelineResult result) {
var simplified = new SimplePipelineResult(result);
var bytes = simplified.toByteArray();
rawBytesEntry.forceSetRaw(bytes);
pipelineIndexEntry.forceSetNumber(pipelineIndexSupplier.get());
driverModeEntry.forceSetBoolean(driverModeSupplier.getAsBoolean());
latencyMillisEntry.forceSetDouble(result.getLatencyMillis());
hasTargetEntry.forceSetBoolean(result.hasTargets());
if (result.hasTargets()) {
var bestTarget = result.targets.get(0);
targetPitchEntry.forceSetDouble(bestTarget.getPitch());
targetYawEntry.forceSetDouble(bestTarget.getYaw());
targetAreaEntry.forceSetDouble(bestTarget.getArea());
var poseX = bestTarget.getRobotRelativePose().getTranslation().getX();
var poseY = bestTarget.getRobotRelativePose().getTranslation().getY();
var poseRot = bestTarget.getRobotRelativePose().getRotation().getDegrees();
targetPoseEntry.forceSetDoubleArray(new double[] {poseX, poseY, poseRot});
} else {
targetPitchEntry.forceSetDouble(0);
targetYawEntry.forceSetDouble(0);
targetAreaEntry.forceSetDouble(0);
targetPoseEntry.forceSetDoubleArray(new double[] {0, 0, 0});
}
rootTable.getInstance().flush();
}
}

View File

@@ -21,6 +21,7 @@ import edu.wpi.first.networktables.LogMessage;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import java.util.function.Consumer;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.scripting.ScriptEventType;
@@ -34,30 +35,31 @@ public class NetworkTablesManager {
private static final NetworkTableInstance ntInstance = NetworkTableInstance.getDefault();
public static final String kRootTableName = "/chameleon-vision";
public static final String kRootTableName = "/photonvision";
public static final NetworkTable kRootTable =
NetworkTableInstance.getDefault().getTable(kRootTableName);
public static boolean isServer = false;
private static int getTeamNumber() {
// TODO: FIX
return 0;
// return ConfigManager.settings.teamNumber;
return ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber;
}
private static class NTLogger implements Consumer<LogMessage> {
private boolean hasReportedConnectionFailure = false;
private long lastConnectMessageMillis = 0;
@Override
public void accept(LogMessage logMessage) {
if (!hasReportedConnectionFailure && logMessage.message.contains("timed out")) {
logger.error("NT Connection has failed! Will retry in background.");
hasReportedConnectionFailure = true;
} else if (logMessage.message.contains("connected")) {
} else if (logMessage.message.contains("connected")
&& System.currentTimeMillis() - lastConnectMessageMillis > 125) {
logger.info("NT Connected!");
hasReportedConnectionFailure = false;
lastConnectMessageMillis = System.currentTimeMillis();
ScriptManager.queueEvent(ScriptEventType.kNTConnected);
}
}

View File

@@ -1,20 +0,0 @@
/*
* Copyright (C) 2020 Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.datatransfer;
public interface DataProvider {}

View File

@@ -1,96 +0,0 @@
/*
* Copyright (C) 2020 Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.datatransfer.networktables;
import edu.wpi.first.networktables.LogMessage;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import java.util.function.Consumer;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.scripting.ScriptEventType;
import org.photonvision.common.scripting.ScriptManager;
public class NetworkTablesManager {
private NetworkTablesManager() {}
private static final Logger logger = new Logger(NetworkTablesManager.class, LogGroup.General);
private static final NetworkTableInstance ntInstance = NetworkTableInstance.getDefault();
public static final String kRootTableName = "/photonvision";
public static final NetworkTable kRootTable =
NetworkTableInstance.getDefault().getTable(kRootTableName);
public static boolean isServer = false;
private static int getTeamNumber() {
return ConfigManager.getInstance().getConfig().getNetworkConfig().teamNumber;
}
private static class NTLogger implements Consumer<LogMessage> {
private boolean hasReportedConnectionFailure = false;
@Override
public void accept(LogMessage logMessage) {
if (!hasReportedConnectionFailure && logMessage.message.contains("timed out")) {
logger.error("NT Connection has failed! Will retry in background.");
hasReportedConnectionFailure = true;
} else if (logMessage.message.contains("connected")) {
logger.info("NT Connected!");
hasReportedConnectionFailure = false;
ScriptManager.queueEvent(ScriptEventType.kNTConnected);
}
}
}
static {
NetworkTableInstance.getDefault().addLogger(new NTLogger(), 0, 255); // to hide error messages
}
public static void setClientMode(String host) {
isServer = false;
logger.info("Starting NT Client");
ntInstance.stopServer();
if (host != null) {
ntInstance.startClient(host);
} else {
ntInstance.startClientTeam(getTeamNumber());
if (ntInstance.isConnected()) {
logger.info("[NetworkTablesManager] Connected to the robot!");
} else {
logger.info(
"[NetworkTablesManager] Could NOT to the robot! Will retry in the background...");
}
}
}
public static void setTeamClientMode() {
setClientMode(null);
}
public static void setServerMode() {
isServer = true;
logger.info("Starting NT Server");
ntInstance.stopClient();
ntInstance.startServer();
}
}