Skip to content

Commit 7042b96

Browse files
committed
Simulates, but does not update robot position in simulation.
Signed-off-by: Mike Stitt <[email protected]>
1 parent c454e6d commit 7042b96

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
from heapq import heappush, heappop
2+
import hal
3+
4+
from hal import report, initializeNotifier, setNotifierName, observeUserProgramStarting, updateNotifierAlarm, \
5+
waitForNotifierAlarm
6+
from wpilib import RobotController
7+
8+
from wpilib.iterativerobotpy import IterativeRobotPy
9+
10+
11+
class Callback:
12+
def __init__(self, func, periodUs: int, expirationUs: int):
13+
self.func = func
14+
self.periodUs = periodUs
15+
self.expirationUs = expirationUs
16+
17+
@classmethod
18+
def makeCallBack(cls,
19+
func,
20+
startTimeUs: int,
21+
periodUs: int,
22+
offsetUs: int):
23+
# todo does the "// periodUs * periodUs" do the correct integer math?
24+
nowUs = RobotController.getFPGATime()
25+
#print(f"makeCallBack getFPGATim={nowUs}")
26+
expirationUs = \
27+
startTimeUs + offsetUs + periodUs + \
28+
((nowUs - startTimeUs) // periodUs) * periodUs
29+
30+
return Callback(
31+
func,
32+
periodUs,
33+
expirationUs
34+
)
35+
36+
def setNextExpirationTimeDelta(self, currentTimeUs: int):
37+
# increment the expiration time by the number of full periods it's behind
38+
# plus one to avoid rapid repeat fires from a large loop overrun. We assume
39+
# currentTime ≥ expirationTime rather than checking for it since the
40+
# callback wouldn't be running otherwise.
41+
# todo does this math work?
42+
self.expirationUs = self.expirationUs + self.periodUs \
43+
+ ((currentTimeUs - self.expirationUs) // self.periodUs) * self.periodUs
44+
45+
def __lt__(self, other):
46+
return self.expirationUs < other.expirationUs
47+
48+
def __bool__(self):
49+
return True
50+
51+
52+
class OrderedList:
53+
def __init__(self):
54+
self._data = []
55+
56+
def add(self, item):
57+
heappush(self._data, item)
58+
59+
def pop(self):
60+
return heappop(self._data)
61+
62+
def peek(self):
63+
if self._data:
64+
return self._data[0]
65+
else:
66+
return None
67+
68+
def __len__(self):
69+
return len(self._data)
70+
71+
def __iter__(self):
72+
return iter(sorted(self._data))
73+
74+
def __contains__(self, item):
75+
return item in self._data
76+
77+
def __str__(self):
78+
return str(sorted(self._data))
79+
80+
81+
class TimedRobotPy(IterativeRobotPy):
82+
83+
def __init__(self, periodS: float = 0.020): # todo are the units on period correct?
84+
super().__init__(periodS)
85+
86+
self.startTimeUs = RobotController.getFPGATime()
87+
#print(f"self.startTimeUs={int(self.startTimeUs/timedelta(microseconds=1))}")
88+
self.callbacks = OrderedList()
89+
self.addPeriodic(self.loopFunc, period=periodS)
90+
91+
self.notifier, status = initializeNotifier()
92+
#print(f"Initialized status={status} notifier={self.notifier}", flush=True)
93+
if status != 1:
94+
message = f"initializeNotifier() returned {status} {self.notifier}"
95+
raise RuntimeError(message)
96+
97+
status = setNotifierName(self.notifier, "TimedRobot")
98+
if status != 1:
99+
raise RuntimeError(f"setNotifierName() returned {status}")
100+
101+
report(hal.tResourceType.kResourceType_Framework, hal.tInstances.kFramework_Timed)
102+
103+
def startCompetition(self) -> None:
104+
self.robotInit()
105+
106+
if self.isSimulation():
107+
self.simulationInit()
108+
109+
# Tell the DS that the robot is ready to be enabled
110+
print("********** Robot program startup complete **********")
111+
observeUserProgramStarting()
112+
113+
# Loop forever, calling the appropriate mode-dependent function
114+
# (really not forever, there is a check for a break)
115+
while True:
116+
# We don't have to check there's an element in the queue first because
117+
# there's always at least one (the constructor adds one). It's reenqueued
118+
# at the end of the loop.
119+
callback = self.callbacks.pop()
120+
121+
#print(f"type(callback)={type(callback)} type(callback.expirationUs)={type(callback.expirationUs)} callback.expirationUs={callback.expirationUs}", flush=True)
122+
123+
timeout = callback.expirationUs
124+
fpgaTime = RobotController.getFPGATime()
125+
#print(f"timeout={timeout} fpgatime={fpgaTime}", flush=True)
126+
status = updateNotifierAlarm(self.notifier, timeout)
127+
#print(f"updateNotifierAlarm({self.notifier}, {timeout}) status={status}", flush=True)
128+
#if status != 0:
129+
# message = f"updateNotifierAlarm() returned {status}"
130+
# print(message, flush=True)
131+
# raise RuntimeError(message)
132+
133+
currentTimeUs, status = waitForNotifierAlarm(self.notifier) # todo what are the return values, what is the return order?
134+
if status != 0:
135+
message = f"waitForNotifierAlarm() returned currentTimeUs={currentTimeUs} status={status}"
136+
#print(message, flush=True)
137+
#raise RuntimeError(message)
138+
139+
if currentTimeUs == 0:
140+
break
141+
self.loopStartTimeUs = RobotController.getFPGATime()
142+
print(f"self.loopStartTimeUs={self.loopStartTimeUs}")
143+
self._runCallbackAndReschedule(callback, currentTimeUs)
144+
145+
# Process all other callbacks that are ready to run
146+
while self.callbacks.peek().expirationUs <= currentTimeUs:
147+
callback = self.callbacks.pop()
148+
self._runCallbackAndReschedule(callback, currentTimeUs)
149+
150+
def _runCallbackAndReschedule(self, callback, currentTimeUs):
151+
callback.func()
152+
callback.setNextExpirationTimeDelta(currentTimeUs)
153+
self.callbacks.add(callback)
154+
155+
def endCompetition(self):
156+
hal.stopNotifier(self.notifier)
157+
158+
"""
159+
todo this doesn't really translate to python (is it really needed?):
160+
161+
TimedRobot::~TimedRobot() {
162+
if (m_notifier != HAL_kInvalidHandle) {
163+
int32_t status = 0;
164+
HAL_StopNotifier(m_notifier, &status);
165+
FRC_ReportError(status, "StopNotifier");
166+
}
167+
}
168+
"""
169+
170+
def getLoopStartTime(self):
171+
return self.loopStartTimeUs/1e6 # todo units are seconds
172+
173+
def addPeriodic(self,
174+
callback, # todo typehint
175+
period: float, # todo units seconds
176+
offset: float = 0.0): # todo units seconds
177+
self.callbacks.add(
178+
Callback.makeCallBack(
179+
callback,
180+
self.startTimeUs, int(period*1e6), int(offset*1e6)
181+
)
182+
)

0 commit comments

Comments
 (0)