1
+ import traceback
2
+ from sys import argv
1
3
from typing import Any , Callable , Iterable , ClassVar
2
4
from heapq import heappush , heappop , _siftup
3
5
from hal import (
25
27
26
28
class _Callback :
27
29
28
- __slots__ = "func" , "_periodUs" , "expirationUs"
29
-
30
30
def __init__ (
31
31
self ,
32
32
func : Callable [[], None ],
@@ -100,21 +100,14 @@ def __repr__(self) -> str:
100
100
101
101
class _OrderedListSort :
102
102
103
- __slots__ = "_data"
104
-
105
103
def __init__ (self ) -> None :
106
104
self ._data : list [Any ] = []
107
105
108
106
def add (self , item : Any ) -> None :
109
107
self ._data .append (item )
110
108
self ._data .sort ()
111
109
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 :
118
111
if self ._data :
119
112
return self ._data [0 ]
120
113
else :
@@ -138,20 +131,13 @@ def __repr__(self) -> str:
138
131
139
132
class _OrderedListMin :
140
133
141
- __slots__ = "_data"
142
-
143
134
def __init__ (self ) -> None :
144
135
self ._data : list [Any ] = []
145
136
146
137
def add (self , item : Any ) -> None :
147
138
self ._data .append (item )
148
139
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 :
155
141
if self ._data :
156
142
return min (self ._data )
157
143
else :
@@ -175,24 +161,14 @@ def __repr__(self) -> str:
175
161
176
162
class _OrderedListHeapq :
177
163
178
- __slots__ = "_data"
179
-
180
164
def __init__ (self ) -> None :
181
165
self ._data : list [Any ] = []
182
166
183
167
def add (self , item : Any ) -> None :
184
168
heappush (self ._data , item )
185
169
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 ]
196
172
197
173
def reorderListAfterAChangeInTheFirstElement (self ):
198
174
_siftup (self ._data , 0 )
@@ -209,9 +185,23 @@ def __contains__(self, item) -> bool:
209
185
def __repr__ (self ) -> str :
210
186
return str (sorted (self ._data ))
211
187
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
+
212
202
213
203
# todo what should the name of this class be?
214
- class TimedRobotPy (IterativeRobotPy ):
204
+ class TimedRobotPy (_IterativeRobotPy ):
215
205
"""
216
206
TimedRobotPy implements the IterativeRobotBase robot program framework.
217
207
@@ -232,26 +222,30 @@ def __init__(self, period: wpimath.units.seconds = kDefaultPeriod) -> None:
232
222
233
223
:param period: period of the main robot periodic loop in seconds.
234
224
"""
235
- super ().__init__ (period )
225
+ if "_IterativeRobotPyIsObject" in argv :
226
+ super ().__init__ ()
227
+ self ._periodS = period
228
+ else :
229
+ super ().__init__ (period )
236
230
237
231
# All periodic functions created by addPeriodic are relative
238
232
# to this self._startTimeUs
239
233
self ._startTimeUs = _getFPGATime ()
240
- self ._callbacks = _OrderedListSort ()
234
+ self ._callbacks = _OrderedList ()
241
235
self ._loopStartTimeUs = 0
242
236
self .addPeriodic (self ._loopFunc , period = self ._periodS )
243
237
244
- self ._notifier , status = initializeNotifier ()
238
+ self ._notifier , status = _initializeNotifier ()
245
239
if status != 0 :
246
240
raise RuntimeError (
247
241
f"initializeNotifier() returned { self ._notifier } , { status } "
248
242
)
249
243
250
- status = setNotifierName (self ._notifier , "TimedRobotPy" )
244
+ status = _setNotifierName (self ._notifier , "TimedRobotPy" )
251
245
if status != 0 :
252
246
raise RuntimeError (f"setNotifierName() returned { status } " )
253
247
254
- report (_kResourceType_Framework , _kFramework_Timed )
248
+ _report (_kResourceType_Framework , _kFramework_Timed )
255
249
256
250
def startCompetition (self ) -> None :
257
251
"""
@@ -265,67 +259,88 @@ def startCompetition(self) -> None:
265
259
266
260
# Tell the DS that the robot is ready to be enabled
267
261
print ("********** Robot program startup complete **********" , flush = True )
268
- observeUserProgramStarting ()
262
+ _observeUserProgramStarting ()
269
263
270
264
# 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
+
323
285
finally :
286
+ print ("Reached after finally: self._stopNotifier(): " , flush = True )
324
287
# pytests hang on PC when we don't force a call to self._stopNotifier()
325
288
self ._stopNotifier ()
326
289
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
+
327
343
def _runCallbackAtHeadOfListAndReschedule (self , callback ) -> None :
328
- # callback = self._callbacks.peek()
329
344
# The callback.func() may have added more callbacks to self._callbacks,
330
345
# but each is sorted by the _getFPGATime() at the moment it is
331
346
# created, which is greater than self.expirationUs of this callback,
@@ -346,7 +361,7 @@ def _runCallbackAtHeadOfListAndReschedule(self, callback) -> None:
346
361
self ._callbacks .reorderListAfterAChangeInTheFirstElement ()
347
362
348
363
def _stopNotifier (self ):
349
- stopNotifier (self ._notifier )
364
+ _stopNotifier (self ._notifier )
350
365
351
366
def endCompetition (self ) -> None :
352
367
"""
0 commit comments