Skip to content

Commit 151ed58

Browse files
authored
Merge pull request #127 from auscompgeek/magicbot-telemetry
Add loop timing telemetry to magicbot
2 parents c35ace7 + f56a21b commit 151ed58

File tree

2 files changed

+105
-32
lines changed

2 files changed

+105
-32
lines changed

magicbot/magicrobot.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
import inspect
33
import logging
44

5+
import hal
56
import wpilib
67

78
from networktables import NetworkTables
89
from wpilib.shuffleboard import Shuffleboard
910

10-
from robotpy_ext.misc import NotifierDelay
1111
from robotpy_ext.autonomous import AutonomousModeSelector
12-
12+
from robotpy_ext.misc import NotifierDelay
1313
from robotpy_ext.misc.orderedclass import OrderedClass
1414
from robotpy_ext.misc.annotations import get_class_annotations
1515

@@ -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,21 +320,31 @@ 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():
317327
if ds_attached != self.ds.isDSAttached():
318328
ds_attached = not ds_attached
319329
self.__nt.putBoolean("is_ds_attached", ds_attached)
320330

331+
hal.observeUserProgramDisabled()
321332
try:
322333
self.disabledPeriodic()
323334
except:
324335
self.onException()
336+
watchdog.addEpoch("disabledPeriodic()")
325337

326338
self._update_feedback()
327339
self.robotPeriodic()
340+
watchdog.addEpoch("robotPeriodic()")
341+
watchdog.disable()
342+
343+
if watchdog.isExpired():
344+
watchdog.printEpochs()
345+
328346
delay.wait()
347+
watchdog.reset()
329348

330349
def operatorControl(self):
331350
"""
@@ -335,6 +354,8 @@ def operatorControl(self):
335354
336355
.. warning:: Internal API, don't override
337356
"""
357+
watchdog = self.watchdog
358+
watchdog.reset()
338359

339360
self.__nt.putString("mode", "teleop")
340361
# don't need to update this during teleop -- presumably will switch
@@ -349,24 +370,36 @@ def operatorControl(self):
349370
self.teleopInit()
350371
except:
351372
self.onException(forceReport=True)
373+
watchdog.addEpoch("teleopInit()")
352374

353375
with NotifierDelay(self.control_loop_wait_time) as delay:
354376
while self.isOperatorControl() and self.isEnabled():
377+
hal.observeUserProgramTeleop()
355378
try:
356379
self.teleopPeriodic()
357380
except:
358381
self.onException()
382+
watchdog.addEpoch("teleopPeriodic()")
359383

360384
self._execute_components()
385+
361386
self._update_feedback()
362387
self.robotPeriodic()
388+
watchdog.addEpoch("robotPeriodic()")
389+
watchdog.disable()
390+
391+
if watchdog.isExpired():
392+
watchdog.printEpochs()
363393

364394
delay.wait()
395+
watchdog.reset()
365396

366397
self._on_mode_disable_components()
367398

368399
def test(self):
369400
"""Called when the robot is in test mode"""
401+
watchdog = self.watchdog
402+
watchdog.reset()
370403

371404
self.__nt.putString("mode", "test")
372405
self.__nt.putBoolean("is_ds_attached", self.ds.isDSAttached())
@@ -376,33 +409,45 @@ def test(self):
376409
self.testInit()
377410
except:
378411
self.onException(forceReport=True)
412+
watchdog.addEpoch("testInit()")
379413

380414
with NotifierDelay(self.control_loop_wait_time) as delay:
381415
while self.isTest() and self.isEnabled():
416+
hal.observeUserProgramTest()
382417
try:
383418
self.testPeriodic()
384419
except:
385420
self.onException()
421+
watchdog.addEpoch("testPeriodic()")
386422

387423
self._update_feedback()
388424
self.robotPeriodic()
425+
watchdog.addEpoch("robotPeriodic()")
426+
watchdog.disable()
427+
428+
if watchdog.isExpired():
429+
watchdog.printEpochs()
430+
389431
delay.wait()
432+
watchdog.reset()
390433

391434
def _on_mode_enable_components(self):
392435
# initialize things
393-
for component in self._components:
394-
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:
395439
try:
396-
component.on_enable()
440+
on_enable()
397441
except:
398442
self.onException(forceReport=True)
399443

400444
def _on_mode_disable_components(self):
401445
# deinitialize things
402-
for component in self._components:
403-
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:
404449
try:
405-
component.on_disable()
450+
on_disable()
406451
except:
407452
self.onException(forceReport=True)
408453

@@ -492,7 +537,6 @@ def _create_components(self):
492537

493538
# For each new component, perform magic injection
494539
for cname, component in components:
495-
self._components.append(component)
496540
setup_tunables(component, cname, "components")
497541
self._feedbacks += collect_feedbacks(component, cname, "components")
498542
self._setup_vars(cname, component)
@@ -518,6 +562,8 @@ def _create_components(self):
518562
if hasattr(mode, "setup"):
519563
mode.setup()
520564

565+
self._components = components
566+
521567
def _create_component(self, name, ctyp):
522568
# Create instance, set it on self
523569
component = ctyp()
@@ -638,13 +684,19 @@ def _update_feedback(self):
638684
self.onException()
639685
continue
640686
entry.setValue(value)
687+
self.watchdog.addEpoch("@magicbot.feedback")
641688

642689
def _execute_components(self):
643-
for component in self._components:
690+
for name, component in self._components:
644691
try:
645692
component.execute()
646693
except:
647694
self.onException()
695+
self.watchdog.addEpoch(name)
648696

649697
for reset_dict, component in self._reset_components:
650698
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: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
from glob import glob
21
import importlib
32
import inspect
4-
import os
5-
63
import logging
4+
import os
5+
from glob import glob
76

8-
logger = logging.getLogger("autonomous")
7+
import hal
8+
import wpilib
99

1010
from ..misc.precise_delay import NotifierDelay
1111

12-
import wpilib
12+
logger = logging.getLogger("autonomous")
1313

1414

1515
class AutonomousModeSelector:
@@ -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,25 +243,36 @@ 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
239251
#
240252

241253
with NotifierDelay(control_loop_wait_time) as delay:
242254
while self.ds.isAutonomous() and self.ds.isEnabled():
255+
hal.observeUserProgramAutonomous()
243256
try:
244257
self._on_iteration(timer.get())
245258
except:
246259
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()
247269

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

254273
delay.wait()
274+
if watchdog:
275+
watchdog.reset()
255276

256277
#
257278
# Done with autonomous, finish up
@@ -284,7 +305,7 @@ def _on_autonomous_enable(self):
284305
self.active_mode = self.chooser.getSelected()
285306

286307
if self.active_mode is not None:
287-
logger.info("Enabling '%s'" % self.active_mode.MODE_NAME)
308+
logger.info("Enabling '%s'", self.active_mode.MODE_NAME)
288309
self.active_mode.on_enable()
289310
else:
290311
logger.warning(

0 commit comments

Comments
 (0)