Skip to content

Commit f56a21b

Browse files
committed
Add loop timing watchdog to magicbot
1 parent 0f04ac4 commit f56a21b

File tree

2 files changed

+94
-25
lines changed

2 files changed

+94
-25
lines changed

magicbot/magicrobot.py

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class MagicRobot(wpilib.SampleRobot, metaclass=OrderedClass):
3434
3535
MagicRobot uses the :class:`.AutonomousModeSelector` to allow you
3636
to define multiple autonomous modes and to select one of them via
37-
the SmartDashboard/SFX.
38-
37+
the SmartDashboard/Shuffleboard.
38+
3939
MagicRobot will set the following NetworkTables variables
4040
automatically:
4141
@@ -78,6 +78,8 @@ def robotInit(self):
7878
self.__nt.putBoolean("is_simulation", self.isSimulation())
7979
self.__nt.putBoolean("is_ds_attached", self.ds.isDSAttached())
8080

81+
self.watchdog = wpilib.Watchdog(self.control_loop_wait_time, self._loop_overrun)
82+
8183
def createObjects(self):
8284
"""
8385
You should override this and initialize all of your wpilib
@@ -183,9 +185,13 @@ def robotPeriodic(self):
183185
The default implementation will update
184186
SmartDashboard, LiveWindow and Shuffleboard.
185187
"""
188+
watchdog = self.watchdog
186189
wpilib.SmartDashboard.updateValues()
190+
watchdog.addEpoch("SmartDashboard")
187191
wpilib.LiveWindow.updateValues()
192+
watchdog.addEpoch("LiveWindow")
188193
Shuffleboard.update()
194+
watchdog.addEpoch("Shuffleboard")
189195

190196
def onException(self, forceReport=False):
191197
"""
@@ -289,6 +295,7 @@ def autonomous(self):
289295
self.control_loop_wait_time,
290296
(self._execute_components, self._update_feedback, self.robotPeriodic),
291297
self.onException,
298+
watchdog=self.watchdog,
292299
)
293300

294301
self._on_mode_disable_components()
@@ -301,6 +308,8 @@ def disabled(self):
301308
302309
.. warning:: Internal API, don't override
303310
"""
311+
watchdog = self.watchdog
312+
watchdog.reset()
304313

305314
self.__nt.putString("mode", "disabled")
306315
ds_attached = None
@@ -311,6 +320,7 @@ def disabled(self):
311320
self.disabledInit()
312321
except:
313322
self.onException(forceReport=True)
323+
watchdog.addEpoch("disabledInit()")
314324

315325
with NotifierDelay(self.control_loop_wait_time) as delay:
316326
while self.isDisabled():
@@ -323,10 +333,18 @@ def disabled(self):
323333
self.disabledPeriodic()
324334
except:
325335
self.onException()
336+
watchdog.addEpoch("disabledPeriodic()")
326337

327338
self._update_feedback()
328339
self.robotPeriodic()
340+
watchdog.addEpoch("robotPeriodic()")
341+
watchdog.disable()
342+
343+
if watchdog.isExpired():
344+
watchdog.printEpochs()
345+
329346
delay.wait()
347+
watchdog.reset()
330348

331349
def operatorControl(self):
332350
"""
@@ -336,6 +354,8 @@ def operatorControl(self):
336354
337355
.. warning:: Internal API, don't override
338356
"""
357+
watchdog = self.watchdog
358+
watchdog.reset()
339359

340360
self.__nt.putString("mode", "teleop")
341361
# don't need to update this during teleop -- presumably will switch
@@ -350,6 +370,7 @@ def operatorControl(self):
350370
self.teleopInit()
351371
except:
352372
self.onException(forceReport=True)
373+
watchdog.addEpoch("teleopInit()")
353374

354375
with NotifierDelay(self.control_loop_wait_time) as delay:
355376
while self.isOperatorControl() and self.isEnabled():
@@ -358,17 +379,27 @@ def operatorControl(self):
358379
self.teleopPeriodic()
359380
except:
360381
self.onException()
382+
watchdog.addEpoch("teleopPeriodic()")
361383

