- FRC Game Manual
- FRC Game Q&A

# Creating a Client-side Program

If all you need to do is have your robot program communicate with a COTS coprocessor or a dashboard running on the Driver Station laptop, then the previous examples of writing robot programs are sufficient. But if you would like to write some custom client code that would run on the drivers station or on a coprocessor then you need to know how to build NetworkTables programs for those (non-roboRIO) platforms.

A basic client program looks like the following example.

Java

    import edu.wpi.first.networktables.DoubleSubscriber;
    import edu.wpi.first.networktables.NetworkTable;
    import edu.wpi.first.networktables.NetworkTableInstance;
    import edu.wpi.first.networktables.NetworkTablesJNI;
    import edu.wpi.first.util.CombinedRuntimeLoader;
    import java.io.IOException;
    import edu.wpi.first.cscore.CameraServerJNI;
    import edu.wpi.first.math.WPIMathJNI;
    import edu.wpi.first.util.WPIUtilJNI;
    public class Program {
        public static void main(String[] args) throws IOException {
            NetworkTablesJNI.Helper.setExtractOnStaticLoad(false);
            WPIUtilJNI.Helper.setExtractOnStaticLoad(false);
            WPIMathJNI.Helper.setExtractOnStaticLoad(false);
            CameraServerJNI.Helper.setExtractOnStaticLoad(false);
            CombinedRuntimeLoader.loadLibraries(Program.class, "wpiutiljni", "wpimathjni", "ntcorejni",
                Core.NATIVE_LIBRARY_NAME, "cscorejni");
            new Program().run();
        }
        public void run() {
            NetworkTableInstance inst = NetworkTableInstance.getDefault();
            NetworkTable table = inst.getTable("datatable");
            DoubleSubscriber xSub = table.getDoubleTopic("x").subscribe(0.0);
            DoubleSubscriber ySub = table.getDoubleTopic("y").subscribe(0.0);
            inst.startClient4("example client");
            inst.setServerTeam(TEAM); // where TEAM=190, 294, etc, or use inst.setServer("hostname") or similar
            inst.startDSClient(); // recommended if running on DS computer; this gets the robot IP from the DS
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    System.out.println("interrupted");
                    return;
                }
                double x = xSub.get();
                double y = ySub.get();
                System.out.println("X: " + x + " Y: " + y);
            }
        }
    }

C++

    #include <chrono>
    #include <thread>
    #include <fmt/format.h>
    #include <networktables/NetworkTableInstance.h>
    #include <networktables/NetworkTable.h>
    #include <networktables/DoubleTopic.h>
    int main() {
      auto inst = nt::NetworkTableInstance::GetDefault();
      auto table = inst.GetTable("datatable");
      auto xSub = table->GetDoubleTopic("x").Subscribe(0.0);
      auto ySub = table->GetDoubleTopic("y").Subscribe(0.0);
      inst.StartClient4("example client");
      inst.SetServerTeam(TEAM);  // where TEAM=190, 294, etc, or use inst.setServer("hostname") or similar
      inst.StartDSClient();  // recommended if running on DS computer; this gets the robot IP from the DS
      while (true) {
        using namespace std::chrono_literals;
        std::this_thread::sleep_for(1s);
        double x = xSub.Get();
        double y = ySub.Get();
        fmt::print("X: {} Y: {}\n", x, y);
      }
    }

C++ (Handle-based)

    #include <chrono>
    #include <thread>
    #include <fmt/format.h>
    #include <ntcore_cpp.h>
    int main() {
      NT_Inst inst = nt::GetDefaultInstance();
      NT_Subscriber xSub =
          nt::Subscribe(nt::GetTopic(inst, "/datatable/x"), NT_DOUBLE, "double");
      NT_Subscriber ySub =
          nt::Subscribe(nt::GetTopic(inst, "/datatable/y"), NT_DOUBLE, "double");
      nt::StartClient4(inst, "example client");
      nt::SetServerTeam(inst, TEAM, 0);  // where TEAM=190, 294, etc, or use inst.setServer("hostname") or similar
      nt::StartDSClient(inst, 0);  // recommended if running on DS computer; this gets the robot IP from the DS
      while (true) {
        using namespace std::chrono_literals;
        std::this_thread::sleep_for(1s);
        double x = nt::GetDouble(xSub, 0.0);
        double y = nt::GetDouble(ySub, 0.0);
        fmt::print("X: {} Y: {}\n", x, y);
      }
    }

C

    #include <stdio.h>
    #include <threads.h>
    #include <time.h>
    #include <networktables/ntcore.h>
    int main() {
      NT_Instance inst = NT_GetDefaultInstance();
      NT_Subscriber xSub =
          NT_Subscribe(NT_GetTopic(inst, "/datatable/x"), NT_DOUBLE, "double", NULL, 0);
      NT_Subscriber ySub =
          NT_Subscribe(NT_GetTopic(inst, "/datatable/y"), NT_DOUBLE, "double", NULL, 0);
      NT_StartClient4(inst, "example client");
      NT_SetServerTeam(inst, TEAM);  // where TEAM=190, 294, etc, or use inst.setServer("hostname") or similar
      NT_StartDSClient(inst);  // recommended if running on DS computer; this gets the robot IP from the DS
      while (true) {
        thrd_sleep(&(struct timespec){.tv_sec=1}, NULL);
        double x = NT_GetDouble(xSub, 0.0);
        double y = NT_GetDouble(ySub, 0.0);
        printf("X: %f Y: %f\n", x, y);
      }
    }

