1- from typing import Callable , List
1+ from typing import Callable , Dict , List
22
33import magicbot
4- from wpilib import DriverStation , SmartDashboard , Timer
4+ from wpilib import DriverStation , Timer
55
6- from lemonlib .smart import SmartPreference
6+ from lemonlib .smart import SmartNT , SmartPreference
77
88
99class LemonRobot (magicbot .MagicRobot ):
@@ -18,14 +18,25 @@ class LemonRobot(magicbot.MagicRobot):
1818 watchdog_profile = SmartPreference (False )
1919 watchdog_profile_period = SmartPreference (0.25 )
2020
21+ # EMA coefficient (0 < alpha <= 1)
22+ watchdog_ema_alpha = SmartPreference (0.25 )
23+
2124 def __init__ (self ):
2225 super ().__init__ ()
26+
2327 self ._periodic_callbacks : List [List ] = []
2428
2529 self .loop_time = self .control_loop_wait_time
2630 self ._last_watchdog_profile_time = 0.0
2731 self ._overrun_count = 0
28- self ._last_overrun_epochs : dict = {}
32+
33+ # Profiling storage
34+ self ._last_overrun_epochs : Dict [str , float ] = {}
35+
36+ self ._epoch_ema_all : Dict [str , float ] = {}
37+ self ._epoch_ema_overrun : Dict [str , float ] = {}
38+
39+ self ._smart_nt = SmartNT ("LemonRobot" )
2940
3041 def add_periodic (self , callback : Callable [[], None ], period : float ):
3142 now = Timer .getFPGATimestamp ()
@@ -40,27 +51,13 @@ def _run_periodics(self):
4051 callback ()
4152
4253 def autonomousPeriodic (self ):
43- """
44- Periodic code for autonomous mode should go here.
45- Runs when not enabled for trajectory display.
46-
47- Users should override this method for code which will be called
48- periodically at a regular rate while the robot is in autonomous mode.
49-
50- This code executes before the ``execute`` functions of all
51- components are called.
52- """
5354 pass
5455
5556 def autonomous (self ):
5657 super ().autonomous ()
5758 self .autonomousPeriodic ()
5859
5960 def enabledperiodic (self ) -> None :
60- """Periodic code for when the bot is enabled should go here.
61- Runs when not enabled for trajectory display.
62-
63- Users should override this method for code which will be called"""
6461 pass
6562
6663 def _on_mode_enable_components (self ):
@@ -71,13 +68,11 @@ def on_enable(self):
7168 pass
7269
7370 def _enabled_periodic (self ) -> None :
74- """Run components and all periodic methods."""
7571 watchdog = self .watchdog
7672
7773 for name , component in self ._components :
7874 try :
7975 component .execute ()
80-
8176 except Exception :
8277 self .onException ()
8378 watchdog .addEpoch (name )
@@ -91,67 +86,117 @@ def _enabled_periodic(self) -> None:
9186 def _do_periodics (self ):
9287 super ()._do_periodics ()
9388
94- is_overrun = self .watchdog .getTime () > self .control_loop_wait_time
89+ if not self .watchdog_profile :
90+ self .loop_time = max (self .control_loop_wait_time , self .watchdog .getTime ())
91+ return
92+
93+ wd = self .watchdog
94+ loop_time = wd .getTime ()
95+ is_overrun = loop_time > self .control_loop_wait_time
96+
97+ epochs = wd ._epochs
98+ prev = wd ._startTime
99+
100+ alpha = float (self .watchdog_ema_alpha )
101+ one_minus_alpha = 1.0 - alpha
102+
103+ ema_all = self ._epoch_ema_all
104+ ema_overrun = self ._epoch_ema_overrun
105+ last_overrun = self ._last_overrun_epochs
106+
107+ for key , value in epochs :
108+ delta = (value - prev ) * 1e-6
109+
110+ prev_ema = ema_all .get (key )
111+ if prev_ema is None :
112+ ema_all [key ] = delta
113+ else :
114+ ema_all [key ] = alpha * delta + one_minus_alpha * prev_ema
115+
116+ prev = value
117+
95118 if is_overrun :
96119 self ._overrun_count += 1
97- # Capture epoch data for overrun
98- prev = self .watchdog ._startTime
99- now = self .watchdog ._get_time ()
100- for key , value in self .watchdog ._epochs :
101- self ._last_overrun_epochs [key ] = (value - prev ) / 1e6
120+
121+ prev = wd ._startTime
122+ for key , value in epochs :
123+ delta = (value - prev ) * 1e-6
124+ last_overrun [key ] = delta
125+
126+ prev_ema = ema_overrun .get (key )
127+ if prev_ema is None :
128+ ema_overrun [key ] = delta
129+ else :
130+ ema_overrun [key ] = alpha * delta + one_minus_alpha * prev_ema
131+
102132 prev = value
103133
104- if self . watchdog_profile :
105- now_fpga = Timer . getFPGATimestamp ()
106- if (
107- now_fpga - self . _last_watchdog_profile_time
108- >= self . watchdog_profile_period
109- ):
110- self . _last_watchdog_profile_time = now_fpga
111-
112- now = self .watchdog . _get_time ( )
113- self ._lastEpochsPrintTime = now
114- prev = self .watchdog . _startTime
115- max_epoch_time = 0.0
116- max_epoch_key = ""
117- for key , value in self .watchdog . _epochs :
118- time = ( value - prev ) / 1e6
119- prev = value
120- if time > max_epoch_time :
121- max_epoch_time = time
122- max_epoch_key = key
123- SmartDashboard . putNumber ( f"Watchdog Epochs/ { key } " , time )
124-
125- total_time = ( now - self . watchdog . _startTime ) / 1e6
126- SmartDashboard . putNumber ( "Watchdog Epochs/Total" , total_time )
127- SmartDashboard . putNumber ( "Watchdog Epochs/Max" , max_epoch_time )
128- SmartDashboard . putString ( "Watchdog Epochs/MaxKey" , max_epoch_key )
129-
130- SmartDashboard . putNumber ( "Watchdog/LoopTime" , self . watchdog . getTime () )
131- SmartDashboard . putNumber (
132- "Watchdog/ControlPeriod " , self . control_loop_wait_time
134+ now_fpga = Timer . getFPGATimestamp ()
135+ if now_fpga - self . _last_watchdog_profile_time >= self . watchdog_profile_period :
136+ self . _last_watchdog_profile_time = now_fpga
137+
138+ start = wd . _startTime
139+ now = wd . _get_time ()
140+ total_time = ( now - start ) * 1e-6
141+
142+ self ._smart_nt . put_number ( "Watchdog/LoopTime" , round ( loop_time , 6 ) )
143+ self ._smart_nt . put_number (
144+ "Watchdog/ControlPeriod" , round ( self .control_loop_wait_time , 6 )
145+ )
146+ self . _smart_nt . put_boolean ( "Watchdog/Overrun" , is_overrun )
147+ self . _smart_nt . put_number ( "Watchdog/OverrunCount" , self ._overrun_count )
148+ self . _smart_nt . put_number ( "Watchdog Epochs/Total" , round ( total_time , 6 ))
149+
150+ if last_overrun :
151+ max_last = 0.0
152+ total_last = 0.0
153+ for v in last_overrun . values ():
154+ total_last += v
155+ if v > max_last :
156+ max_last = v
157+
158+ self . _smart_nt . put_number (
159+ "Watchdog LastOverrun/Max" , round ( max_last , 6 )
160+ )
161+ self . _smart_nt . put_number (
162+ "Watchdog LastOverrun/Total " , round ( total_last , 6 )
133163 )
134- SmartDashboard .putBoolean (
135- "Watchdog/Overrun" ,
136- self .watchdog .getTime () > self .control_loop_wait_time ,
164+
165+ for k , v in last_overrun .items ():
166+ self ._smart_nt .put_number (f"Watchdog LastOverrun/{ k } " , round (v , 6 ))
167+
168+ if ema_all :
169+ max_all = 0.0
170+ total_all = 0.0
171+ for v in ema_all .values ():
172+ total_all += v
173+ if v > max_all :
174+ max_all = v
175+
176+ self ._smart_nt .put_number ("Watchdog EMA/Max" , round (max_all , 6 ))
177+ self ._smart_nt .put_number ("Watchdog EMA/Total" , round (total_all , 6 ))
178+
179+ for k , v in ema_all .items ():
180+ self ._smart_nt .put_number (f"Watchdog EMA/{ k } " , round (v , 6 ))
181+
182+ if ema_overrun :
183+ max_or = 0.0
184+ total_or = 0.0
185+ for v in ema_overrun .values ():
186+ total_or += v
187+ if v > max_or :
188+ max_or = v
189+
190+ self ._smart_nt .put_number ("Watchdog EMAOverrun/Max" , round (max_or , 6 ))
191+ self ._smart_nt .put_number (
192+ "Watchdog EMAOverrun/Total" , round (total_or , 6 )
137193 )
138- SmartDashboard .putNumber ("Watchdog/OverrunCount" , self ._overrun_count )
139-
140- # Display last overrun epochs
141- if self ._last_overrun_epochs :
142- max_overrun_time = max (self ._last_overrun_epochs .values ())
143- total_overrun_time = sum (self ._last_overrun_epochs .values ())
144- SmartDashboard .putNumber (
145- "Watchdog LastOverrun/Max" , max_overrun_time
146- )
147- SmartDashboard .putNumber (
148- "Watchdog LastOverrun/Total" , total_overrun_time
149- )
150- for key , time in self ._last_overrun_epochs .items ():
151- SmartDashboard .putNumber (f"Watchdog LastOverrun/{ key } " , time )
152-
153- self .watchdog .addEpoch ("watchdog_profile" )
154- self .loop_time = max (self .control_loop_wait_time , self .watchdog .getTime ())
194+
195+ for k , v in ema_overrun .items ():
196+ self ._smart_nt .put_number (f"Watchdog EMAOverrun/{ k } " , round (v , 6 ))
197+
198+ wd .addEpoch ("watchdog_profile" )
199+ self .loop_time = max (self .control_loop_wait_time , loop_time )
155200
156201 def get_period (self ) -> float :
157202 """Get the period of the robot loop in seconds."""
0 commit comments