Skip to content

Commit de15d86

Browse files
authored
Add python NT4 documentation (#2168)
1 parent da727a8 commit de15d86

11 files changed

+552
-12
lines changed

source/docs/software/networktables/client-side-program.rst

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,31 @@ A basic client program looks like the following example.
127127
}
128128
}
129129
130+
.. group-tab:: Python
131+
132+
.. code-block:: python
133+
134+
#!/usr/bin/env python3
135+
136+
import ntcore
137+
import time
138+
139+
if __name__ == "__main__":
140+
inst = ntcore.NetworkTableInstance.getDefault()
141+
table = inst.getTable("datatable")
142+
xSub = table.getDoubleTopic("x").subscribe(0)
143+
ySub = table.getDoubleTopic("y").subscribe(0)
144+
inst.startClient4("example client")
145+
inst.setServerTeam(TEAM) # where TEAM=190, 294, etc, or use inst.setServer("hostname") or similar
146+
inst.startDSClient() # recommended if running on DS computer; this gets the robot IP from the DS
147+
148+
while True:
149+
time.sleep(1)
150+
151+
x = xSub.get()
152+
y = ySub.get()
153+
print(f"X: {x} Y: {y}")
154+
130155
131156
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".
132157

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

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

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

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

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

174+
For Python, refer to the `RobotPy pyntcore install documentation <https://robotpy.readthedocs.io/en/stable/install/pynetworktables.html>`__.
175+
149176
Building using Gradle
150177
^^^^^^^^^^^^^^^^^^^^^
151178

source/docs/software/networktables/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ NetworkTables
33

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

6-
.. 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.
6+
.. 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.
77

88
.. toctree::
99
:maxdepth: 1

source/docs/software/networktables/listening-for-change.rst

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,41 @@ There are a few different ways to detect that a topic's value has changed; the e
129129
}
130130
};
131131
132+
.. group-tab:: Python
133+
134+
.. code-block:: python
135+
136+
class Example:
137+
def __init__(self) -> None:
138+
139+
# get the default instance of NetworkTables
140+
inst = ntcore.NetworkTableInstance.getDefault()
141+
142+
# get the subtable called "datatable"
143+
datatable = inst.getTable("datatable")
144+
145+
# subscribe to the topic in "datatable" called "Y"
146+
self.ySub = datatable.getDoubleTopic("Y").subscribe(0.0)
147+
148+
self.prev = 0
149+
150+
def periodic(self):
151+
# get() can be used with simple change detection to the previous value
152+
value = self.ySub.get()
153+
if value != self.prev:
154+
self.prev = value
155+
# save previous value
156+
print("X changed value: " + value)
157+
158+
# readQueue() provides all value changes since the last call;
159+
# this way it's not possible to miss a change by polling too slowly
160+
for tsValue in self.ySub.readQueue():
161+
print(f"X changed value: {tsValue.value} at local time {tsValue.time}")
162+
163+
# may not be necessary for robot programs if this class lives for
164+
# the length of the program
165+
def close(self):
166+
self.ySub.close()
132167
133168
With a command-based robot, it's also possible to use ``NetworkBooleanEvent`` to link boolean topic changes to callback actions (e.g. running commands).
134169

@@ -306,3 +341,76 @@ The ``addListener`` functions in NetworkTableInstance return a listener handle.
306341
inst.RemoveListener(topicListenerHandle);
307342
}
308343
};
344+
345+
.. group-tab:: Python
346+
347+
.. code-block:: python
348+
349+
import ntcore
350+
import threading
351+
352+
class Example:
353+
def __init__(self) -> None:
354+
355+
# get the default instance of NetworkTables
356+
inst = ntcore.NetworkTableInstance.getDefault()
357+
358+
# Use a mutex to ensure thread safety
359+
self.lock = threading.Lock()
360+
self.yValue = None
361+
362+
# add a connection listener; the first parameter will cause the
363+
# callback to be called immediately for any current connections
364+
def _connect_cb(event: ntcore.Event):
365+
if event.is_(ntcore.EventFlags.kConnected):
366+
print("Connected to", event.data.remote_id)
367+
elif event.is_(ntcore.EventFlags.kDisconnected):
368+
print("Disconnected from", event.data.remote_id)
369+
370+
self.connListenerHandle = inst.addConnectionListener(True, _connect_cb)
371+
372+
# get the subtable called "datatable"
373+
datatable = inst.getTable("datatable")
374+
375+
# subscribe to the topic in "datatable" called "Y"
376+
self.ySub = datatable.getDoubleTopic("Y").subscribe(0.0)
377+
378+
# add a listener to only value changes on the Y subscriber
379+
def _on_ysub(event: ntcore.Event):
380+
# can only get doubles because it's a DoubleSubscriber, but
381+
# could check value.isDouble() here too
382+
with self.lock:
383+
self.yValue = event.data.value.getDouble()
384+
385+
self.valueListenerHandle = inst.addListener(
386+
self.ySub, ntcore.EventFlags.kValueAll, _on_ysub
387+
)
388+
389+
# add a listener to see when new topics are published within datatable
390+
# the string array is an array of topic name prefixes.
391+
def _on_pub(event: ntcore.Event):
392+
if event.is_(ntcore.EventFlags.kPublish):
393+
# topicInfo.name is the full topic name, e.g. "/datatable/X"
394+
print("newly published", event.data.name)
395+
396+
self.topicListenerHandle = inst.addListener(
397+
[datatable.getPath() + "/"], ntcore.EventFlags.kTopic, _on_pub
398+
)
399+
400+
def periodic(self):
401+
# get the latest value by reading the value; set it to null
402+
# when we read to ensure we only get value changes
403+
with self.lock:
404+
value, self.yValue = self.yValue, None
405+
406+
if value is not None:
407+
print("got new value", value)
408+
409+
# may not be needed for robot programs if this class exists for the
410+
# lifetime of the program
411+
def close(self):
412+
inst = ntcore.NetworkTableInstance.getDefault()
413+
inst.removeListener(self.topicListenerHandle)
414+
inst.removeListener(self.valueListenerHandle)
415+
inst.removeListener(self.connListenerHandle)
416+
self.ySub.close()

