0
# Integration Utilities
1
2
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.
3
4
## Capabilities
5
6
### Property Decorator
7
8
Create class properties that automatically sync with NetworkTables.
9
10
```python { .api }
11
def ntproperty(key: str, defaultValue, writeDefault: bool = True,
12
doc: str = None, persistent: bool = False,
13
*, inst = NetworkTables) -> property:
14
"""
15
Create a property that automatically synchronizes with NetworkTables.
16
17
Parameters:
18
- key: str. Full NetworkTables path (e.g., '/SmartDashboard/speed')
19
- defaultValue: Any. Default value if entry doesn't exist
20
- writeDefault: bool. Whether to write default value to NetworkTables
21
- doc: str, optional. Property docstring
22
- persistent: bool. Whether to make the NetworkTables entry persistent
23
- inst: NetworkTablesInstance. NetworkTables instance to use
24
25
Returns:
26
property: Python property descriptor that syncs with NetworkTables
27
"""
28
```
29
30
### SendableChooser Interface
31
32
Interface for WPILib SendableChooser objects over NetworkTables.
33
34
```python { .api }
35
class ChooserControl:
36
def __init__(self, key: str, on_choices=None, on_selected=None,
37
*, inst=NetworkTables):
38
"""
39
Create a SendableChooser interface.
40
41
Parameters:
42
- key: str. NetworkTables key for the chooser (e.g., 'Autonomous Mode')
43
- on_choices: Callable, optional. Function called when choices change
44
- on_selected: Callable, optional. Function called when selection changes
45
- inst: NetworkTablesInstance. NetworkTables instance to use
46
"""
47
48
def getChoices() -> List[str]:
49
"""
50
Get available choices from the chooser.
51
52
Returns:
53
List[str]: List of available choice strings
54
"""
55
56
def getSelected() -> Optional[str]:
57
"""
58
Get the currently selected choice.
59
60
Returns:
61
Optional[str]: Selected choice string, or None if nothing selected
62
"""
63
64
def setSelected(selection: str) -> None:
65
"""
66
Set the selected choice (if it exists in available choices).
67
68
Parameters:
69
- selection: str. Choice to select
70
"""
71
72
def close() -> None:
73
"""Clean up resources and remove listeners."""
74
```
75
76
## Usage Examples
77
78
### Basic Property Integration
79
80
```python
81
from networktables.util import ntproperty
82
from networktables import NetworkTables
83
84
class DriveSubsystem:
85
# Automatically sync with SmartDashboard
86
max_speed = ntproperty('/SmartDashboard/maxSpeed', 1.0, persistent=True)
87
current_speed = ntproperty('/SmartDashboard/currentSpeed', 0.0)
88
drive_mode = ntproperty('/SmartDashboard/driveMode', 'manual')
89
90
def __init__(self):
91
# Properties are available immediately
92
print(f"Initial max speed: {self.max_speed}")
93
94
def set_speed(self, speed):
95
# Clamp to maximum
96
speed = min(speed, self.max_speed)
97
self.current_speed = speed
98
# Value automatically appears on SmartDashboard
99
100
def get_speed(self):
101
return self.current_speed
102
103
# Usage
104
NetworkTables.initialize(server='roborio-1234-frc.local')
105
drive = DriveSubsystem()
106
107
# These property accesses automatically sync with NetworkTables
108
drive.max_speed = 0.8 # Updates SmartDashboard
109
current = drive.current_speed # Reads from SmartDashboard
110
```
111
112
### Advanced Property Configuration
113
114
```python
115
from networktables.util import ntproperty
116
from networktables import NetworkTables
117
118
class RobotConfiguration:
119
# Persistent configuration values
120
max_speed = ntproperty(
121
'/Config/maxSpeed',
122
1.0,
123
persistent=True,
124
doc="Maximum robot speed in m/s"
125
)
126
127
autonomous_delay = ntproperty(
128
'/Config/autonomousDelay',
129
0.0,
130
persistent=True,
131
doc="Delay before autonomous starts in seconds"
132
)
133
134
# Dashboard display values (not persistent)
135
battery_voltage = ntproperty('/SmartDashboard/batteryVoltage', 0.0)
136
connection_count = ntproperty('/SmartDashboard/connectionCount', 0)
137
138
# Vision processing parameters
139
camera_exposure = ntproperty('/Vision/exposure', 50, persistent=True)
140
target_threshold = ntproperty('/Vision/threshold', 128, persistent=True)
141
142
def __init__(self):
143
self.load_config()
144
145
def load_config(self):
146
"""Load configuration from NetworkTables."""
147
print(f"Max speed: {self.max_speed}")
148
print(f"Auto delay: {self.autonomous_delay}")
149
print(f"Camera exposure: {self.camera_exposure}")
150
151
def update_dashboard(self, voltage, connections):
152
"""Update dashboard values."""
153
self.battery_voltage = voltage
154
self.connection_count = connections
155
156
# Usage
157
config = RobotConfiguration()
158
config.max_speed = 0.75 # Automatically persistent
159
```
160
161
### Property Validation and Processing
162
163
```python
164
from networktables.util import ntproperty
165
from networktables import NetworkTables
166
167
class SmartMotorController:
168
_speed_raw = ntproperty('/Motors/speed', 0.0)
169
_enabled = ntproperty('/Motors/enabled', False)
170
171
@property
172
def speed(self):
173
"""Get motor speed with validation."""
174
return max(-1.0, min(1.0, self._speed_raw))
175
176
@speed.setter
177
def speed(self, value):
178
"""Set motor speed with validation."""
179
self._speed_raw = max(-1.0, min(1.0, value))
180
181
@property
182
def enabled(self):
183
"""Check if motor is enabled."""
184
return self._enabled
185
186
@enabled.setter
187
def enabled(self, value):
188
"""Enable/disable motor with safety check."""
189
if not value:
190
self._speed_raw = 0.0 # Stop motor when disabled
191
self._enabled = value
192
193
def emergency_stop(self):
194
"""Emergency stop with logging."""
195
print("EMERGENCY STOP ACTIVATED")
196
self.enabled = False
197
self.speed = 0.0
198
199
# Usage
200
motor = SmartMotorController()
201
motor.speed = 1.5 # Automatically clamped to 1.0
202
motor.enabled = True
203
```
204
205
### SendableChooser Integration
206
207
```python
208
from networktables.util import ChooserControl
209
from networktables import NetworkTables
210
211
class AutonomousSelector:
212
def __init__(self):
213
self.selected_mode = None
214
215
# Create chooser interface
216
self.chooser = ChooserControl(
217
'Autonomous Mode',
218
on_choices=self.on_choices_changed,
219
on_selected=self.on_selection_changed
220
)
221
222
def on_choices_changed(self, choices):
223
"""Called when available autonomous modes change."""
224
print(f"Available autonomous modes: {choices}")
225
226
def on_selection_changed(self, selected):
227
"""Called when autonomous mode selection changes."""
228
print(f"Selected autonomous mode: {selected}")
229
self.selected_mode = selected
230
231
def get_autonomous_mode(self):
232
"""Get the currently selected autonomous mode."""
233
return self.chooser.getSelected()
234
235
def set_default_mode(self, mode):
236
"""Set the default autonomous mode."""
237
choices = self.chooser.getChoices()
238
if mode in choices:
239
self.chooser.setSelected(mode)
240
else:
241
print(f"Warning: Mode '{mode}' not available in {choices}")
242
243
def cleanup(self):
244
"""Clean up resources."""
245
self.chooser.close()
246
247
# Usage in robot code
248
NetworkTables.initialize(server='roborio-1234-frc.local')
249
auto_selector = AutonomousSelector()
250
251
# Later in autonomous init
252
selected_mode = auto_selector.get_autonomous_mode()
253
if selected_mode == 'Defense':
254
run_defense_autonomous()
255
elif selected_mode == 'Shoot and Move':
256
run_shoot_and_move_autonomous()
257
258
# Cleanup when done
259
auto_selector.cleanup()
260
```
261
262
### Multiple Chooser Management
263
264
```python
265
from networktables.util import ChooserControl
266
from networktables import NetworkTables
267
268
class DashboardInterface:
269
def __init__(self):
270
self.choosers = {}
271
self.selections = {}
272
273
# Create multiple choosers
274
self.create_chooser('Autonomous Mode', self.on_auto_change)
275
self.create_chooser('Drive Mode', self.on_drive_change)
276
self.create_chooser('Camera Selection', self.on_camera_change)
277
278
def create_chooser(self, name, callback):
279
"""Create a chooser with callback."""
280
chooser = ChooserControl(name, on_selected=callback)
281
self.choosers[name] = chooser
282
self.selections[name] = None
283
284
def on_auto_change(self, selected):
285
"""Handle autonomous mode changes."""
286
self.selections['Autonomous Mode'] = selected
287
print(f"Autonomous mode: {selected}")
288
289
def on_drive_change(self, selected):
290
"""Handle drive mode changes."""
291
self.selections['Drive Mode'] = selected
292
print(f"Drive mode: {selected}")
293
294
# React to drive mode changes
295
if selected == 'Precision':
296
self.set_max_speed(0.5)
297
elif selected == 'Turbo':
298
self.set_max_speed(1.0)
299
300
def on_camera_change(self, selected):
301
"""Handle camera selection changes."""
302
self.selections['Camera Selection'] = selected
303
print(f"Active camera: {selected}")
304
305
def set_max_speed(self, speed):
306
"""Update max speed on dashboard."""
307
sd = NetworkTables.getTable('SmartDashboard')
308
sd.putNumber('maxSpeed', speed)
309
310
def get_selection(self, chooser_name):
311
"""Get current selection for a chooser."""
312
return self.selections.get(chooser_name)
313
314
def set_default_selections(self):
315
"""Set default selections for all choosers."""
316
defaults = {
317
'Autonomous Mode': 'Defense',
318
'Drive Mode': 'Normal',
319
'Camera Selection': 'Front'
320
}
321
322
for name, default in defaults.items():
323
if name in self.choosers:
324
self.choosers[name].setSelected(default)
325
326
def cleanup(self):
327
"""Clean up all choosers."""
328
for chooser in self.choosers.values():
329
chooser.close()
330
self.choosers.clear()
331
self.selections.clear()
332
333
# Usage
334
dashboard = DashboardInterface()
335
dashboard.set_default_selections()
336
337
# Access selections
338
auto_mode = dashboard.get_selection('Autonomous Mode')
339
drive_mode = dashboard.get_selection('Drive Mode')
340
341
# Cleanup when robot shuts down
342
dashboard.cleanup()
343
```
344
345
### Custom Property Types
346
347
```python
348
from networktables.util import ntproperty
349
from networktables import NetworkTables
350
import json
351
352
class RobotTelemetry:
353
# Simple numeric properties
354
x_position = ntproperty('/Robot/position/x', 0.0)
355
y_position = ntproperty('/Robot/position/y', 0.0)
356
heading = ntproperty('/Robot/heading', 0.0)
357
358
# JSON-encoded complex data
359
_pose_json = ntproperty('/Robot/pose', '{"x": 0, "y": 0, "angle": 0}')
360
361
@property
362
def pose(self):
363
"""Get robot pose as a dictionary."""
364
try:
365
return json.loads(self._pose_json)
366
except json.JSONDecodeError:
367
return {"x": 0, "y": 0, "angle": 0}
368
369
@pose.setter
370
def pose(self, pose_dict):
371
"""Set robot pose from a dictionary."""
372
self._pose_json = json.dumps(pose_dict)
373
374
# Array properties using Value objects
375
_targets_raw = ntproperty('/Vision/targets', [])
376
377
@property
378
def targets(self):
379
"""Get vision targets as list."""
380
return self._targets_raw if isinstance(self._targets_raw, list) else []
381
382
@targets.setter
383
def targets(self, target_list):
384
"""Set vision targets."""
385
self._targets_raw = target_list
386
387
def update_position(self, x, y, angle):
388
"""Update robot position."""
389
self.x_position = x
390
self.y_position = y
391
self.heading = angle
392
393
# Also update compound pose
394
self.pose = {"x": x, "y": y, "angle": angle}
395
396
def add_target(self, target_data):
397
"""Add a vision target."""
398
current_targets = self.targets
399
current_targets.append(target_data)
400
self.targets = current_targets
401
402
# Usage
403
telemetry = RobotTelemetry()
404
telemetry.update_position(1.5, 2.3, 45.0)
405
telemetry.add_target({"distance": 3.2, "angle": 15.0})
406
407
print(f"Current pose: {telemetry.pose}")
408
print(f"Targets: {telemetry.targets}")
409
```
410
411
## Best Practices
412
413
1. **Use descriptive paths**: Choose clear, hierarchical NetworkTables paths
414
2. **Make configuration persistent**: Use `persistent=True` for values that should survive restarts
415
3. **Validate inputs**: Add property setters with validation for critical values
416
4. **Clean up choosers**: Always call `close()` on ChooserControl objects
417
5. **Handle JSON errors**: Use try/catch when working with JSON-encoded properties
418
6. **Document properties**: Use the `doc` parameter to document property purpose
419
7. **Group related properties**: Organize properties by subsystem or functionality