CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pynetworktables

A pure Python implementation of NetworkTables, used for robot communications in the FIRST Robotics Competition.

75

1.01x
Overview
Eval results
Files

integration-utilities.mddocs/

Integration Utilities

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.

Capabilities

Property Decorator

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
    """

SendableChooser Interface

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."""

Usage Examples

Basic Property Integration

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 SmartDashboard

Advanced Property Configuration

from 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 persistent

Property Validation and Processing

from 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 = True

SendableChooser Integration

from 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()

Multiple Chooser Management

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()

Custom Property Types

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}")

Best Practices

  1. Use descriptive paths: Choose clear, hierarchical NetworkTables paths
  2. Make configuration persistent: Use persistent=True for values that should survive restarts
  3. Validate inputs: Add property setters with validation for critical values
  4. Clean up choosers: Always call close() on ChooserControl objects
  5. Handle JSON errors: Use try/catch when working with JSON-encoded properties
  6. Document properties: Use the doc parameter to document property purpose
  7. Group related properties: Organize properties by subsystem or functionality

Install with Tessl CLI

npx tessl i tessl/pypi-pynetworktables

docs

entry-interface.md

index.md

instance-management.md

integration-utilities.md

listeners-events.md

persistence-data.md

table-operations.md

tile.json