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.
@@ -0,0 +1,68 @@
1
+ from ntcore import (
2
+ GenericPublisher,
3
+ IntegerPublisher,
4
+ NetworkTable,
5
+ NetworkTableInstance,
6
+ )
7
+
8
+ from pykit.logdatareciever import LogDataReciever
9
+ from pykit.logtable import LogTable
10
+ from pykit.logvalue import LogValue
11
+
12
+
13
+ class NT4Publisher(LogDataReciever):
14
+ pykitTable: NetworkTable
15
+ lastTable: LogTable = LogTable(0)
16
+
17
+ timestampPublisher: IntegerPublisher
18
+ publishers: dict[str, GenericPublisher] = {}
19
+
20
+ def __init__(self, actLikeAKit: bool = False):
21
+ self.pykitTable = NetworkTableInstance.getDefault().getTable(
22
+ "/AdvantageKit" if actLikeAKit else "/PyKit"
23
+ )
24
+ self.timestampPublisher = self.pykitTable.getIntegerTopic(
25
+ self.timestampKey[1:]
26
+ ).publish()
27
+
28
+ def putTable(self, table: LogTable):
29
+ self.timestampPublisher.set(table.getTimestamp(), table.getTimestamp())
30
+
31
+ newMap = table.getAll(False)
32
+ oldMap = self.lastTable.getAll(False)
33
+
34
+ for key, newValue in newMap.items():
35
+ if newValue == oldMap.get(key):
36
+ continue
37
+ key = key[1:]
38
+ publisher = self.publishers.get(key)
39
+ if publisher is None:
40
+ publisher = self.pykitTable.getTopic(key).genericPublish(
41
+ newValue.getNT4Type()
42
+ )
43
+ self.publishers[key] = publisher
44
+
45
+ match newValue.log_type:
46
+ case LogValue.LoggableType.Raw:
47
+ publisher.setRaw(newValue.value, table.getTimestamp())
48
+
49
+ case LogValue.LoggableType.Boolean:
50
+ publisher.setBoolean(newValue.value, table.getTimestamp())
51
+ case LogValue.LoggableType.Integer:
52
+ publisher.setInteger(newValue.value, table.getTimestamp())
53
+ case LogValue.LoggableType.Float:
54
+ publisher.setFloat(newValue.value, table.getTimestamp())
55
+ case LogValue.LoggableType.Double:
56
+ publisher.setDouble(newValue.value, table.getTimestamp())
57
+ case LogValue.LoggableType.String:
58
+ publisher.setString(newValue.value, table.getTimestamp())
59
+ case LogValue.LoggableType.BooleanArray:
60
+ publisher.setBooleanArray(newValue.value, table.getTimestamp())
61
+ case LogValue.LoggableType.IntegerArray:
62
+ publisher.setIntegerArray(newValue.value, table.getTimestamp())
63
+ case LogValue.LoggableType.FloatArray:
64
+ publisher.setFloatArray(newValue.value, table.getTimestamp())
65
+ case LogValue.LoggableType.DoubleArray:
66
+ publisher.setDoubleArray(newValue.value, table.getTimestamp())
67
+ case LogValue.LoggableType.StringArray:
68
+ publisher.setStringArray(newValue.value, table.getTimestamp())
@@ -0,0 +1,102 @@
1
+ import os
2
+ import argparse
3
+ import importlib.metadata
4
+ import logging
5
+ import pathlib
6
+ from tempfile import gettempdir
7
+ import time
8
+ import typing
9
+ from watchdog.events import FileSystemEventHandler
10
+ from watchdog.observers import Observer
11
+
12
+
13
+ from pykit.loggedrobot import LoggedRobot
14
+
15
+
16
+ logger = logging.getLogger("pyfrc.sim")
17
+
18
+
19
+ entry_points = importlib.metadata.entry_points
20
+
21
+ AKIT_FILENAME = "akit-log-path.txt"
22
+
23
+
24
+ class PyKitReplayWatch:
25
+ """
26
+ Runs the robot in simulation and replay watch
27
+ """
28
+
29
+ do_update: bool = False
30
+
31
+ @classmethod
32
+ def doUpdate(cls) -> bool:
33
+ return cls.do_update
34
+
35
+ def __init__(self, parser: argparse.ArgumentParser):
36
+ self.simexts = {}
37
+
38
+ for entry_point in entry_points(group="robotpy_sim.2026"):
39
+ try:
40
+ sim_ext_module = entry_point.load()
41
+ except ImportError:
42
+ print(f"WARNING: Error detected in {entry_point}")
43
+ continue
44
+
45
+ self.simexts[entry_point.name] = sim_ext_module
46
+
47
+ try:
48
+ cmd_help = importlib.metadata.metadata(entry_point.dist.name)["summary"]
49
+ except AttributeError:
50
+ cmd_help = "Load specified simulation extension"
51
+ parser.add_argument(
52
+ f"--{entry_point.name}",
53
+ default=False,
54
+ action="store_true",
55
+ help=cmd_help,
56
+ )
57
+
58
+ def run(
59
+ self,
60
+ options: argparse.Namespace, # pylint: disable=unused-argument
61
+ project_path: pathlib.Path, # pylint: disable=unused-argument
62
+ robot_class: typing.Type[LoggedRobot], # pylint: disable=unused-argument
63
+ ):
64
+
65
+ PyKitReplayWatch.do_update = False
66
+
67
+ class UpdateHandler(FileSystemEventHandler):
68
+ def on_modified(self, event):
69
+ if not event.is_directory and event.src_path.endswith(".py"):
70
+ print("[PyKit] Modification detected!")
71
+ PyKitReplayWatch.do_update = True
72
+
73
+ file_handler = UpdateHandler()
74
+ self.observer = Observer()
75
+ self.observer.schedule(file_handler, ".", recursive=True)
76
+
77
+ self.observer.start()
78
+
79
+ if "LOG_PATH" not in os.environ:
80
+ # see if we can pull from ascope's actively loaded log
81
+ readPath = os.path.join(gettempdir(), AKIT_FILENAME)
82
+ if not os.path.exists(readPath):
83
+ print("[PyKit] Cannot load log to replay!")
84
+ return
85
+ with open(
86
+ os.path.join(gettempdir(), AKIT_FILENAME), "r", encoding="utf-8"
87
+ ) as f:
88
+ readfilepath = f.readline()
89
+ os.environ["LOG_PATH"] = readfilepath
90
+ print(f"[PyKit] Logging from {readfilepath}")
91
+ else:
92
+ logpath = os.environ["LOG_PATH"]
93
+ print(f"[PyKit] Logging from {logpath}")
94
+
95
+ while True:
96
+ PyKitReplayWatch.do_update = False
97
+ print("[PyKit] Running replay...")
98
+ # this is hacky, a real solution is needed for resetting environment
99
+ os.system("python -m robotpy sim --nogui")
100
+ print("[PyKit] replay finished...")
101
+ while not PyKitReplayWatch.doUpdate():
102
+ time.sleep(1)
@@ -0,0 +1,2 @@
1
+ extraHeader = "PyKit"
2
+ entryMetadata = '{"source": "PyKit"}'
@@ -0,0 +1,175 @@
1
+ from typing import Any, Iterator
2
+
3
+ from wpiutil.log import DataLogReader
4
+
5
+ from pykit.logreplaysource import LogReplaySource
6
+ from pykit.logtable import LogTable
7
+ from pykit.logvalue import LogValue
8
+
9
+
10
+ def safeNext(val: Iterator[Any]):
11
+ try:
12
+ return next(val)
13
+ except StopIteration:
14
+ return None
15
+
16
+
17
+ class WPILOGReader(LogReplaySource):
18
+ """Reads a .wpilog file and provides the data as a replay source."""
19
+
20
+ def __init__(self, filename: str) -> None:
21
+ """
22
+ Constructor for WPILOGReader.
23
+
24
+ :param filename: The path to the .wpilog file.
25
+ """
26
+ self.filename = filename
27
+
28
+ def start(self):
29
+ self.reader = DataLogReader(self.filename)
30
+ self.isValid = (
31
+ self.reader.isValid()
32
+ # and self.reader.getExtraHeader() == wpilogconstants.extraHeader
33
+ )
34
+ print(self.reader.isValid())
35
+ print(self.reader.getExtraHeader())
36
+ self.records = iter([])
37
+
38
+ if self.isValid:
39
+ # Create a new iterator for the initial entry scan
40
+ self.records = iter(self.reader)
41
+ self.entryIds: dict[int, str] = {}
42
+ self.entryTypes: dict[int, LogValue.LoggableType] = {}
43
+ self.timestamp = None
44
+ self.entryCustomTypes = {}
45
+
46
+ else:
47
+ print("[WPILogReader] not valid")
48
+
49
+ def updateTable(self, table: LogTable) -> bool:
50
+ """
51
+ Updates a LogTable with the next record from the log file.
52
+
53
+ :param table: The LogTable to update.
54
+ :return: True if the table was updated, False if the end of the log was reached.
55
+ """
56
+ if not self.isValid:
57
+ return False
58
+
59
+ if self.timestamp is not None:
60
+ table.setTimestamp(self.timestamp)
61
+
62
+ keepLogging = False
63
+ while (record := safeNext(self.records)) is not None:
64
+ if record.isControl():
65
+ if record.isStart():
66
+ startData = record.getStartData()
67
+ self.entryIds[startData.entry] = startData.name
68
+ typeStr = startData.type
69
+ self.entryTypes[startData.entry] = (
70
+ LogValue.LoggableType.fromWPILOGType(typeStr)
71
+ )
72
+ if typeStr.startswith("struct:") or typeStr == "structschema":
73
+ self.entryCustomTypes[startData.entry] = typeStr
74
+ else:
75
+ entry = self.entryIds.get(record.getEntry())
76
+ if entry is not None:
77
+ if entry == self.timestampKey:
78
+ firsttimestamp = self.timestamp is None
79
+ self.timestamp = record.getInteger()
80
+ if firsttimestamp:
81
+ table.setTimestamp(self.timestamp)
82
+ else:
83
+ keepLogging = True # we still have a timestamp, just need to wait until next iter
84
+ break
85
+ elif (
86
+ self.timestamp is not None
87
+ and record.getTimestamp() == self.timestamp
88
+ ):
89
+ entry = entry[1:]
90
+ if entry.startswith("ReplayOutputs"):
91
+ continue
92
+ customType = self.entryCustomTypes.get(record.getEntry())
93
+ entryType = self.entryTypes.get(record.getEntry())
94
+ if customType is None:
95
+ customType = ""
96
+ match entryType:
97
+ case LogValue.LoggableType.Raw:
98
+ table.putValue(
99
+ entry,
100
+ LogValue.withType(
101
+ entryType, record.getRaw(), customType
102
+ ),
103
+ )
104
+ case LogValue.LoggableType.Boolean:
105
+ table.putValue(
106
+ entry,
107
+ LogValue.withType(
108
+ entryType, record.getBoolean(), customType
109
+ ),
110
+ )
111
+ case LogValue.LoggableType.Integer:
112
+ table.putValue(
113
+ entry,
114
+ LogValue.withType(
115
+ entryType, record.getInteger(), customType
116
+ ),
117
+ )
118
+ case LogValue.LoggableType.Float:
119
+ table.putValue(
120
+ entry,
121
+ LogValue.withType(
122
+ entryType, record.getFloat(), customType
123
+ ),
124
+ )
125
+ case LogValue.LoggableType.Double:
126
+ table.putValue(
127
+ entry,
128
+ LogValue.withType(
129
+ entryType, record.getDouble(), customType
130
+ ),
131
+ )
132
+ case LogValue.LoggableType.String:
133
+ table.putValue(
134
+ entry,
135
+ LogValue.withType(
136
+ entryType, record.getString(), customType
137
+ ),
138
+ )
139
+ case LogValue.LoggableType.BooleanArray:
140
+ table.putValue(
141
+ entry,
142
+ LogValue.withType(
143
+ entryType, record.getBooleanArray(), customType
144
+ ),
145
+ )
146
+ case LogValue.LoggableType.IntegerArray:
147
+ table.putValue(
148
+ entry,
149
+ LogValue.withType(
150
+ entryType, record.getIntegerArray(), customType
151
+ ),
152
+ )
153
+ case LogValue.LoggableType.FloatArray:
154
+ table.putValue(
155
+ entry,
156
+ LogValue.withType(
157
+ entryType, record.getFloatArray(), customType
158
+ ),
159
+ )
160
+ case LogValue.LoggableType.DoubleArray:
161
+ table.putValue(
162
+ entry,
163
+ LogValue.withType(
164
+ entryType, record.getDoubleArray(), customType
165
+ ),
166
+ )
167
+ case LogValue.LoggableType.StringArray:
168
+ table.putValue(
169
+ entry,
170
+ LogValue.withType(
171
+ entryType, record.getStringArray(), customType
172
+ ),
173
+ )
174
+
175
+ return keepLogging
@@ -0,0 +1,279 @@
1
+ import os
2
+ import random
3
+ import datetime
4
+ from tempfile import gettempdir
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ from hal import MatchType
9
+ from wpilib import DataLogManager, RobotBase, RobotController
10
+ from pykit.logdatareciever import LogDataReciever
11
+ from pykit.logger import Logger
12
+ from pykit.logtable import LogTable
13
+ from pykit.logvalue import LogValue
14
+ from pykit.wpilog import wpilogconstants
15
+
16
+ if TYPE_CHECKING:
17
+ from wpiutil.log import DataLog
18
+
19
+
20
+ ASCOPE_FILENAME = "ascope-log-path.txt"
21
+
22
+
23
+ class WPILOGWriter(LogDataReciever):
24
+ """Writes a LogTable to a .wpilog file."""
25
+
26
+ log: "DataLog"
27
+ defaultPathRio: str = "/U/logs"
28
+ defaultPathSim: str = "pyLogs"
29
+
30
+ folder: str
31
+ filename: str
32
+ randomIdentifier: str
33
+ dsAttachedTime: int = 0
34
+ autoRename: bool
35
+ logDate: datetime.datetime | None
36
+ logMatchText: str
37
+
38
+ isOpen: bool = False
39
+ lastTable: LogTable
40
+ timestampId: int
41
+ entryIds: dict[str, int]
42
+ entryTypes: dict[str, LogValue.LoggableType]
43
+ entryUnits: dict[str, str]
44
+
45
+ def __init__(self, filename: str | None = None):
46
+ path = self.defaultPathSim if RobotBase.isSimulation() else self.defaultPathRio
47
+
48
+ self.randomIdentifier = f"{random.randint(0, 0xFFFF):04X}"
49
+
50
+ self.folder = os.path.abspath(
51
+ os.path.dirname(filename) if filename is not None else path
52
+ )
53
+ self.filename = (
54
+ os.path.basename(filename)
55
+ if filename is not None
56
+ else f"pykit_{self.randomIdentifier}.wpilog"
57
+ )
58
+ self.autoRename = False
59
+
60
+ def start(self):
61
+ # create folder if necessary
62
+ if not os.path.exists(self.folder):
63
+ os.makedirs(self.folder)
64
+
65
+ # delete log if it exists
66
+
67
+ # create a new log
68
+ fullPath = os.path.join(self.folder, self.filename)
69
+ print(f"[WPILogWriter] Creating WPILOG file at {fullPath}")
70
+ # DataLogManager.stop() # ensure its fully stopped
71
+ if os.path.exists(fullPath):
72
+ print("[WPILogWriter] File exists, overwriting")
73
+ os.remove(fullPath)
74
+ DataLogManager.stop()
75
+ DataLogManager.start(self.folder, self.filename)
76
+ print(DataLogManager.getLogDir())
77
+ DataLogManager.logNetworkTables(False)
78
+ self.log = DataLogManager.getLog()
79
+ # self.log = DataLogWriter(fullPath, wpilogconstants.extraHeader)
80
+
81
+ self.isOpen = True
82
+ self.timestampId = self.log.start(
83
+ self.timestampKey,
84
+ LogValue.LoggableType.Integer.getWPILOGType(),
85
+ wpilogconstants.entryMetadata,
86
+ 0,
87
+ )
88
+ self.lastTable = LogTable(0)
89
+
90
+ self.entryIds: dict[str, int] = {}
91
+ self.entryTypes: dict[str, LogValue.LoggableType] = {}
92
+ self.entryUnits: dict[str, str] = {}
93
+ self.logDate = None
94
+ self.logMatchText = f"pykit_{self.randomIdentifier}"
95
+
96
+ def end(self):
97
+ print("[WPILogWriter] Shutting down")
98
+ self.log.flush()
99
+ self.log.stop()
100
+
101
+ if RobotBase.isSimulation() and Logger.isReplay():
102
+ # open ascope
103
+ fullpath = os.path.join(gettempdir(), ASCOPE_FILENAME)
104
+ if not os.path.exists(gettempdir()):
105
+ return
106
+ fullLogPath = os.path.abspath(os.path.join(self.folder, self.filename))
107
+ print(f"Sending {fullLogPath} to AScope")
108
+ with open(fullpath, "w", encoding="utf-8") as f:
109
+ f.write(fullLogPath)
110
+
111
+ # DataLogManager.stop()
112
+
113
+ def putTable(self, table: LogTable):
114
+ if not self.isOpen:
115
+ return
116
+ if self.autoRename:
117
+ # rename log if necessary
118
+ if self.logDate is None:
119
+ if (
120
+ table.get("DriverStation/DSAttached", False)
121
+ and table.get("SystemStats/SystemTimeValid", False)
122
+ ) or RobotBase.isSimulation():
123
+ if self.dsAttachedTime == 0:
124
+ self.dsAttachedTime = RobotController.getFPGATime() / 1e6
125
+ elif (
126
+ RobotController.getFPGATime() / 1e6 - self.dsAttachedTime
127
+ ) > 5 or RobotBase.isSimulation():
128
+ self.logDate = datetime.datetime.now()
129
+ else:
130
+ self.dsAttachedTime = 0
131
+
132
+ matchType: MatchType
133
+ match table.get("DriverStation/MatchType", 0):
134
+ case 1:
135
+ matchType = MatchType.practice
136
+ case 2:
137
+ matchType = MatchType.qualification
138
+ case 3:
139
+ matchType = MatchType.elimination
140
+ case _:
141
+ matchType = MatchType.none
142
+
143
+ if self.logMatchText == "" and matchType != MatchType.none:
144
+ match matchType:
145
+ case MatchType.practice:
146
+ self.logMatchText = "p"
147
+ case MatchType.qualification:
148
+ self.logMatchText = "q"
149
+ case MatchType.elimination:
150
+ self.logMatchText = "e"
151
+ case _:
152
+ self.logMatchText = "u"
153
+ self.logMatchText += str(table.get("DriverStation/MatchNumber", 0))
154
+
155
+ # update filename
156
+ filename = "pykit_"
157
+ if self.logDate is not None:
158
+ filename += self.logDate.strftime("%Y%m%d_%H%M%S")
159
+ else:
160
+ filename += self.randomIdentifier
161
+ eventName = (
162
+ table.get("DriverStation/EventName", "").lower().replace(" ", "_")
163
+ )
164
+ if eventName != "":
165
+ filename += f"_{eventName}"
166
+ if self.logMatchText != "":
167
+ filename += f"_{self.logMatchText}"
168
+ filename += ".wpilog"
169
+ if self.filename != filename:
170
+ print(f"[WPILogWriter] Renaming log to {filename}")
171
+ # DataLogManager.stop()
172
+ # self.log.stop()
173
+ self.log.stop()
174
+ fullPath = os.path.join(self.folder, self.filename)
175
+ if os.path.exists(fullPath):
176
+ print(f"[WPILogWriter] Old file removed ({self.filename})")
177
+ os.remove(fullPath)
178
+
179
+ # DataLogManager.logNetworkTables(False)
180
+ DataLogManager.stop()
181
+ DataLogManager.start(self.folder, filename)
182
+ self.log = DataLogManager.getLog()
183
+ # self.log = DataLogWriter(fullPath)
184
+ # self.log._startFile()
185
+ self.timestampId = self.log.start(
186
+ "/Timestamp",
187
+ LogValue.LoggableType.Integer.getWPILOGType(),
188
+ wpilogconstants.entryMetadata,
189
+ 0,
190
+ )
191
+ self.filename = filename
192
+
193
+ # write timestamp
194
+ self.log.appendInteger(
195
+ self.timestampId, table.getTimestamp(), table.getTimestamp()
196
+ )
197
+
198
+ # get new and old data
199
+ newMap = table.getAll()
200
+ oldMap = self.lastTable.getAll()
201
+
202
+ # encode fields
203
+ for key, newValue in newMap.items():
204
+ fieldType = newValue.log_type
205
+ appendData = False
206
+
207
+ if key not in self.entryIds: # new field
208
+ entryId = self.log.start(
209
+ key,
210
+ newValue.getWPILOGType(),
211
+ wpilogconstants.entryMetadata,
212
+ table.getTimestamp(),
213
+ )
214
+ self.entryIds[key] = entryId
215
+ self.entryTypes[key] = newValue.log_type
216
+ self.entryUnits[key] = ""
217
+
218
+ appendData = True
219
+ elif newValue != oldMap.get(key): # existing field changed
220
+ appendData = True
221
+
222
+ # check if type changed
223
+ elif newValue.log_type != self.entryTypes[key]:
224
+ print(
225
+ f"[WPILOGWriter] Type of {key} changed from "
226
+ f"{self.entryTypes[key]} to {newValue.log_type}, skipping log"
227
+ )
228
+ continue
229
+
230
+ if appendData:
231
+ entryId = self.entryIds[key]
232
+ match fieldType:
233
+ case LogValue.LoggableType.Raw:
234
+ self.log.appendRaw(
235
+ entryId, newValue.value, table.getTimestamp()
236
+ )
237
+ case LogValue.LoggableType.Boolean:
238
+ self.log.appendBoolean(
239
+ entryId, newValue.value, table.getTimestamp()
240
+ )
241
+ case LogValue.LoggableType.Integer:
242
+ self.log.appendInteger(
243
+ entryId, newValue.value, table.getTimestamp()
244
+ )
245
+ case LogValue.LoggableType.Float:
246
+ self.log.appendFloat(
247
+ entryId, newValue.value, table.getTimestamp()
248
+ )
249
+ case LogValue.LoggableType.Double:
250
+ self.log.appendDouble(
251
+ entryId, newValue.value, table.getTimestamp()
252
+ )
253
+ case LogValue.LoggableType.String:
254
+ self.log.appendString(
255
+ entryId, newValue.value, table.getTimestamp()
256
+ )
257
+ case LogValue.LoggableType.BooleanArray:
258
+ self.log.appendBooleanArray(
259
+ entryId, newValue.value, table.getTimestamp()
260
+ )
261
+ case LogValue.LoggableType.IntegerArray:
262
+ self.log.appendIntegerArray(
263
+ entryId, newValue.value, table.getTimestamp()
264
+ )
265
+ case LogValue.LoggableType.FloatArray:
266
+ self.log.appendFloatArray(
267
+ entryId, newValue.value, table.getTimestamp()
268
+ )
269
+ case LogValue.LoggableType.DoubleArray:
270
+ self.log.appendDoubleArray(
271
+ entryId, newValue.value, table.getTimestamp()
272
+ )
273
+ case LogValue.LoggableType.StringArray:
274
+ self.log.appendStringArray(
275
+ entryId, newValue.value, table.getTimestamp()
276
+ )
277
+
278
+ self.log.flush()
279
+ self.lastTable = table
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: robotpy-pykit
3
+ Version: 0.1.3b1
4
+ Summary: A logging, telemetry, and replay framework for FRC robots running python
5
+ Author-email: Luke Maxwell <luke@whsrobotics.org>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/1757WestwoodRobotics/PyKit
8
+ Keywords: pykit,frc,robotics,logging,telemetry,replay
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: pyntcore>=2025.3.2.2
13
+ Requires-Dist: robotpy-wpimath>=2025.3.2.2
14
+ Requires-Dist: robotpy-wpiutil>=2025.3.2.2
15
+ Requires-Dist: wpilib>=2025.3.2.2
16
+ Requires-Dist: robotpy-hal>=2025.3.2.2
17
+ Requires-Dist: pyfrc>=2025.1.0; platform_machine == "x86_64"
18
+ Requires-Dist: watchdog>=6.0.0; platform_machine == "x86_64"
19
+ Dynamic: license-file
20
+
21
+ # PyKit
22
+
23
+ PyKit is a Pure-Python logging, telementary, and replay framework developed by [Team 1757](https://whsrobotics.org) for use on Python robots. PyKit enables replaying log files to re-simulate robot code based on real data.
24
+
25
+ See also
26
+ - [AdvantageKit](https://github.com/Mechanical-Advantage/AdvantageKit/) the inspiration project, in Java
27
+ - [WPILib Data Logging](https://docs.wpilib.org/en/stable/docs/software/telemetry/datalog.html) a simpler logging system included in WPILib
28
+
29
+ Documentation and example projects coming soon!
30
+
31
+ Feedback, feature requests, and bug reports are welcome on the [issues](https://github.com/1757WestwoodRobotics/PyKit/issues) page. For non-public inquiries, please send a message to contact@whsrobotics.org.
@@ -0,0 +1,22 @@
1
+ pykit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pykit/autolog.py,sha256=y98ei8QXh0Ig5F5UxgKRglH17J3kleqkl6B1PVPCE00,12413
3
+ pykit/logdatareciever.py,sha256=n0Xbx93gVzAhVU7lTjXe8LjHc4jeeEgGdQJi3BJw0ME,221
4
+ pykit/loggedrobot.py,sha256=MYIoXgEi7LhiGqfgh4KRvSO4SdpmWG7SIl_cA4VIjmM,2609
5
+ pykit/logger.py,sha256=Qgs5rEIVG80dQI1tOc-KLm5iwQ41lPv4_4BYzD1dyu4,7367
6
+ pykit/logreplaysource.py,sha256=w-HqCuUqjv77FXyRmsJg1K9FbKd_KNvICJ_ayXECMXI,349
7
+ pykit/logtable.py,sha256=SHFkRo1bJ790gw1GdhBqfR5j0qbuFmP4uFtJRZKCoRg,9448
8
+ pykit/logvalue.py,sha256=ic5N3oxI5jmD75fOcyNTsErYp3JDhAI_DdqN_1E6sPs,4429
9
+ pykit/inputs/loggableds.py,sha256=KsOtSOkvShdw24Scxaq1nx7MCjltz7-0tuM5xXABxQY,5011
10
+ pykit/networktables/loggeddashboardchooser.py,sha256=GW3dH0PS1BJJ8K5UXhp7q9Yk9DtHo1YGq2tbjByRrS4,1668
11
+ pykit/networktables/loggednetworkinput.py,sha256=t5uH8B8cLq_9NtdbYQVa8OO0alhbJxUMwd8i0sVmwho,272
12
+ pykit/networktables/nt4Publisher.py,sha256=D9DSoWU9Nrwp7xpFvx7yltBSblX4DhIjI29tkUEBzUk,2860
13
+ pykit/watch/cli_replaywatch.py,sha256=hpiIndevBZsY1S9aDx4TAFxb7PK3mxcsNyea7FbF8g4,3244
14
+ pykit/wpilog/wpilogconstants.py,sha256=BdhugV6UC4ShP5s7lG8CO95Txc5qlVmxTOxyjyhpmfo,60
15
+ pykit/wpilog/wpilogreader.py,sha256=6wtO79aBCoP8M7kzuABrkX0eX6kN8I0zWC1q7E72dQY,7646
16
+ pykit/wpilog/wpilogwriter.py,sha256=WaRQMu8ob2sqrGPmUdLqb7C6pA9NswkvNaJm4x7DdWo,10772
17
+ robotpy_pykit-0.1.3b1.dist-info/licenses/LICENSE,sha256=QEEUUO4dKcmhX9pW9BP4WMEMWqY-jhnmwdPi-Oo-S9w,1554
18
+ robotpy_pykit-0.1.3b1.dist-info/METADATA,sha256=Sp1K_xWrdOOlhTFLrYzwpk_PUHR8mwKL8eHAryB1Tu4,1517
19
+ robotpy_pykit-0.1.3b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ robotpy_pykit-0.1.3b1.dist-info/entry_points.txt,sha256=fLvOR7Epp2Wx9epr_YMtEbQoSMiHfxozV5QsiraA_Os,63
21
+ robotpy_pykit-0.1.3b1.dist-info/top_level.txt,sha256=tJQlZyFURJ0FEmLtjr4t5CR3lrZKIFEgdw_xxabFeeY,6
22
+ robotpy_pykit-0.1.3b1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+