Python

    #!/usr/bin/env python3
    import ntcore
    import time
    if __name__ == "__main__":
        inst = ntcore.NetworkTableInstance.getDefault()
        table = inst.getTable("datatable")
        xSub = table.getDoubleTopic("x").subscribe(0)
        ySub = table.getDoubleTopic("y").subscribe(0)
        inst.startClient4("example client")
        inst.setServerTeam(TEAM) # where TEAM=190, 294, etc, or use inst.setServer("hostname") or similar
        inst.startDSClient() # recommended if running on DS computer; this gets the robot IP from the DS
        while True:
            time.sleep(1)
            x = xSub.get()
            y = ySub.get()
            print(f"X: {x} Y: {y}")

In this example an instance of NetworkTables is created and subscribers are created to reference the values of “x” and “y” from a table called “datatable”.

Then this instance is started as a NetworkTables client with the team number (the roboRIO is always the server). Additionally, if the program is running on the Driver Station computer, by using the startDSClient() method, NetworkTables will get the robot IP address from the Driver Station.

Then this sample program simply loops once a second and gets the values for x and y and prints them on the console. In a more realistic program, the client might be processing or generating values for the robot to consume.

## Building using Gradle

Example build.gradle files are provided in the StandaloneAppSamples Repository Update the GradleRIO version to correspond to the desired WPILib version.

Java

     1plugins {
     2    id "java"
     3    id 'application'
     4    id 'com.gradleup.shadow' version '8.3.5'
     5    id "edu.wpi.first.GradleRIO" version "2025.1.1"
     6    id 'edu.wpi.first.WpilibTools' version '2.1.0'
     7}
     8
     9application {
    10    mainClass = 'Program'
    11}
    12
    13wpilibTools.deps.wpilibVersion = wpi.versions.wpilibVersion.get()
    14
    15def nativeConfigName = 'wpilibNatives'
    16def nativeConfig = configurations.create(nativeConfigName)
    17
    18def nativeTasks = wpilibTools.createExtractionTasks {
    19    configurationName = nativeConfigName
    20}
    21
    22nativeTasks.addToSourceSetResources(sourceSets.main)
    23nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpimath")
    24nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpinet")
    25nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpiutil")
    26nativeConfig.dependencies.add wpilibTools.deps.wpilib("ntcore")
    27nativeConfig.dependencies.add wpilibTools.deps.wpilib("cscore")
    28nativeConfig.dependencies.add wpilibTools.deps.wpilibOpenCv("frc" + wpi.frcYear.get(), wpi.versions.opencvVersion.get())
    29
    30dependencies {
    31    implementation wpilibTools.deps.wpilibJava("wpiutil")
    32    implementation wpilibTools.deps.wpilibJava("wpimath")
    33    implementation wpilibTools.deps.wpilibJava("wpinet")
    34    implementation wpilibTools.deps.wpilibJava("ntcore")
    35    implementation wpilibTools.deps.wpilibJava("cscore")
    36    implementation wpilibTools.deps.wpilibJava("cameraserver")
    37    implementation wpilibTools.deps.wpilibOpenCvJava("frc" + wpi.frcYear.get(), wpi.versions.opencvVersion.get())
    38
    39    implementation group: "com.fasterxml.jackson.core", name: "jackson-annotations", version: wpi.versions.jacksonVersion.get()
    40    implementation group: "com.fasterxml.jackson.core", name: "jackson-core", version: wpi.versions.jacksonVersion.get()
    41    implementation group: "com.fasterxml.jackson.core", name: "jackson-databind", version: wpi.versions.jacksonVersion.get()
    42
    43    implementation group: "org.ejml", name: "ejml-simple", version: wpi.versions.ejmlVersion.get()
    44    implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: wpi.versions.quickbufVersion.get();
    45}
    46
    47shadowJar {
    48    archiveBaseName = "TestApplication"
    49    archiveVersion = ""
    50    exclude("module-info.class")
    51    archiveClassifier.set(wpilibTools.currentPlatform.platformName)
    52}
    53
    54wrapper {
    55    gradleVersion = '8.11'
    56}

C++

Uncomment the appropriate platform as highlighted.

     1plugins {
     2    id "cpp"
     3    id "edu.wpi.first.GradleRIO" version "2025.1.1"
     4}
     5
     6// Disable local cache, as it won't have the cross artifact necessary
     7wpi.maven.useLocal = false
     8
     9// Set to true to run simulation in debug mode
    10wpi.cpp.debugSimulation = false
    11
    12def appName = "TestApplication"
    13
    14//nativeUtils.withCrossLinuxArm64() // Uncomment to build for Linux Arm 64. targetPlatform below also needs to be fixed
    15//nativeUtils.withCrossLinuxArm32() // Uncomment to build for Linux Arm 32. targetPlatform below also needs to be fixed
    16
    17model {
    18    components {
    19        "${appName}"(NativeExecutableSpec) {
    20            targetPlatform wpi.platforms.desktop // Uncomment to build on whatever the native platform currently is
    21            //targetPlatform wpi.platforms.linuxarm64 // Uncomment to build for Linux Arm 64
    22            //targetPlatform wpi.platforms.linuxarm32 // Uncomment to build for Linux Arm 32
    23
    24            sources.cpp {
    25                source {
    26                    srcDir 'src/main/cpp'
    27                    include '**/*.cpp', '**/*.cc'
    28                }
    29                exportedHeaders {
    30                    srcDir 'src/main/include'
    31                }
    32            }
    33
    34            // Enable run tasks for this component
    35            wpi.cpp.enableExternalTasks(it)
    36
    37            wpi.cpp.deps.wpilibStatic(it)
    38        }
    39    }
    40}
    41
    42wrapper {
    43    gradleVersion = '8.11'
    44}

## Building Python

For Python, refer to the RobotPy install documentation.