Updating NetworkTables from VisionThread

I have modified the example code that comes with the FRCVision to try to update the NetworkTables from the VisionThread. I am using an RPi 3 with the RPi camera v2.1. Shuffleboard streams the video and displays the NetworkTableEntry. However, I cannot update the entry from the thread. In the code I modify the entry in 5 places as an experiment. In Shuffleboard the value reaches 2.0. Is there something about Java concurrency that is preventing this?
I’m using
FRCVision 2019.3.1
WPILib extension v2019.4.1

/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved.                             */
/* Open Source Software - may be modified and shared by FRC teams. The code   */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project.                                                               */
/*----------------------------------------------------------------------------*/

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import edu.wpi.cscore.MjpegServer;
import edu.wpi.cscore.UsbCamera;
import edu.wpi.cscore.VideoSource;
import edu.wpi.first.cameraserver.CameraServer;
import edu.wpi.first.networktables.NetworkTableEntry;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import edu.wpi.first.vision.VisionPipeline;
import edu.wpi.first.vision.VisionThread;

import org.opencv.core.Mat;

public final class Main {
  private static String configFile = "/home/pi/frc.json";

  @SuppressWarnings("MemberName")
  public static class CameraConfig {
	public String name;
	public String path;
	public JsonObject config;
	public JsonElement streamConfig;
  }

  public static int team;
  public static boolean server;
  public static List<CameraConfig> cameraConfigs = new ArrayList<>();
  public static List<VideoSource> cameras = new ArrayList<>();

  private Main() {
  }

  /**
   * Report parse error.
   */
  public static void parseError(String str) {
	System.err.println("config error in '" + configFile + "': " + str);
  }

  /**
   * Read single camera configuration.
   */
  public static boolean readCameraConfig(JsonObject config) {
	CameraConfig cam = new CameraConfig();

	// name
	JsonElement nameElement = config.get("name");
	if (nameElement == null) {
	  parseError("could not read camera name");
	  return false;
	}
	cam.name = nameElement.getAsString();

	// path
	JsonElement pathElement = config.get("path");
	if (pathElement == null) {
	  parseError("camera '" + cam.name + "': could not read path");
	  return false;
	}
	cam.path = pathElement.getAsString();

	// stream properties
	cam.streamConfig = config.get("stream");

	cam.config = config;

	cameraConfigs.add(cam);
	return true;
  }

  /**
   * Read configuration file.
   */
  @SuppressWarnings("PMD.CyclomaticComplexity")
  public static boolean readConfig() {
	// parse file
	JsonElement top;
	try {
	  top = new JsonParser().parse(Files.newBufferedReader(Paths.get(configFile)));
	} catch (IOException ex) {
	  System.err.println("could not open '" + configFile + "': " + ex);
	  return false;
	}

	// top level must be an object
	if (!top.isJsonObject()) {
	  parseError("must be JSON object");
	  return false;
	}
	JsonObject obj = top.getAsJsonObject();

	// team number
	JsonElement teamElement = obj.get("team");
	if (teamElement == null) {
	  parseError("could not read team number");
	  return false;
	}
	team = teamElement.getAsInt();

	// ntmode (optional)
	if (obj.has("ntmode")) {
	  String str = obj.get("ntmode").getAsString();
	  if ("client".equalsIgnoreCase(str)) {
		server = false;
	  } else if ("server".equalsIgnoreCase(str)) {
		server = true;
	  } else {
		parseError("could not understand ntmode value '" + str + "'");
	  }
	}

	// cameras
	JsonElement camerasElement = obj.get("cameras");
	if (camerasElement == null) {
	  parseError("could not read cameras");
	  return false;
	}
	JsonArray cameras = camerasElement.getAsJsonArray();
	for (JsonElement camera : cameras) {
	  if (!readCameraConfig(camera.getAsJsonObject())) {
		return false;
	  }
	}

	return true;
  }

