1+ import traceback
2+ from sys import argv
13from typing import Any , Callable , Iterable , ClassVar
24from heapq import heappush , heappop , _siftup
35from hal import (
2527
2628class _Callback :
2729
28- __slots__ = "func" , "_periodUs" , "expirationUs"
29-
3030 def __init__ (
3131 self ,
3232 func : Callable [[], None ],
@@ -100,21 +100,14 @@ def __repr__(self) -> str:
100100
101101class _OrderedListSort :
102102
103- __slots__ = "_data"
104-
105103 def __init__ (self ) -> None :
106104 self ._data : list [Any ] = []
107105
108106 def add (self , item : Any ) -> None :
109107 self ._data .append (item )
110108 self ._data .sort ()
111109
112- def pop (self ) -> Any :
113- return self ._data .pop ()
114-
115- def peek (
116- self ,
117- ) -> Any : # todo change to Any | None when we don't build with python 3.9
110+ def peek (self ) -> Any :
118111 if self ._data :
119112 return self ._data [0 ]
120113 else :
@@ -138,20 +131,13 @@ def __repr__(self) -> str:
138131
139132class _OrderedListMin :
140133
141- __slots__ = "_data"
142-
143134 def __init__ (self ) -> None :
144135 self ._data : list [Any ] = []
145136
146137 def add (self , item : Any ) -> None :
147138 self ._data .append (item )
148139
149- # def pop(self) -> Any:
150- # return self._data.pop()
151-
152- def peek (
153- self ,
154- ) -> Any : # todo change to Any | None when we don't build with python 3.9
140+ def peek (self ) -> Any :
155141 if self ._data :
156142 return min (self ._data )
157143 else :
@@ -175,24 +161,14 @@ def __repr__(self) -> str:
175161
176162class _OrderedListHeapq :
177163
178- __slots__ = "_data"
179-
180164 def __init__ (self ) -> None :
181165 self ._data : list [Any ] = []
182166
183167 def add (self , item : Any ) -> None :
184168 heappush (self ._data , item )
185169
186- def pop (self ) -> Any :
187- return heappop (self ._data )
188-
189- def peek (
190- self ,
191- ) -> Any : # todo change to Any | None when we don't build with python 3.9
192- if self ._data :
193- return self ._data [0 ]
194- else :
195- return None
170+ def peek (self ) -> Any :
171+ return self ._data [0 ]
196172
197173 def reorderListAfterAChangeInTheFirstElement (self ):
198174 _siftup (self ._data , 0 )
@@ -209,9 +185,23 @@ def __contains__(self, item) -> bool:
209185 def __repr__ (self ) -> str :
210186 return str (sorted (self ._data ))
211187
188+ # Hooks to use timeit to evaluate configurations of TimedRobotPy
189+ _OrderedList = _OrderedListSort
190+ _initializeNotifier = initializeNotifier
191+ _setNotifierName = setNotifierName
192+ _observeUserProgramStarting = observeUserProgramStarting
193+ _updateNotifierAlarm = updateNotifierAlarm
194+ _waitForNotifierAlarm = waitForNotifierAlarm
195+ _stopNotifier = stopNotifier
196+ _report = report
197+ _IterativeRobotPy = IterativeRobotPy
198+ if '_IterativeRobotPyIsObject' in argv :
199+ print ("_IterativeRobotPyIsObject" )
200+ _IterativeRobotPy = object
201+
212202
213203# todo what should the name of this class be?
214- class TimedRobotPy (IterativeRobotPy ):
204+ class TimedRobotPy (_IterativeRobotPy ):
215205 """
216206 TimedRobotPy implements the IterativeRobotBase robot program framework.
217207
@@ -232,26 +222,30 @@ def __init__(self, period: wpimath.units.seconds = kDefaultPeriod) -> None:
232222
233223 :param period: period of the main robot periodic loop in seconds.
234224 """
235- super ().__init__ (period )
225+ if "_IterativeRobotPyIsObject" in argv :
226+ super ().__init__ ()
227+ self ._periodS = period
228+ else :
229+ super ().__init__ (period )
236230
237231 # All periodic functions created by addPeriodic are relative
238232 # to this self._startTimeUs
239233 self ._startTimeUs = _getFPGATime ()
240- self ._callbacks = _OrderedListSort ()
234+ self ._callbacks = _OrderedList ()
241235 self ._loopStartTimeUs = 0
242236 self .addPeriodic (self ._loopFunc , period = self ._periodS )
243237
244- self ._notifier , status = initializeNotifier ()
238+ self ._notifier , status = _initializeNotifier ()
245239 if status != 0 :
246240 raise RuntimeError (
247241 f"initializeNotifier() returned { self ._notifier } , { status } "
248242 )
249243
250- status = setNotifierName (self ._notifier , "TimedRobotPy" )
244+ status = _setNotifierName (self ._notifier , "TimedRobotPy" )
251245 if status != 0 :
252246 raise RuntimeError (f"setNotifierName() returned { status } " )
253247
254- report (_kResourceType_Framework , _kFramework_Timed )
248+ _report (_kResourceType_Framework , _kFramework_Timed )
255249
256250 def startCompetition (self ) -> None :
257251 """
@@ -265,67 +259,88 @@ def startCompetition(self) -> None:
265259
266260 # Tell the DS that the robot is ready to be enabled
267261 print ("********** Robot program startup complete **********" , flush = True )
268- observeUserProgramStarting ()
262+ _observeUserProgramStarting ()
269263
270264 # Loop forever, calling the appropriate mode-dependent function
271- # (really not forever, there is a check for a break)
272- while True :
273- # We don't have to check there's an element in the queue first because
274- # there's always at least one (the constructor adds one). It's re-enqueued
275- # at the end of the loop.
276- callback = self ._callbacks .peek ()
277-
278- status = updateNotifierAlarm (self ._notifier , callback .expirationUs )
279- if status != 0 :
280- raise RuntimeError (f"updateNotifierAlarm() returned { status } " )
281-
282- self ._loopStartTimeUs , status = waitForNotifierAlarm (self ._notifier )
283-
284- # The C++ code that this was based upon used the following line to establish
285- # the loopStart time. Uncomment it and
286- # the "self._loopStartTimeUs = startTimeUs" further below to emulate the
287- # legacy behavior.
288- # startTimeUs = _getFPGATime() # uncomment this for legacy behavior
289-
290- if status != 0 :
291- raise RuntimeError (
292- f"waitForNotifierAlarm() returned _loopStartTimeUs={ self ._loopStartTimeUs } status={ status } "
293- )
294-
295- if self ._loopStartTimeUs == 0 :
296- # when HAL_StopNotifier(self.notifier) is called the above waitForNotifierAlarm
297- # will return a _loopStartTimeUs==0 and the API requires robots to stop any loops.
298- # See the API for waitForNotifierAlarm
299- break
300-
301- # On a RoboRio 2, the following print statement results in values like:
302- # print(f"expUs={callback.expirationUs} current={self._loopStartTimeUs}, legacy={startTimeUs}")
303- # [2.27] expUs=3418017 current=3418078, legacy=3418152
304- # [2.29] expUs=3438017 current=3438075, legacy=3438149
305- # This indicates that there is about 60 microseconds of skid from
306- # callback.expirationUs to self._loopStartTimeUs
307- # and there is about 70 microseconds of skid from self._loopStartTimeUs to startTimeUs.
308- # Consequently, this code uses "self._loopStartTimeUs, status = waitForNotifierAlarm"
309- # to establish loopStartTime, rather than slowing down the code by adding an extra call to
310- # "startTimeUs = _getFPGATime()".
311-
312- # self._loopStartTimeUs = startTimeUs # Uncomment this line for legacy behavior.
313-
314- self ._runCallbackAtHeadOfListAndReschedule (callback )
315-
316- # Process all other callbacks that are ready to run
317- # Changing the comparison to be _getFPGATime() rather than
318- # self._loopStartTimeUs would also be correct.
319- while (
320- callback := self ._callbacks .peek ()
321- ).expirationUs <= _getFPGATime ():
322- self ._runCallbackAtHeadOfListAndReschedule (callback )
265+ # (really not forever, there is a check for a stop)
266+ while self ._bodyOfMainLoop ():
267+ pass
268+ print ("Reached after while self._bodyOfMainLoop(): " , flush = True )
269+
270+ except Exception as e :
271+ # Print the exception type and message
272+ print (f"Exception caught: { type (e ).__name__ } : { e } " )
273+
274+ # Print the stack trace
275+ print ("Stack trace:" )
276+ traceback .print_exc ()
277+
278+ # Alternatively, get the formatted traceback as a string:
279+ # formatted_traceback = traceback.format_exc()
280+ # print(formatted_traceback)
281+
282+ # Rethrow the exception to propagate it up the call stack
283+ raise
284+
323285 finally :
286+ print ("Reached after finally: self._stopNotifier(): " , flush = True )
324287 # pytests hang on PC when we don't force a call to self._stopNotifier()
325288 self ._stopNotifier ()
326289
290+ def _bodyOfMainLoop (self ) -> bool :
291+ keepGoing = True
292+ # We don't have to check there's an element in the queue first because
293+ # there's always at least one (the constructor adds one).
294+ callback = self ._callbacks .peek ()
295+
296+ status = _updateNotifierAlarm (self ._notifier , callback .expirationUs )
297+ if status != 0 :
298+ raise RuntimeError (f"updateNotifierAlarm() returned { status } " )
299+
300+ self ._loopStartTimeUs , status = _waitForNotifierAlarm (self ._notifier )
301+
302+ # The C++ code that this was based upon used the following line to establish
303+ # the loopStart time. Uncomment it and
304+ # the "self._loopStartTimeUs = startTimeUs" further below to emulate the
305+ # legacy behavior.
306+ # startTimeUs = _getFPGATime() # uncomment this for legacy behavior
307+
308+ if status != 0 :
309+ raise RuntimeError (
310+ f"waitForNotifierAlarm() returned _loopStartTimeUs={ self ._loopStartTimeUs } status={ status } "
311+ )
312+
313+ if self ._loopStartTimeUs == 0 :
314+ # when HAL_StopNotifier(self.notifier) is called the above waitForNotifierAlarm
315+ # will return a _loopStartTimeUs==0 and the API requires robots to stop any loops.
316+ # See the API for waitForNotifierAlarm
317+ keepGoing = False
318+ return keepGoing
319+
320+ # On a RoboRio 2, the following print statement results in values like:
321+ # print(f"expUs={callback.expirationUs} current={self._loopStartTimeUs}, legacy={startTimeUs}")
322+ # [2.27] expUs=3418017 current=3418078, legacy=3418152
323+ # [2.29] expUs=3438017 current=3438075, legacy=3438149
324+ # This indicates that there is about 60 microseconds of skid from
325+ # callback.expirationUs to self._loopStartTimeUs
326+ # and there is about 70 microseconds of skid from self._loopStartTimeUs to startTimeUs.
327+ # Consequently, this code uses "self._loopStartTimeUs, status = waitForNotifierAlarm"
328+ # to establish loopStartTime, rather than slowing down the code by adding an extra call to
329+ # "startTimeUs = _getFPGATime()".
330+
331+ # self._loopStartTimeUs = startTimeUs # Uncomment this line for legacy behavior.
332+
333+ self ._runCallbackAtHeadOfListAndReschedule (callback )
334+
335+ # Process all other callbacks that are ready to run
336+ # Changing the comparison to be _getFPGATime() rather than
337+ # self._loopStartTimeUs would also be correct.
338+ while (
339+ callback := self ._callbacks .peek ()
340+ ).expirationUs <= _getFPGATime ():
341+ self ._runCallbackAtHeadOfListAndReschedule (callback )
342+
327343 def _runCallbackAtHeadOfListAndReschedule (self , callback ) -> None :
328- # callback = self._callbacks.peek()
329344 # The callback.func() may have added more callbacks to self._callbacks,
330345 # but each is sorted by the _getFPGATime() at the moment it is
331346 # created, which is greater than self.expirationUs of this callback,
@@ -346,7 +361,7 @@ def _runCallbackAtHeadOfListAndReschedule(self, callback) -> None:
346361 self ._callbacks .reorderListAfterAChangeInTheFirstElement ()
347362
348363 def _stopNotifier (self ):
349- stopNotifier (self ._notifier )
364+ _stopNotifier (self ._notifier )
350365
351366 def endCompetition (self ) -> None :
352367 """
0 commit comments