A pure Python implementation of NetworkTables, used for robot communications in the FIRST Robotics Competition.
75
Property decorators and utility classes for seamless integration with object-oriented robotics code and WPILib components. These utilities simplify NetworkTables usage in class-based robot code and provide interfaces to common FRC dashboard components.
Create class properties that automatically sync with NetworkTables.
def ntproperty(key: str, defaultValue, writeDefault: bool = True,
doc: str = None, persistent: bool = False,
*, inst = NetworkTables) -> property:
"""
Create a property that automatically synchronizes with NetworkTables.
Parameters:
- key: str. Full NetworkTables path (e.g., '/SmartDashboard/speed')
- defaultValue: Any. Default value if entry doesn't exist
- writeDefault: bool. Whether to write default value to NetworkTables
- doc: str, optional. Property docstring
- persistent: bool. Whether to make the NetworkTables entry persistent
- inst: NetworkTablesInstance. NetworkTables instance to use
Returns:
property: Python property descriptor that syncs with NetworkTables
"""Interface for WPILib SendableChooser objects over NetworkTables.
class ChooserControl:
def __init__(self, key: str, on_choices=None, on_selected=None,
*, inst=NetworkTables):
"""
Create a SendableChooser interface.
Parameters:
- key: str. NetworkTables key for the chooser (e.g., 'Autonomous Mode')
- on_choices: Callable, optional. Function called when choices change
- on_selected: Callable, optional. Function called when selection changes
- inst: NetworkTablesInstance. NetworkTables instance to use
"""
def getChoices() -> List[str]:
"""
Get available choices from the chooser.
Returns:
List[str]: List of available choice strings
"""
def getSelected() -> Optional[str]:
"""
Get the currently selected choice.
Returns:
Optional[str]: Selected choice string, or None if nothing selected
"""
def setSelected(selection: str) -> None:
"""
Set the selected choice (if it exists in available choices).
Parameters:
- selection: str. Choice to select
"""
def close() -> None:
"""Clean up resources and remove listeners."""from networktables.util import ntproperty
from networktables import NetworkTables
class DriveSubsystem:
# Automatically sync with SmartDashboard
max_speed = ntproperty('/SmartDashboard/maxSpeed', 1.0, persistent=True)
current_speed = ntproperty('/SmartDashboard/currentSpeed', 0.0)
drive_mode = ntproperty('/SmartDashboard/driveMode', 'manual')
def __init__(self):
# Properties are available immediately
print(f"Initial max speed: {self.max_speed}")
def set_speed(self, speed):
# Clamp to maximum
speed = min(speed, self.max_speed)
self.current_speed = speed
# Value automatically appears on SmartDashboard
def get_speed(self):
return self.current_speed
# Usage
NetworkTables.initialize(server='roborio-1234-frc.local')
drive = DriveSubsystem()
# These property accesses automatically sync with NetworkTables
drive.max_speed = 0.8 # Updates SmartDashboard
current = drive.current_speed # Reads from SmartDashboardfrom networktables.util import ntproperty
from networktables import NetworkTables
class RobotConfiguration:
# Persistent configuration values
max_speed = ntproperty(
'/Config/maxSpeed',
1.0,
persistent=True,
doc="Maximum robot speed in m/s"
)
autonomous_delay = ntproperty(
'/Config/autonomousDelay',
0.0,
persistent=True,
doc="Delay before autonomous starts in seconds"
)
# Dashboard display values (not persistent)
battery_voltage = ntproperty('/SmartDashboard/batteryVoltage', 0.0)
connection_count = ntproperty('/SmartDashboard/connectionCount', 0)
# Vision processing parameters
camera_exposure = ntproperty('/Vision/exposure', 50, persistent=True)
target_threshold = ntproperty('/Vision/threshold', 128, persistent=True)
def __init__(self):
self.load_config()
def load_config(self):
"""Load configuration from NetworkTables."""
print(f"Max speed: {self.max_speed}")
print(f"Auto delay: {self.autonomous_delay}")
print(f"Camera exposure: {self.camera_exposure}")
def update_dashboard(self, voltage, connections):
"""Update dashboard values."""
self.battery_voltage = voltage
self.connection_count = connections
# Usage
config = RobotConfiguration()
config.max_speed = 0.75 # Automatically persistentfrom networktables.util import ntproperty
from networktables import NetworkTables
class SmartMotorController:
_speed_raw = ntproperty('/Motors/speed', 0.0)
_enabled = ntproperty('/Motors/enabled', False)
@property
def speed(self):
"""Get motor speed with validation."""
return max(-1.0, min(1.0, self._speed_raw))
@speed.setter
def speed(self, value):
"""Set motor speed with validation."""
self._speed_raw = max(-1.0, min(1.0, value))
@property
def enabled(self):
"""Check if motor is enabled."""
return self._enabled
@enabled.setter
def enabled(self, value):
"""Enable/disable motor with safety check."""
if not value:
self._speed_raw = 0.0 # Stop motor when disabled
self._enabled = value
def emergency_stop(self):
"""Emergency stop with logging."""
print("EMERGENCY STOP ACTIVATED")
self.enabled = False
self.speed = 0.0
# Usage
motor = SmartMotorController()
motor.speed = 1.5 # Automatically clamped to 1.0
motor.enabled = Truefrom networktables.util import ChooserControl
from networktables import NetworkTables
class AutonomousSelector:
def __init__(self):
self.selected_mode = None
# Create chooser interface
self.chooser = ChooserControl(
'Autonomous Mode',
on_choices=self.on_choices_changed,
on_selected=self.on_selection_changed
)
def on_choices_changed(self, choices):
"""Called when available autonomous modes change."""
print(f"Available autonomous modes: {choices}")
def on_selection_changed(self, selected):
"""Called when autonomous mode selection changes."""
print(f"Selected autonomous mode: {selected}")
self.selected_mode = selected
def get_autonomous_mode(self):
"""Get the currently selected autonomous mode."""
return self.chooser.getSelected()
def set_default_mode(self, mode):
"""Set the default autonomous mode."""
choices = self.chooser.getChoices()
if mode in choices:
self.chooser.setSelected(mode)
else:
print(f"Warning: Mode '{mode}' not available in {choices}")
def cleanup(self):
"""Clean up resources."""
self.chooser.close()
# Usage in robot code
NetworkTables.initialize(server='roborio-1234-frc.local')
auto_selector = AutonomousSelector()
# Later in autonomous init
selected_mode = auto_selector.get_autonomous_mode()
if selected_mode == 'Defense':
run_defense_autonomous()
elif selected_mode == 'Shoot and Move':
run_shoot_and_move_autonomous()
# Cleanup when done
auto_selector.cleanup()from networktables.util import ChooserControl
from networktables import NetworkTables
class DashboardInterface:
def __init__(self):
self.choosers = {}
self.selections = {}
# Create multiple choosers
self.create_chooser('Autonomous Mode', self.on_auto_change)
self.create_chooser('Drive Mode', self.on_drive_change)
self.create_chooser('Camera Selection', self.on_camera_change)
def create_chooser(self, name, callback):
"""Create a chooser with callback."""
chooser = ChooserControl(name, on_selected=callback)
self.choosers[name] = chooser
self.selections[name] = None
def on_auto_change(self, selected):
"""Handle autonomous mode changes."""
self.selections['Autonomous Mode'] = selected
print(f"Autonomous mode: {selected}")
def on_drive_change(self, selected):
"""Handle drive mode changes."""
self.selections['Drive Mode'] = selected
print(f"Drive mode: {selected}")
# React to drive mode changes
if selected == 'Precision':
self.set_max_speed(0.5)
elif selected == 'Turbo':
self.set_max_speed(1.0)
def on_camera_change(self, selected):
"""Handle camera selection changes."""
self.selections['Camera Selection'] = selected
print(f"Active camera: {selected}")
def set_max_speed(self, speed):
"""Update max speed on dashboard."""
sd = NetworkTables.getTable('SmartDashboard')
sd.putNumber('maxSpeed', speed)
def get_selection(self, chooser_name):
"""Get current selection for a chooser."""
return self.selections.get(chooser_name)
def set_default_selections(self):
"""Set default selections for all choosers."""
defaults = {
'Autonomous Mode': 'Defense',
'Drive Mode': 'Normal',
'Camera Selection': 'Front'
}
for name, default in defaults.items():
if name in self.choosers:
self.choosers[name].setSelected(default)
def cleanup(self):
"""Clean up all choosers."""
for chooser in self.choosers.values():
chooser.close()
self.choosers.clear()
self.selections.clear()
# Usage
dashboard = DashboardInterface()
dashboard.set_default_selections()
# Access selections
auto_mode = dashboard.get_selection('Autonomous Mode')
drive_mode = dashboard.get_selection('Drive Mode')
# Cleanup when robot shuts down
dashboard.cleanup()from networktables.util import ntproperty
from networktables import NetworkTables
import json
class RobotTelemetry:
# Simple numeric properties
x_position = ntproperty('/Robot/position/x', 0.0)
y_position = ntproperty('/Robot/position/y', 0.0)
heading = ntproperty('/Robot/heading', 0.0)
# JSON-encoded complex data
_pose_json = ntproperty('/Robot/pose', '{"x": 0, "y": 0, "angle": 0}')
@property
def pose(self):
"""Get robot pose as a dictionary."""
try:
return json.loads(self._pose_json)
except json.JSONDecodeError:
return {"x": 0, "y": 0, "angle": 0}
@pose.setter
def pose(self, pose_dict):
"""Set robot pose from a dictionary."""
self._pose_json = json.dumps(pose_dict)
# Array properties using Value objects
_targets_raw = ntproperty('/Vision/targets', [])
@property
def targets(self):
"""Get vision targets as list."""
return self._targets_raw if isinstance(self._targets_raw, list) else []
@targets.setter
def targets(self, target_list):
"""Set vision targets."""
self._targets_raw = target_list
def update_position(self, x, y, angle):
"""Update robot position."""
self.x_position = x
self.y_position = y
self.heading = angle
# Also update compound pose
self.pose = {"x": x, "y": y, "angle": angle}
def add_target(self, target_data):
"""Add a vision target."""
current_targets = self.targets
current_targets.append(target_data)
self.targets = current_targets
# Usage
telemetry = RobotTelemetry()
telemetry.update_position(1.5, 2.3, 45.0)
telemetry.add_target({"distance": 3.2, "angle": 15.0})
print(f"Current pose: {telemetry.pose}")
print(f"Targets: {telemetry.targets}")persistent=True for values that should survive restartsclose() on ChooserControl objectsdoc parameter to document property purposeInstall with Tessl CLI
npx tessl i tessl/pypi-pynetworktablesdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10