362384
self._execute_components()
385+
363386
self._update_feedback()
364387
self.robotPeriodic()
388+
watchdog.addEpoch("robotPeriodic()")
389+
watchdog.disable()
390+
391+
if watchdog.isExpired():
392+
watchdog.printEpochs()
365393

366394
delay.wait()
395+
watchdog.reset()
367396

368397
self._on_mode_disable_components()
369398

370399
def test(self):
371400
"""Called when the robot is in test mode"""
401+
watchdog = self.watchdog
402+
watchdog.reset()
372403

373404
self.__nt.putString("mode", "test")
374405
self.__nt.putBoolean("is_ds_attached", self.ds.isDSAttached())
@@ -378,6 +409,7 @@ def test(self):
378409
self.testInit()
379410
except:
380411
self.onException(forceReport=True)
412+
watchdog.addEpoch("testInit()")
381413

382414
with NotifierDelay(self.control_loop_wait_time) as delay:
383415
while self.isTest() and self.isEnabled():
@@ -386,26 +418,36 @@ def test(self):
386418
self.testPeriodic()
387419
except:
388420
self.onException()
421+
watchdog.addEpoch("testPeriodic()")
389422

390423
self._update_feedback()
391424
self.robotPeriodic()
425+
watchdog.addEpoch("robotPeriodic()")
426+
watchdog.disable()
427+
428+
if watchdog.isExpired():
429+
watchdog.printEpochs()
430+
392431
delay.wait()
432+
watchdog.reset()
393433

394434
def _on_mode_enable_components(self):
395435
# initialize things
396-
for component in self._components:
397-
if hasattr(component, "on_enable"):
436+
for _, component in self._components:
437+
on_enable = getattr(component, "on_enable", None)
438+
if on_enable is not None:
398439
try:
399-
component.on_enable()
440+
on_enable()
400441
except:
401442
self.onException(forceReport=True)
402443

403444
def _on_mode_disable_components(self):
404445
# deinitialize things
405-
for component in self._components:
406-
if hasattr(component, "on_disable"):
446+
for _, component in self._components:
447+
on_disable = getattr(component, "on_disable", None)
448+
if on_disable is not None:
407449
try:
408-
component.on_disable()
450+
on_disable()
409451
except:
410452
self.onException(forceReport=True)
411453

@@ -495,7 +537,6 @@ def _create_components(self):
495537

496538
# For each new component, perform magic injection
497539
for cname, component in components:
498-
self._components.append(component)
499540
setup_tunables(component, cname, "components")
500541
self._feedbacks += collect_feedbacks(component, cname, "components")
501542
self._setup_vars(cname, component)
@@ -521,6 +562,8 @@ def _create_components(self):
521562
if hasattr(mode, "setup"):
522563
mode.setup()
523564

565+
self._components = components
566+
524567
def _create_component(self, name, ctyp):
525568
# Create instance, set it on self
526569
component = ctyp()
@@ -641,13 +684,19 @@ def _update_feedback(self):
641684
self.onException()
642685
continue
643686
entry.setValue(value)
687+
self.watchdog.addEpoch("@magicbot.feedback")
644688

645689
def _execute_components(self):
646-
for component in self._components:
690+
for name, component in self._components:
647691
try:
648692
component.execute()
649693
except:
650694
self.onException()
695+
self.watchdog.addEpoch(name)
651696

652697
for reset_dict, component in self._reset_components:
653698
component.__dict__.update(reset_dict)
699+
700+
def _loop_overrun(self):
701+
# TODO: print something here without making it extremely annoying
702+
pass

robotpy_ext/autonomous/selector.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ def __init__(self, autonomous_pkgname, *args, **kwargs):
118118
# on the field..
119119

120120
for name, obj in inspect.getmembers(module, inspect.isclass):
121-
122-
if hasattr(obj, "MODE_NAME"):
121+
mode_name = getattr(obj, "MODE_NAME", None)
122+
if mode_name is not None:
123123

124124
# don't allow the driver to select this mode
125-
if hasattr(obj, "DISABLED") and obj.DISABLED:
125+
if getattr(obj, "DISABLED", False):
126126
logger.warning(
127127
"autonomous mode %s is marked as disabled", obj.MODE_NAME
128128
)
@@ -137,16 +137,15 @@ def __init__(self, autonomous_pkgname, *args, **kwargs):
137137
else:
138138
continue
139139

