Skip to content

Commit

Permalink
Add python NT4 documentation (wpilibsuite#2168)
Browse files Browse the repository at this point in the history
  • Loading branch information
virtuald authored Jan 22, 2023
1 parent da727a8 commit de15d86
Show file tree
Hide file tree
Showing 11 changed files with 552 additions and 12 deletions.
29 changes: 28 additions & 1 deletion source/docs/software/networktables/client-side-program.rst
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,31 @@ A basic client program looks like the following example.
}
}
.. group-tab:: Python

.. code-block:: 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".

Expand All @@ -136,7 +161,7 @@ Then this sample program simply loops once a second and gets the values for x an

Building the program
--------------------
When building and running the program you will need some additional libraries to include with your client-side program. These are:
When building and running the program you will need some additional libraries to include with your client-side program. For Java these are:

https://frcmaven.wpi.edu/artifactory/development/edu/wpi/first/ntcore/ntcore-java/ (ntcore Java files)

Expand All @@ -146,6 +171,8 @@ https://frcmaven.wpi.edu/artifactory/development/edu/wpi/first/wpiutil/wpiutil-j

.. note:: The desktop platform jar is for Windows, macOS, and Linux.

For Python, refer to the `RobotPy pyntcore install documentation <https://robotpy.readthedocs.io/en/stable/install/pynetworktables.html>`__.

Building using Gradle
^^^^^^^^^^^^^^^^^^^^^

Expand Down
2 changes: 1 addition & 1 deletion source/docs/software/networktables/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ NetworkTables

This section outlines the details of using the NetworkTables (v4) API to communicate information across the robot network.

.. important:: The code examples in this section are not intended for the user to copy-paste. Ensure that the following documentation is thoroughly read and the API (`Java <https://github.wpilib.org/allwpilib/docs/release/java/index.html>`__, `C++ <https://github.wpilib.org/allwpilib/docs/release/cpp/index.html>`__) is consulted when necessary.
.. important:: The code examples in this section are not intended for the user to copy-paste. Ensure that the following documentation is thoroughly read and the API (`Java <https://github.wpilib.org/allwpilib/docs/release/java/index.html>`__, `C++ <https://github.wpilib.org/allwpilib/docs/release/cpp/index.html>`__, `Python <https://robotpy.readthedocs.io/projects/pyntcore/en/stable/api.html>`__) is consulted when necessary.

.. toctree::
:maxdepth: 1
Expand Down
108 changes: 108 additions & 0 deletions source/docs/software/networktables/listening-for-change.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,41 @@ There are a few different ways to detect that a topic's value has changed; the e
}
};
.. group-tab:: Python

.. code-block:: python
class Example:
def __init__(self) -> None:
# get the default instance of NetworkTables
inst = ntcore.NetworkTableInstance.getDefault()
# get the subtable called "datatable"
datatable = inst.getTable("datatable")
# subscribe to the topic in "datatable" called "Y"
self.ySub = datatable.getDoubleTopic("Y").subscribe(0.0)
self.prev = 0
def periodic(self):
# get() can be used with simple change detection to the previous value
value = self.ySub.get()
if value != self.prev:
self.prev = value
# save previous value
print("X changed value: " + value)
# readQueue() provides all value changes since the last call;
# this way it's not possible to miss a change by polling too slowly
for tsValue in self.ySub.readQueue():
print(f"X changed value: {tsValue.value} at local time {tsValue.time}")
# may not be necessary for robot programs if this class lives for
# the length of the program
def close(self):
self.ySub.close()
With a command-based robot, it's also possible to use ``NetworkBooleanEvent`` to link boolean topic changes to callback actions (e.g. running commands).

Expand Down Expand Up @@ -306,3 +341,76 @@ The ``addListener`` functions in NetworkTableInstance return a listener handle.
inst.RemoveListener(topicListenerHandle);
}
};
.. group-tab:: Python

.. code-block:: python
import ntcore
import threading
class Example:
def __init__(self) -> None:
# get the default instance of NetworkTables
inst = ntcore.NetworkTableInstance.getDefault()
# Use a mutex to ensure thread safety
self.lock = threading.Lock()
self.yValue = None
# add a connection listener; the first parameter will cause the
# callback to be called immediately for any current connections
def _connect_cb(event: ntcore.Event):
if event.is_(ntcore.EventFlags.kConnected):
print("Connected to", event.data.remote_id)
elif event.is_(ntcore.EventFlags.kDisconnected):
print("Disconnected from", event.data.remote_id)
self.connListenerHandle = inst.addConnectionListener(True, _connect_cb)
# get the subtable called "datatable"
datatable = inst.getTable("datatable")
# subscribe to the topic in "datatable" called "Y"
self.ySub = datatable.getDoubleTopic("Y").subscribe(0.0)
# add a listener to only value changes on the Y subscriber
def _on_ysub(event: ntcore.Event):
# can only get doubles because it's a DoubleSubscriber, but
# could check value.isDouble() here too
with self.lock:
self.yValue = event.data.value.getDouble()
self.valueListenerHandle = inst.addListener(
self.ySub, ntcore.EventFlags.kValueAll, _on_ysub
)
# add a listener to see when new topics are published within datatable
# the string array is an array of topic name prefixes.
def _on_pub(event: ntcore.Event):
if event.is_(ntcore.EventFlags.kPublish):
# topicInfo.name is the full topic name, e.g. "/datatable/X"
print("newly published", event.data.name)
self.topicListenerHandle = inst.addListener(
[datatable.getPath() + "/"], ntcore.EventFlags.kTopic, _on_pub
)
def periodic(self):
# get the latest value by reading the value; set it to null
# when we read to ensure we only get value changes
with self.lock:
value, self.yValue = self.yValue, None
if value is not None:
print("got new value", value)
# may not be needed for robot programs if this class exists for the
# lifetime of the program
def close(self):
inst = ntcore.NetworkTableInstance.getDefault()
inst.removeListener(self.topicListenerHandle)
inst.removeListener(self.valueListenerHandle)
inst.removeListener(self.connListenerHandle)
self.ySub.close()
17 changes: 16 additions & 1 deletion source/docs/software/networktables/multiple-instances.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ For most general usage, you should use the "default" instance, as all current da