  /**
   * Start running the camera.
   */
  public static VideoSource startCamera(CameraConfig config) {
	System.out.println("Starting camera '" + config.name + "' on " + config.path);
	CameraServer inst = CameraServer.getInstance();
	UsbCamera camera = new UsbCamera(config.name, config.path);
	MjpegServer server = inst.startAutomaticCapture(camera);

	Gson gson = new GsonBuilder().create();

	camera.setConfigJson(gson.toJson(config.config));
	camera.setConnectionStrategy(VideoSource.ConnectionStrategy.kKeepOpen);

	if (config.streamConfig != null) {
	  server.setConfigJson(gson.toJson(config.streamConfig));
	}

	return camera;
  }

  /**
   * Example pipeline.
   */
  public static class MyPipeline implements VisionPipeline {
	public int val;
	NetworkTableEntry entry;

	public MyPipeline(NetworkTableEntry entry) {
	  this.entry = entry;
	  entry.setDouble(2.0);
	}

	@Override
	public void process(Mat mat) {
	  val += 1;

	  entry.setDouble(3.0);
	}
  }

  /**
   * Main.
   */
  public static void main(String... args) {
	if (args.length > 0) {
	  configFile = args[0];
	}

	// read configuration
	if (!readConfig()) {
	  return;
	}
	server = true;

	// start NetworkTables
	NetworkTableInstance ntinst = NetworkTableInstance.getDefault();
	System.out.println("Setting up NetworkTables server");
	ntinst.startServer();
	NetworkTable table = ntinst.getTable("myTable");
	NetworkTableEntry entry = table.getEntry("myEntry");
	entry.setDouble(1.0);

	// start cameras
	for (CameraConfig config : cameraConfigs) {
	  cameras.add(startCamera(config));
	}

	// start image processing on camera 0 if present
	if (cameras.size() >= 1) {
	  VisionThread visionThread = new VisionThread(cameras.get(0), new MyPipeline(entry), pipeline -> {
		entry.setDouble(4.0);
		pipeline.entry.setDouble(5.0);
		// do something with pipeline results
	  });

	  visionThread.start();
	}

	// loop forever
	for (;;) {
	  try {
		Thread.sleep(10000);
	  } catch (InterruptedException ex) {
		return;
	  }
	}
  }
}

Also, is there a more complete example somewhere?

1 Like

Have you tried following this example? It looks like you’re starting a server, which may be in conflict with the server started by the roboRIO.

https://wpilib.screenstepslive.com/s/currentCS/m/75361/l/851714-creating-a-client-side-program

I have the RPi wired to a LAN with my desktop PC. I don’t have a roboRIO to test with.

1 Like

You can use OutlineViewer to host the server on your Desktop and then test it with SmartDasboard/Shuffleboard to make sure you have an initial connection before testing the code with the R Pi. Additionally this will allow you to change the R Pi to a client, which will reflect what you will actually use with a roboRIO in the loop.

https://wpilib.screenstepslive.com/s/currentCS/m/getting_started/l/144981-frc-software-component-overview#outline_viewer

OK I ran OutlineViewer, made a key, and it showed up in Shuffleboard. I already have a key show up in Shuffleboard which is made programmatically from my RPi program. The problem is that I can’t change the key once the thread gets running. In the code I am trying to change the table entry in different places and beyond the MyPipeline constructor I am unsuccessful. I am looking to understand why. I’m not sure how OutlineViewer is supposed to help.

OutlineViewer can act as a client or server for NetworkTables, and gives you visibility into what keys and values it contains. It won’t solve your problem, but it might give you diagnostic information about the problem as a whole.

Based on the source for VisionRunner, it looks like your camera isn’t giving any information back to Wpilib. Perhaps the camera you want isn’t provided by cameras.get(0)? I’m not familiar with FRCVision, but that would be the next place I would look.

ed: TL;DR I’d check to make sure your camera is configured properly