source/docs/software/networktables/multiple-instances.rst

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ For most general usage, you should use the "default" instance, as all current da
77

88
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.
99

10-
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.
10+
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.
1111

1212
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.
1313

@@ -64,3 +64,18 @@ Destroying a NetworkTableInstance frees all resources related to the instance. A
6464
6565
// destroy a NetworkTable instance
6666
NT_DestroyInstance(inst);
67+
68+
.. group-tab:: Python
69+
70+
.. code-block:: python
71+
72+
import ntcore
73+
74+
# get the default NetworkTable instance
75+
defaultInst = ntcore.NetworkTableInstance.getDefault()
76+
77+
# create a NetworkTable instance
78+
inst = NetworkTableInstance.create();
79+
80+
# destroy a NetworkTable instance
81+
ntcore.NetworkTableInstance.destroy(inst)

source/docs/software/networktables/networktables-intro.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Because of this, two timestamps are visible through the API: a server timestamp
5656
NetworkTables Organization
5757
--------------------------
5858

59-
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.
59+
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.
6060

6161
: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).
6262

@@ -100,3 +100,5 @@ Publishers, subscribers, and entries only exist as long as the objects exist.
100100
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.
101101

102102
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.
103+
104+
Python is similar to Java, except that subscribers or publishers are released when they are garbage collected.

source/docs/software/networktables/networktables-networking.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ Starting a NetworkTables Server
3636
NT_Inst inst = NT_GetDefaultInstance();
3737
NT_StartServer(inst, "networktables.json", "", NT_DEFAULT_PORT3, NT_DEFAULT_PORT4);
3838
39+
.. group-tab:: Python
40+
41+
.. code-block:: python
42+
43+
import ntcore
44+
45+
inst = ntcore.NetworkTableInstance.getDefault()
46+
inst.startServer()
47+
3948
4049
Starting a NetworkTables Client
4150
-------------------------------
@@ -113,3 +122,23 @@ Starting a NetworkTables Client
113122
114123
// connect to a specific host/port
115124
NT_SetServer(inst, "host", NT_DEFAULT_PORT4)
125+
126+
.. group-tab:: Python
127+
128+
.. code-block:: python
129+
130+
import ntcore
131+
132+
inst = ntcore.NetworkTableInstance.getDefault()
133+
134+
# start a NT4 client
135+
inst.startClient4("example client")
136+
137+
# connect to a roboRIO with team number TEAM
138+
inst.setServerTeam(TEAM)
139+
140+
# starting a DS client will try to get the roboRIO address from the DS application
141+
inst.startDSClient()
142+
143+
# connect to a specific host/port
144+
inst.setServer("host", ntcore.NetworkTableInstance.kDefaultPort4)

source/docs/software/networktables/nt4-migration-guide.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,28 @@ NT3 code (was):
6969
}
7070
};
7171
72+
.. group-tab:: Python
73+
74+
.. code-block:: python
75+
76+
class Example:
77+
def __init__(self):
78+
inst = ntcore.NetworkTableInstance.getDefault()
79+
80+
# get the subtable called "datatable"
81+
datatable = inst.getTable("datatable")
82+
83+
# get the entry in "datatable" called "Y"
84+
self.yEntry = datatable.getEntry("Y")
85+
86+
# get the entry in "datatable" called "Out"
87+
self.outEntry = datatable.getEntry("Out")
88+
89+
def periodic(self):
90+
# read a double value from Y, and set Out to that value multiplied by 2
91+
value = self.yEntry.getDouble(0.0) # default to 0
92+
self.outEntry.setDouble(value * 2)
93+
7294
7395
Recommended NT4 equivalent (should be):
7496

@@ -141,6 +163,35 @@ Recommended NT4 equivalent (should be):
141163
}
142164
};
143165
166+
.. group-tab:: Python
167+
168+
.. code-block:: python
169+
170+
class Example:
171+
def __init__(self) -> None:
172+
inst = ntcore.NetworkTableInstance.getDefault()
173+
174+
# get the subtable called "datatable"
175+
datatable = inst.getTable("datatable")
176+
177+
# subscribe to the topic in "datatable" called "Y"
178+
# default value is 0
179+
self.ySub = datatable.getDoubleTopic("Y").subscribe(0.0)
180+
181+
# publish to the topic in "datatable" called "Out"
182+
self.outPub = datatable.getDoubleTopic("Out").publish()
183+
184+
def periodic(self):
185+
# read a double value from Y, and set Out to that value multiplied by 2
186+
value = self.ySub.get()
187+
self.outPub.set(value * 2)
188+
189+
# often not required in robot code, unless this class doesn't exist for
190+
# the lifetime of the entire robot program, in which case close() needs to be
191+
# called to stop subscribing
192+
def close(self):
193+
self.ySub.close()
194+
self.outPub.close()
144195
145196
Shuffleboard
146197
------------

0 commit comments

Comments
 (0)