However, if you wanted to do unit testing of your robot program's NetworkTables communications, you could set up your unit tests such that they create a separate client instance (still within the same program) and have it connect to the server instance that the main robot code is running.

The ``NetworkTableInstance`` (`Java <https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/networktables/NetworkTableInstance.html>`__, `C++ <https://github.wpilib.org/allwpilib/docs/release/cpp/classnt_1_1_network_table_instance.html>`__) class provides the API abstraction for instances. The number of instances that can be simultaneously created is limited to 16 (including the default instance), so when using multiple instances in cases such as unit testing code, it's important to destroy instances that are no longer needed.
The ``NetworkTableInstance`` (`Java <https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/networktables/NetworkTableInstance.html>`__, `C++ <https://github.wpilib.org/allwpilib/docs/release/cpp/classnt_1_1_network_table_instance.html>`__, `Python <https://robotpy.readthedocs.io/projects/pyntcore/en/stable/ntcore/NetworkTableInstance.html>`__) class provides the API abstraction for instances. The number of instances that can be simultaneously created is limited to 16 (including the default instance), so when using multiple instances in cases such as unit testing code, it's important to destroy instances that are no longer needed.

Destroying a NetworkTableInstance frees all resources related to the instance. All classes or handles that reference the instance (e.g. Topics, Publishers, and Subscribers) are invalidated and may result in unexpected behavior if used after the instance is destroyed--in particular, instance handles are reused so it's possible for a handle "left over" from a previously destroyed instance to refer to an unexpected resource in a newly created instance.

Expand Down Expand Up @@ -64,3 +64,18 @@ Destroying a NetworkTableInstance frees all resources related to the instance. A
// destroy a NetworkTable instance
NT_DestroyInstance(inst);
.. group-tab:: Python

.. code-block:: python
import ntcore
# get the default NetworkTable instance
defaultInst = ntcore.NetworkTableInstance.getDefault()
# create a NetworkTable instance
inst = NetworkTableInstance.create();
# destroy a NetworkTable instance
ntcore.NetworkTableInstance.destroy(inst)
4 changes: 3 additions & 1 deletion source/docs/software/networktables/networktables-intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Because of this, two timestamps are visible through the API: a server timestamp
NetworkTables Organization
--------------------------

Data is organized in NetworkTables in a hierarchy much like a filesystem's folders and files. There can be multiple subtables (folders) and topics (files) that may be nested in whatever way fits the data organization desired. At the top level (``NetworkTableInstance``: `Java <https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/networktables/NetworkTableInstance.html>`__, `C++ <https://github.wpilib.org/allwpilib/docs/release/cpp/classnt_1_1_network_table_instance.html>`__), topic names are handled similar to absolute paths in a filesystem: subtables are represented as a long topic name with slashes ("/") separating the nested subtable and value names. A ``NetworkTable`` (`Java <https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/networktables/NetworkTable.html>`__, `C++ <https://github.wpilib.org/allwpilib/docs/release/cpp/classnt_1_1_network_table.html>`__) object represents a single subtable (folder), so topic names are relative to the NetworkTable's base path: e.g. for a root table called "SmartDashboard" with a topic named "xValue", the same topic can be accessed via ``NetworkTableInstance`` as a topic named "/SmartDashboard/xValue". However, unlike a filesystem, subtables don't really exist in the same way folders do, as there is no way to represent an empty subtable on the network--a subtable "appears" only as long as there are topics published within it.
Data is organized in NetworkTables in a hierarchy much like a filesystem's folders and files. There can be multiple subtables (folders) and topics (files) that may be nested in whatever way fits the data organization desired. At the top level (``NetworkTableInstance``: `Java <https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/networktables/NetworkTableInstance.html>`__, `C++ <https://github.wpilib.org/allwpilib/docs/release/cpp/classnt_1_1_network_table_instance.html>`__, `Python <https://robotpy.readthedocs.io/projects/pyntcore/en/stable/ntcore/NetworkTableInstance.html#ntcore.NetworkTableInstance>`__), topic names are handled similar to absolute paths in a filesystem: subtables are represented as a long topic name with slashes ("/") separating the nested subtable and value names. A ``NetworkTable`` (`Java <https://github.wpilib.org/allwpilib/docs/release/java/edu/wpi/first/networktables/NetworkTable.html>`__, `C++ <https://github.wpilib.org/allwpilib/docs/release/cpp/classnt_1_1_network_table.html>`__, `Python <https://robotpy.readthedocs.io/projects/pyntcore/en/stable/ntcore/NetworkTable.html#ntcore.NetworkTable>`__) object represents a single subtable (folder), so topic names are relative to the NetworkTable's base path: e.g. for a root table called "SmartDashboard" with a topic named "xValue", the same topic can be accessed via ``NetworkTableInstance`` as a topic named "/SmartDashboard/xValue". However, unlike a filesystem, subtables don't really exist in the same way folders do, as there is no way to represent an empty subtable on the network--a subtable "appears" only as long as there are topics published within it.

