robotpy-pykit 0.1.3b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pykit/__init__.py +0 -0
- pykit/autolog.py +329 -0
- pykit/inputs/loggableds.py +110 -0
- pykit/logdatareciever.py +14 -0
- pykit/loggedrobot.py +80 -0
- pykit/logger.py +207 -0
- pykit/logreplaysource.py +14 -0
- pykit/logtable.py +248 -0
- pykit/logvalue.py +133 -0
- pykit/networktables/loggeddashboardchooser.py +55 -0
- pykit/networktables/loggednetworkinput.py +14 -0
- pykit/networktables/nt4Publisher.py +68 -0
- pykit/watch/cli_replaywatch.py +102 -0
- pykit/wpilog/wpilogconstants.py +2 -0
- pykit/wpilog/wpilogreader.py +175 -0
- pykit/wpilog/wpilogwriter.py +279 -0
- robotpy_pykit-0.1.3b1.dist-info/METADATA +31 -0
- robotpy_pykit-0.1.3b1.dist-info/RECORD +22 -0
- robotpy_pykit-0.1.3b1.dist-info/WHEEL +5 -0
- robotpy_pykit-0.1.3b1.dist-info/entry_points.txt +2 -0
- robotpy_pykit-0.1.3b1.dist-info/licenses/LICENSE +26 -0
- robotpy_pykit-0.1.3b1.dist-info/top_level.txt +1 -0
pykit/__init__.py
ADDED
|
File without changes
|
pykit/autolog.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
import inspect
|
|
3
|
+
import gc
|
|
4
|
+
import dataclasses
|
|
5
|
+
|
|
6
|
+
from wpiutil import wpistruct
|
|
7
|
+
|
|
8
|
+
from pykit.logtable import LogTable
|
|
9
|
+
from pykit.logvalue import LogValue
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AutoLogClassOutputManager:
|
|
13
|
+
"""
|
|
14
|
+
A manager class for handling automatic logging of dataclass fields.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
logged_classes = []
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def register_class(cls, class_to_register: typing.Any):
|
|
21
|
+
"""
|
|
22
|
+
Registers a class for automatic logging.
|
|
23
|
+
|
|
24
|
+
:param class_type: The class type to register.
|
|
25
|
+
"""
|
|
26
|
+
cls.logged_classes.append(class_to_register)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AutoLogInputManager:
|
|
30
|
+
"""
|
|
31
|
+
A manager class for handling automatic input loading of dataclass fields.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
logged_classes = []
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def register_class(cls, class_to_register: typing.Any):
|
|
38
|
+
"""
|
|
39
|
+
Registers a class for automatic input loading.
|
|
40
|
+
|
|
41
|
+
:param class_type: The class type to register.
|
|
42
|
+
"""
|
|
43
|
+
cls.logged_classes.append(class_to_register)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def getInputs(cls) -> typing.List[typing.Any]:
|
|
47
|
+
return cls.logged_classes
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AutoLogOutputManager:
|
|
51
|
+
"""
|
|
52
|
+
A manager class for handling automatic logging of output members (fields/methods).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# Stores a dictionary where keys are class types and values are lists of
|
|
56
|
+
# dictionaries, each representing a decorated member.
|
|
57
|
+
# Each member dictionary contains:
|
|
58
|
+
# 'name': str (name of the field or method)
|
|
59
|
+
# 'is_method': bool (True if it's a method, False if it's a field)
|
|
60
|
+
# 'log_type': LogValue.LoggableType (the type to log as)
|
|
61
|
+
# 'custom_type': str (optional custom type string)
|
|
62
|
+
logged_members: typing.Dict[
|
|
63
|
+
typing.Type, typing.List[typing.Dict[str, typing.Any]]
|
|
64
|
+
] = {}
|
|
65
|
+
|
|
66
|
+
root_cache = []
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def publish_all(cls, table: LogTable, root_instance=None):
|
|
70
|
+
if root_instance is None:
|
|
71
|
+
if cls.root_cache:
|
|
72
|
+
root_instance = cls.root_cache
|
|
73
|
+
else:
|
|
74
|
+
root_instance = []
|
|
75
|
+
for clS in cls.logged_members:
|
|
76
|
+
for instance in gc.get_referrers(
|
|
77
|
+
clS
|
|
78
|
+
): # at runtime take all instances that exist of registered classes
|
|
79
|
+
if instance.__class__ == clS:
|
|
80
|
+
root_instance.append(instance)
|
|
81
|
+
cls.root_cache = root_instance
|
|
82
|
+
for instance in root_instance:
|
|
83
|
+
cls.publish(instance, table)
|
|
84
|
+
if (
|
|
85
|
+
hasattr(
|
|
86
|
+
instance, "_do_autolog"
|
|
87
|
+
) # is the attempted class actually marked for autolog?
|
|
88
|
+
and getattr(instance, "_do_autolog")
|
|
89
|
+
and hasattr(instance, "__dict__")
|
|
90
|
+
and not isinstance(instance, staticmethod)
|
|
91
|
+
):
|
|
92
|
+
# be recursive, there are sub-members, but only on classes marked for autolog
|
|
93
|
+
cls.publish_all(table, instance.__dict__.values())
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def register_member(
|
|
97
|
+
cls,
|
|
98
|
+
class_type: typing.Type,
|
|
99
|
+
member_name: str,
|
|
100
|
+
is_method: bool,
|
|
101
|
+
log_type: LogValue.LoggableType,
|
|
102
|
+
key: str = "",
|
|
103
|
+
custom_type: str = "",
|
|
104
|
+
):
|
|
105
|
+
"""
|
|
106
|
+
Registers a member (field or method) of a class for automatic output logging.
|
|
107
|
+
"""
|
|
108
|
+
if class_type not in cls.logged_members:
|
|
109
|
+
cls.logged_members[class_type] = []
|
|
110
|
+
cls.logged_members[class_type].append(
|
|
111
|
+
{
|
|
112
|
+
"name": member_name,
|
|
113
|
+
"is_method": is_method,
|
|
114
|
+
"log_type": log_type,
|
|
115
|
+
"key": key,
|
|
116
|
+
"custom_type": custom_type,
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def publish(cls, instance: typing.Any, table: LogTable):
|
|
122
|
+
"""
|
|
123
|
+
Publishes the values of all registered members of an instance to a LogTable.
|
|
124
|
+
"""
|
|
125
|
+
class_type = type(instance)
|
|
126
|
+
if class_type in cls.logged_members:
|
|
127
|
+
for member_info in cls.logged_members[class_type]:
|
|
128
|
+
member_name = member_info["name"]
|
|
129
|
+
is_method = member_info["is_method"]
|
|
130
|
+
log_type = member_info["log_type"]
|
|
131
|
+
custom_type = member_info["custom_type"]
|
|
132
|
+
|
|
133
|
+
key = member_info["key"] or member_name
|
|
134
|
+
|
|
135
|
+
value = None
|
|
136
|
+
if is_method:
|
|
137
|
+
# Assume methods are getters and take no arguments
|
|
138
|
+
value = getattr(instance, member_name)()
|
|
139
|
+
else:
|
|
140
|
+
value = getattr(instance, member_name)
|
|
141
|
+
|
|
142
|
+
# Put the value into the log table with the specified type
|
|
143
|
+
if hasattr(value, "WPIStruct") or (
|
|
144
|
+
hasattr(value, "__iter__")
|
|
145
|
+
and len(value) > 0
|
|
146
|
+
and hasattr(value[0], "WPIStruct")
|
|
147
|
+
):
|
|
148
|
+
table.put(key, value)
|
|
149
|
+
else:
|
|
150
|
+
log_value = LogValue(value, custom_type)
|
|
151
|
+
if log_type is not None:
|
|
152
|
+
# Override the inferred log_type if explicitly provided in the decorator
|
|
153
|
+
log_value.log_type = log_type
|
|
154
|
+
|
|
155
|
+
# if table.writeAllowed(
|
|
156
|
+
# full_key, log_value.log_type, log_value.custom_type
|
|
157
|
+
# ):
|
|
158
|
+
table.putValue(key, log_value)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def autolog_output(
|
|
162
|
+
key: str,
|
|
163
|
+
log_type: typing.Optional[LogValue.LoggableType] = None,
|
|
164
|
+
custom_type: str = "",
|
|
165
|
+
):
|
|
166
|
+
"""
|
|
167
|
+
A decorator for methods or fields in a class to automatically log their output.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
def decorator(member):
|
|
171
|
+
# This part is tricky because Python decorators for methods/fields
|
|
172
|
+
# don't directly give you the class at definition time.
|
|
173
|
+
# We'll store a temporary attribute and process it in a class decorator.
|
|
174
|
+
if inspect.isfunction(member):
|
|
175
|
+
# It's a method
|
|
176
|
+
print(f"[AugoLogOutput] DEBUG: Setting up log for {key}")
|
|
177
|
+
member._autolog_output_info = {
|
|
178
|
+
"is_method": True,
|
|
179
|
+
"log_type": log_type,
|
|
180
|
+
"custom_type": custom_type,
|
|
181
|
+
"key": key,
|
|
182
|
+
}
|
|
183
|
+
else:
|
|
184
|
+
# It's a field (this case is harder to handle directly with a decorator
|
|
185
|
+
# on the field itself, usually done via a class decorator or metaclass)
|
|
186
|
+
# For now, we'll assume it's a method or a property-like descriptor.
|
|
187
|
+
# If it's a simple field, the class decorator approach is more robust.
|
|
188
|
+
# Let's assume for now that direct field decoration will be handled
|
|
189
|
+
# by a class decorator that scans for these attributes.
|
|
190
|
+
# For direct field decoration, we might need a descriptor.
|
|
191
|
+
# For simplicity, let's focus on methods first, or assume a class
|
|
192
|
+
# decorator will pick up field annotations.
|
|
193
|
+
# For now, let's make it work for methods and properties.
|
|
194
|
+
member._autolog_output_info = {
|
|
195
|
+
"is_method": False, # This will be true for properties too
|
|
196
|
+
"log_type": log_type,
|
|
197
|
+
"custom_type": custom_type,
|
|
198
|
+
"key": key,
|
|
199
|
+
}
|
|
200
|
+
return member
|
|
201
|
+
|
|
202
|
+
return decorator
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def autologgable_output(cls):
|
|
206
|
+
"""
|
|
207
|
+
A class decorator that scans for methods/fields decorated with @autolog_output
|
|
208
|
+
and registers them with AutoLogOutputManager.
|
|
209
|
+
"""
|
|
210
|
+
for name in dir(cls):
|
|
211
|
+
member = getattr(cls, name)
|
|
212
|
+
if hasattr(member, "_autolog_output_info"):
|
|
213
|
+
info = member._autolog_output_info
|
|
214
|
+
AutoLogOutputManager.register_member(
|
|
215
|
+
cls,
|
|
216
|
+
name,
|
|
217
|
+
info["is_method"],
|
|
218
|
+
info["log_type"],
|
|
219
|
+
info["key"],
|
|
220
|
+
info["custom_type"],
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
setattr(cls, "_do_autolog", True)
|
|
224
|
+
return cls
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def autolog(cls=None, /):
|
|
228
|
+
"""
|
|
229
|
+
A class decorator that adds 'toLog' and 'fromLog' methods to a dataclass for automatic logging.
|
|
230
|
+
|
|
231
|
+
The 'toLog' method serializes the dataclass fields to a LogTable.
|
|
232
|
+
The 'fromLog' method deserializes the data from a LogTable into the dataclass fields.
|
|
233
|
+
|
|
234
|
+
This decorator is designed to be used with dataclasses and supports nested dataclasses
|
|
235
|
+
decorated with @autolog.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
def wrap(clS):
|
|
239
|
+
resolved_hints = typing.get_type_hints(clS)
|
|
240
|
+
field_names = [field.name for field in dataclasses.fields(clS)]
|
|
241
|
+
|
|
242
|
+
def toLog(self, table: LogTable, prefix: str):
|
|
243
|
+
"""
|
|
244
|
+
Recursively logs the fields of the dataclass to a LogTable.
|
|
245
|
+
|
|
246
|
+
:param table: The LogTable instance to write to.
|
|
247
|
+
:param prefix: The prefix for the log entries.
|
|
248
|
+
"""
|
|
249
|
+
for name in field_names:
|
|
250
|
+
value = getattr(self, name)
|
|
251
|
+
field_prefix = f"{prefix}/{name}"
|
|
252
|
+
if hasattr(value, "toLog"):
|
|
253
|
+
value.toLog(table, field_prefix)
|
|
254
|
+
else:
|
|
255
|
+
table.put(field_prefix, value)
|
|
256
|
+
|
|
257
|
+
def fromLog(self, table: LogTable, prefix: str):
|
|
258
|
+
"""
|
|
259
|
+
Recursively reads the fields of the dataclass from a LogTable.
|
|
260
|
+
|
|
261
|
+
:param table: The LogTable instance to read from.
|
|
262
|
+
:param prefix: The prefix for the log entries.
|
|
263
|
+
"""
|
|
264
|
+
for name in field_names:
|
|
265
|
+
field_prefix = f"{prefix}/{name}"
|
|
266
|
+
|
|
267
|
+
value = getattr(self, name)
|
|
268
|
+
if hasattr(value, "fromLog"):
|
|
269
|
+
value.fromLog(table, field_prefix)
|
|
270
|
+
else:
|
|
271
|
+
field_type = resolved_hints[name]
|
|
272
|
+
new_value = None
|
|
273
|
+
|
|
274
|
+
origin = typing.get_origin(field_type)
|
|
275
|
+
if origin is list:
|
|
276
|
+
list_type = typing.get_args(field_type)[0]
|
|
277
|
+
if list_type is bool:
|
|
278
|
+
new_value = table.getBooleanArray(field_prefix, value)
|
|
279
|
+
elif list_type is int:
|
|
280
|
+
new_value = table.getIntegerArray(field_prefix, value)
|
|
281
|
+
elif list_type is float:
|
|
282
|
+
new_value = table.getDoubleArray(field_prefix, value)
|
|
283
|
+
elif list_type is str:
|
|
284
|
+
new_value = table.getStringArray(field_prefix, value)
|
|
285
|
+
elif hasattr(list_type, "WPIStruct"):
|
|
286
|
+
new_value = wpistruct.unpackArray(
|
|
287
|
+
list_type, table.getRaw(field_prefix, b"")
|
|
288
|
+
)
|
|
289
|
+
# is it struct?
|
|
290
|
+
else:
|
|
291
|
+
print(
|
|
292
|
+
f"[AutoLog] Failed to read of type {field_type} with value {list_type}"
|
|
293
|
+
)
|
|
294
|
+
else:
|
|
295
|
+
if field_type is bool:
|
|
296
|
+
new_value = table.getBoolean(field_prefix, value)
|
|
297
|
+
elif field_type is int:
|
|
298
|
+
new_value = table.getInteger(field_prefix, value)
|
|
299
|
+
elif field_type is float:
|
|
300
|
+
new_value = table.getDouble(field_prefix, value)
|
|
301
|
+
elif field_type is str:
|
|
302
|
+
new_value = table.getString(field_prefix, value)
|
|
303
|
+
elif hasattr(field_type, "WPIStruct"):
|
|
304
|
+
new_value = wpistruct.unpack(
|
|
305
|
+
field_type, table.getRaw(field_prefix, b"")
|
|
306
|
+
)
|
|
307
|
+
# is it struct?
|
|
308
|
+
else:
|
|
309
|
+
print(f"[AutoLog] Failed to read of type {field_type}")
|
|
310
|
+
|
|
311
|
+
if new_value is not None:
|
|
312
|
+
setattr(self, name, new_value)
|
|
313
|
+
|
|
314
|
+
def registerAutologged(self) -> None:
|
|
315
|
+
print(f"[AutoLog] registering {self.name}")
|
|
316
|
+
AutoLogInputManager.register_class(self)
|
|
317
|
+
|
|
318
|
+
setattr(clS, "toLog", toLog)
|
|
319
|
+
setattr(clS, "fromLog", fromLog)
|
|
320
|
+
# https://docs.python.org/3/library/dataclasses.html#dataclasses.__post_init__
|
|
321
|
+
# https://docs.python.org/3/reference/expressions.html#private-name-mangling
|
|
322
|
+
setattr(cls, f"_{clS.__class__.__name__}__post_init__", registerAutologged)
|
|
323
|
+
|
|
324
|
+
return clS
|
|
325
|
+
|
|
326
|
+
if cls is None:
|
|
327
|
+
return wrap
|
|
328
|
+
|
|
329
|
+
return wrap(cls)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from hal import AllianceStationID
|
|
2
|
+
from wpilib import DriverStation
|
|
3
|
+
from wpilib.simulation import DriverStationSim
|
|
4
|
+
|
|
5
|
+
from pykit.logtable import LogTable
|
|
6
|
+
from pykit.logvalue import LogValue
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LoggedDriverStation:
|
|
10
|
+
"""A dataclass for holding Driver Station I/O data."""
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def saveToTable(cls, table: LogTable):
|
|
14
|
+
"""Saves the current Driver Station data to the log table."""
|
|
15
|
+
alliance = DriverStation.getAlliance()
|
|
16
|
+
location = DriverStation.getLocation()
|
|
17
|
+
station = (
|
|
18
|
+
0
|
|
19
|
+
if location is None or alliance is None
|
|
20
|
+
else (location + (3 if alliance == DriverStation.Alliance.kBlue else 0))
|
|
21
|
+
)
|
|
22
|
+
table.put("AllianceStation", station)
|
|
23
|
+
table.put("EventName", DriverStation.getEventName())
|
|
24
|
+
table.put("GameSpecificMessage", DriverStation.getGameSpecificMessage())
|
|
25
|
+
table.put("MatchNumber", DriverStation.getMatchNumber())
|
|
26
|
+
table.put("ReplayNumber", DriverStation.getReplayNumber())
|
|
27
|
+
table.put("MatchType", DriverStation.getMatchType().value)
|
|
28
|
+
table.put("MatchTime", DriverStation.getMatchTime())
|
|
29
|
+
|
|
30
|
+
table.put("Enabled", DriverStation.isEnabled())
|
|
31
|
+
table.put("Autonomous", DriverStation.isAutonomous())
|
|
32
|
+
table.put("Test", DriverStation.isTest())
|
|
33
|
+
table.put("EmergencyStop", DriverStation.isEStopped())
|
|
34
|
+
table.put("FMSAttached", DriverStation.isFMSAttached())
|
|
35
|
+
table.put("DSAttached", DriverStation.isDSAttached())
|
|
36
|
+
|
|
37
|
+
for i in range(DriverStation.kJoystickPorts):
|
|
38
|
+
joystickTable = table.getSubTable(f"Joystick{i}")
|
|
39
|
+
joystickTable.put("Name", DriverStation.getJoystickName(i).strip())
|
|
40
|
+
joystickTable.put("Type", DriverStation.getJoystickType(i))
|
|
41
|
+
joystickTable.put("Xbox", DriverStation.getJoystickIsXbox(i))
|
|
42
|
+
joystickTable.put("ButtonCount", DriverStation.getStickButtonCount(i))
|
|
43
|
+
joystickTable.put("ButtonValues", DriverStation.getStickButtons(i))
|
|
44
|
+
|
|
45
|
+
povCount = DriverStation.getStickPOVCount(i)
|
|
46
|
+
povValues = []
|
|
47
|
+
for j in range(povCount):
|
|
48
|
+
povValues.append(DriverStation.getStickPOV(i, j))
|
|
49
|
+
joystickTable.putValue(
|
|
50
|
+
"POVs", LogValue.withType(LogValue.LoggableType.IntegerArray, povValues)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
axisCount = DriverStation.getStickAxisCount(i)
|
|
54
|
+
axisValues = []
|
|
55
|
+
axisTypes = []
|
|
56
|
+
for j in range(axisCount):
|
|
57
|
+
axisValues.append(DriverStation.getStickAxis(i, j))
|
|
58
|
+
axisTypes.append(DriverStation.getJoystickAxisType(i, j))
|
|
59
|
+
|
|
60
|
+
joystickTable.putValue(
|
|
61
|
+
"AxesValues",
|
|
62
|
+
LogValue.withType(LogValue.LoggableType.DoubleArray, axisValues),
|
|
63
|
+
)
|
|
64
|
+
joystickTable.put("AxisTypes", axisTypes)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def loadFromTable(cls, table: LogTable):
|
|
68
|
+
DriverStationSim.setAllianceStationId(
|
|
69
|
+
AllianceStationID(
|
|
70
|
+
table.get("AllianceStation", AllianceStationID.kRed1.value)
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
DriverStationSim.setEventName(table.get("EventName", ""))
|
|
74
|
+
DriverStationSim.setGameSpecificMessage(table.get("GameSpecificMessage", ""))
|
|
75
|
+
DriverStationSim.setMatchNumber(table.get("MatchNumber", 0))
|
|
76
|
+
DriverStationSim.setReplayNumber(table.get("ReplayNumber", 0))
|
|
77
|
+
DriverStationSim.setMatchType(
|
|
78
|
+
DriverStation.MatchType(table.get("MatchType", 0))
|
|
79
|
+
)
|
|
80
|
+
DriverStationSim.setMatchTime(table.get("MatchTime", -1.0))
|
|
81
|
+
|
|
82
|
+
DriverStationSim.setEnabled(table.get("Enabled", False))
|
|
83
|
+
DriverStationSim.setAutonomous(table.get("Autonomous", False))
|
|
84
|
+
DriverStationSim.setTest(table.get("Test", False))
|
|
85
|
+
DriverStationSim.setEStop(table.get("EmergencyStop", False))
|
|
86
|
+
DriverStationSim.setFmsAttached(table.get("FMSAttached", False))
|
|
87
|
+
dsAttached = table.get("DSAttached", False)
|
|
88
|
+
DriverStationSim.setDsAttached(dsAttached)
|
|
89
|
+
for i in range(DriverStation.kJoystickPorts):
|
|
90
|
+
joystickTable = table.getSubTable(f"Joystick{i}")
|
|
91
|
+
# print(joystickTable.getDoubleArray("AxesValues", []))
|
|
92
|
+
|
|
93
|
+
buttonValues = joystickTable.get("ButtonValues", 0)
|
|
94
|
+
DriverStationSim.setJoystickButtons(i, buttonValues)
|
|
95
|
+
|
|
96
|
+
povValues = joystickTable.get("POVs", [])
|
|
97
|
+
DriverStationSim.setJoystickPOVCount(i, len(povValues))
|
|
98
|
+
for j, pov in enumerate(povValues):
|
|
99
|
+
DriverStationSim.setJoystickPOV(i, j, pov)
|
|
100
|
+
|
|
101
|
+
axisValues = joystickTable.get("AxesValues", [])
|
|
102
|
+
axisTypes = joystickTable.get("AxisTypes", [])
|
|
103
|
+
|
|
104
|
+
DriverStationSim.setJoystickAxisCount(i, len(axisValues))
|
|
105
|
+
for j, (axis_val, axis_type) in enumerate(zip(axisValues, axisTypes)):
|
|
106
|
+
DriverStationSim.setJoystickAxis(i, j, axis_val)
|
|
107
|
+
DriverStationSim.setJoystickAxisType(i, j, axis_type)
|
|
108
|
+
|
|
109
|
+
if dsAttached:
|
|
110
|
+
DriverStationSim.notifyNewData()
|
pykit/logdatareciever.py
ADDED
pykit/loggedrobot.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import hal
|
|
2
|
+
from wpilib import (
|
|
3
|
+
DSControlWord,
|
|
4
|
+
IterativeRobotBase,
|
|
5
|
+
RobotController,
|
|
6
|
+
Watchdog,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from pykit.logger import Logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LoggedRobot(IterativeRobotBase):
|
|
13
|
+
"""A robot base class that provides logging and replay functionality."""
|
|
14
|
+
|
|
15
|
+
default_period = 0.02 # seconds
|
|
16
|
+
|
|
17
|
+
def printOverrunMessage(self):
|
|
18
|
+
"""Prints a message when the main loop overruns."""
|
|
19
|
+
print("Loop overrun detected!")
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""
|
|
23
|
+
Constructor for the LoggedRobot.
|
|
24
|
+
Initializes the robot, sets up the logger, and creates I/O objects.
|
|
25
|
+
"""
|
|
26
|
+
IterativeRobotBase.__init__(self, LoggedRobot.default_period)
|
|
27
|
+
self.useTiming = True
|
|
28
|
+
self._nextCycleUs = 0
|
|
29
|
+
self._periodUs = int(self.getPeriod() * 1000000)
|
|
30
|
+
|
|
31
|
+
self.notifier = hal.initializeNotifier()[0]
|
|
32
|
+
self.watchdog = Watchdog(LoggedRobot.default_period, self.printOverrunMessage)
|
|
33
|
+
self.word = DSControlWord()
|
|
34
|
+
|
|
35
|
+
def endCompetition(self) -> None:
|
|
36
|
+
"""Called at the end of the competition to clean up resources."""
|
|
37
|
+
hal.stopNotifier(self.notifier)
|
|
38
|
+
hal.cleanNotifier(self.notifier)
|
|
39
|
+
|
|
40
|
+
def startCompetition(self) -> None:
|
|
41
|
+
"""
|
|
42
|
+
The main loop of the robot.
|
|
43
|
+
Handles timing, logging, and calling the periodic functions.
|
|
44
|
+
"""
|
|
45
|
+
self.robotInit()
|
|
46
|
+
|
|
47
|
+
# TODO: handle autolog outputs
|
|
48
|
+
|
|
49
|
+
if self.isSimulation():
|
|
50
|
+
self._simulationInit()
|
|
51
|
+
|
|
52
|
+
self.initEnd = RobotController.getFPGATime()
|
|
53
|
+
Logger.periodicAfterUser(self.initEnd, 0)
|
|
54
|
+
print("Robot startup complete!")
|
|
55
|
+
hal.observeUserProgramStarting()
|
|
56
|
+
|
|
57
|
+
Logger.startReciever()
|
|
58
|
+
|
|
59
|
+
while True:
|
|
60
|
+
if self.useTiming:
|
|
61
|
+
currentTime = RobotController.getFPGATime() # microseconds
|
|
62
|
+
if self._nextCycleUs < currentTime:
|
|
63
|
+
# loop overrun, immediate next cycle
|
|
64
|
+
self._nextCycleUs = currentTime
|
|
65
|
+
else:
|
|
66
|
+
hal.updateNotifierAlarm(self.notifier, int(self._nextCycleUs))
|
|
67
|
+
if hal.waitForNotifierAlarm(self.notifier) == 0:
|
|
68
|
+
break
|
|
69
|
+
self._nextCycleUs += self._periodUs
|
|
70
|
+
|
|
71
|
+
periodicBeforeStart = RobotController.getFPGATime()
|
|
72
|
+
Logger.periodicBeforeUser()
|
|
73
|
+
|
|
74
|
+
userCodeStart = RobotController.getFPGATime()
|
|
75
|
+
self._loopFunc()
|
|
76
|
+
userCodeEnd = RobotController.getFPGATime()
|
|
77
|
+
|
|
78
|
+
Logger.periodicAfterUser(
|
|
79
|
+
userCodeEnd - userCodeStart, userCodeStart - periodicBeforeStart
|
|
80
|
+
)
|