140-
if instance.MODE_NAME in self.modes:
140+
if mode_name in self.modes:
141141
if not self.ds.isFMSAttached():
142142
raise RuntimeError(
143-
"Duplicate name %s in %s"
144-
% (instance.MODE_NAME, module_filename)
143+
"Duplicate name %s in %s" % (mode_name, module_filename)
145144
)
146145

147146
logger.error(
148147
"Duplicate name %s specified by object type %s in module %s",
149-
instance.MODE_NAME,
148+
mode_name,
150149
name,
151150
module_filename,
152151
)
@@ -166,7 +165,7 @@ def __init__(self, autonomous_pkgname, *args, **kwargs):
166165
logger.info("Loaded autonomous modes:")
167166
for k, v in sorted(self.modes.items()):
168167

169-
if hasattr(v, "DEFAULT") and v.DEFAULT == True:
168+
if getattr(v, "DEFAULT", False):
170169
logger.info(" -> %s [Default]", k)
171170
self.chooser.setDefaultOption(k, v)
172171
default_modes.append(k)
@@ -198,7 +197,13 @@ def __init__(self, autonomous_pkgname, *args, **kwargs):
198197

199198
logger.info("Autonomous switcher initialized")
200199

201-
def run(self, control_loop_wait_time=0.020, iter_fn=None, on_exception=None):
200+
def run(
201+
self,
202+
control_loop_wait_time=0.020,
203+
iter_fn=None,
204+
on_exception=None,
205+
watchdog: wpilib.Watchdog = None,
206+
):
202207
"""
203208
This function does everything required to implement autonomous
204209
mode behavior. You should call this from your autonomous mode
@@ -215,12 +220,17 @@ def run(self, control_loop_wait_time=0.020, iter_fn=None, on_exception=None):
215220
autonomous mode is executing
216221
:param on_exception: Called when an uncaught exception is raised,
217222
must take a single keyword arg "forceReport"
223+
:param watchdog: a WPILib Watchdog to feed every iteration
218224
"""
225+
if watchdog:
226+
watchdog.reset()
219227

220228
logger.info("Begin autonomous")
221229

222230
if iter_fn is None:
223231
iter_fn = lambda: None
232+
if not isinstance(iter_fn, (list, tuple)):
233+
iter_fn = (iter_fn,)
224234

225235
if on_exception is None:
226236
on_exception = self._on_exception
@@ -233,6 +243,8 @@ def run(self, control_loop_wait_time=0.020, iter_fn=None, on_exception=None):
233243
self._on_autonomous_enable()
234244
except:
235245
on_exception(forceReport=True)
246+
if watchdog:
247+
watchdog.addEpoch("auto on_enable")
236248

237249
#
238250
# Autonomous control loop
@@ -245,14 +257,22 @@ def run(self, control_loop_wait_time=0.020, iter_fn=None, on_exception=None):
245257
self._on_iteration(timer.get())
246258
except:
247259
on_exception()
260+
if watchdog:
261+
watchdog.addEpoch("auto on_iteration")
262+
263+
for fn in iter_fn:
264+
fn()
265+
266+
if watchdog:
267+
watchdog.addEpoch("robotPeriodic()")
268+
watchdog.disable()
248269

249-
if isinstance(iter_fn, (list, tuple)):
250-
for fn in iter_fn:
251-
fn()
252-
else:
253-
iter_fn()
270+
if watchdog.isExpired():
271+
watchdog.printEpochs()
254272

255273
delay.wait()
274+
if watchdog:
275+
watchdog.reset()
256276

257277
#
258278
# Done with autonomous, finish up
@@ -285,7 +305,7 @@ def _on_autonomous_enable(self):
285305
self.active_mode = self.chooser.getSelected()
286306

287307
if self.active_mode is not None:
288-
logger.info("Enabling '%s'" % self.active_mode.MODE_NAME)
308+
logger.info("Enabling '%s'", self.active_mode.MODE_NAME)
289309
self.active_mode.on_enable()
290310
else:
291311
logger.warning(

0 commit comments

Comments
 (0)