:ref:`docs/software/wpilib-tools/outlineviewer/index:outlineviewer` is a utility for exploring the values stored in NetworkTables, and can show either a flat view (topics with absolute paths) or a nested view (subtables and topics).

Expand Down Expand Up @@ -100,3 +100,5 @@ Publishers, subscribers, and entries only exist as long as the objects exist.
In Java, a common bug is to create a subscriber or publisher and not properly release it by calling ``close()``, as this will result in the object lingering around for an unknown period of time and not releasing resources properly. This is less common of an issue in robot programs, as long as the publisher or subscriber object is stored in an instance variable that persists for the life of the program.

In C++, publishers, subscribers, and entries are :term:`RAII`, which means they are automatically destroyed when they go out of scope. ``NetworkTableInstance`` is an exception to this; it is designed to be explicitly destroyed, so it's not necessary to maintain a global instance of it.

Python is similar to Java, except that subscribers or publishers are released when they are garbage collected.
29 changes: 29 additions & 0 deletions source/docs/software/networktables/networktables-networking.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ Starting a NetworkTables Server
NT_Inst inst = NT_GetDefaultInstance();
NT_StartServer(inst, "networktables.json", "", NT_DEFAULT_PORT3, NT_DEFAULT_PORT4);
.. group-tab:: Python

.. code-block:: python
import ntcore
inst = ntcore.NetworkTableInstance.getDefault()
inst.startServer()
Starting a NetworkTables Client
-------------------------------
Expand Down Expand Up @@ -113,3 +122,23 @@ Starting a NetworkTables Client
// connect to a specific host/port
NT_SetServer(inst, "host", NT_DEFAULT_PORT4)
.. group-tab:: Python

.. code-block:: python
import ntcore
inst = ntcore.NetworkTableInstance.getDefault()
# start a NT4 client
inst.startClient4("example client")
# connect to a roboRIO with team number TEAM
inst.setServerTeam(TEAM)
# starting a DS client will try to get the roboRIO address from the DS application
inst.startDSClient()
# connect to a specific host/port
inst.setServer("host", ntcore.NetworkTableInstance.kDefaultPort4)
51 changes: 51 additions & 0 deletions source/docs/software/networktables/nt4-migration-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ NT3 code (was):
}
};
.. group-tab:: Python

.. code-block:: python
class Example:
def __init__(self):
inst = ntcore.NetworkTableInstance.getDefault()
# get the subtable called "datatable"
datatable = inst.getTable("datatable")
# get the entry in "datatable" called "Y"
self.yEntry = datatable.getEntry("Y")
# get the entry in "datatable" called "Out"
self.outEntry = datatable.getEntry("Out")
def periodic(self):
# read a double value from Y, and set Out to that value multiplied by 2
value = self.yEntry.getDouble(0.0) # default to 0
self.outEntry.setDouble(value * 2)
Recommended NT4 equivalent (should be):

Expand Down Expand Up @@ -141,6 +163,35 @@ Recommended NT4 equivalent (should be):
}
};
.. group-tab:: Python

.. code-block:: python
class Example:
def __init__(self) -> None:
inst = ntcore.NetworkTableInstance.getDefault()
# get the subtable called "datatable"
datatable = inst.getTable("datatable")
# subscribe to the topic in "datatable" called "Y"
# default value is 0
self.ySub = datatable.getDoubleTopic("Y").subscribe(0.0)
# publish to the topic in "datatable" called "Out"
self.outPub = datatable.getDoubleTopic("Out").publish()
def periodic(self):
# read a double value from Y, and set Out to that value multiplied by 2
value = self.ySub.get()
self.outPub.set(value * 2)
# often not required in robot code, unless this class doesn't exist for
# the lifetime of the entire robot program, in which case close() needs to be
# called to stop subscribing
def close(self):
self.ySub.close()
self.outPub.close()
Shuffleboard
------------
Expand Down
Loading

0 comments on commit de15d86

Please sign in to comment.