mcpField
A
analyze-wpilog

analyze-wpilog

github.com/jonahsniderData & databases2358 installsUpdated 1d ago
Repository

About analyze-wpilog

analyze-wpilog is a skill for analyzing FRC logs by reading WPILOG binary files with TypeScript or JavaScript. It supports entry catalogs, record parsing, and struct decoding, with a strict mode for handling orphan records.

Key features

  1. 01Catalog WPILOG entries
  2. 02Parse DataLog records
  3. 03Filter records by name and type
  4. 04Decode struct data
  5. 05Detect orphan records in strict mode

Use cases

  1. 01Inspecting the entries in robot logs
  2. 02Analyzing robot state and mode durations
  3. 03Reading structured log data such as localization

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/jonahsnider/wpilog-parser --skill analyze-wpilog

After installing, type/analyze-wpilogin Claude Code to confirm it loaded.

Details

From the project README

wpilog-parser

WPILOG is the binary log format used by logging libraries in the FRC ecosystem. The wpilog-parser library makes it easy to read and analyze the log files using TS/JS.

Creating a project

  1. Init a project using pnpm init or similar

  2. Add wpilog-parser as a dependency

  3. Catalog the entries in the WPILOG file before deciding what to analyze

    pnpm wpilog catalog ./example.wpilog
    

    The default output is plaintext. Use --json, --jsonl/--ndjson, or --csv when another format is more useful.

Analyzing logs

Use parseDataLog to parse DataLog bytes. Filter by name and narrow on type:

import { readFile } from 'node:fs/promises';
import { isDataRecord, parseDataLog, RecordType } from 'wpilog-parser';

const bytes = await readFile('./example.wpilog');

for (const record of parseDataLog(bytes)) {
	if (!isDataRecord(record)) continue;

	if (record.name === '/Robot/Intake/Voltage' && record.type === RecordType.Double) {
		console.log(record.timestamp, record.payload);
	}
}
Decoding structs
import { parseDataLog, RecordType } from 'wpilog-parser';

for (const record of parseDataLog(bytes)) {
	if (record.type === RecordType.Struct && record.name === '/Robot/Localization/EstimatedPose') {
		console.log(record.timestamp, record.payload);
		// 18688018n {
		//   translation: { x: 10.289, y: 0.47 },
		//   rotation: { value: 1.5707961072652017 },
		// }
	}
}
Strict mode
for (const record of parseDataLog(bytes, { strict: true })) {
	// throws on orphan data records instead of skipping
}

Robot state cheatsheet

When DriverStation logging is on, every WPILOG has these boolean entries:

EntryMeaning
/DS:enabledtrue while the robot is enabled (any mode).
/DS:autonomoustrue during autonomous. false means teleop (unless /DS:test).
/DS:testtrue during test mode.
/DS:estoptrue if e-stopped.

The mode flags (autonomous, test) reflect what the DS would run if enabled, so check /DS:enabled together with them. Timestamps are microseconds (bigint).

Tracking enable/mode durations
import { isDataRecord, parseDataLog, RecordType } from 'wpilog-parser';

type Mode = 'auto' | 'teleop' | 'test';
function modeOf(auto: boolean, test: boolean): Mode {
	if (test) return 'test';
	if (auto) return 'auto';
	return 'teleop';
}

let enabled = false;
let auto = false;
let test = false;
let enabledSince: bigint | null = null;
const totals = { auto: 0n, teleop: 0n, test: 0n };

for (const r of parseDataLog(bytes)) {
	if (!isDataRecord(r) || r.type !== RecordType.Boolean) continue;

	const wasEnabled = enabled;
	const prevMode = modeOf(auto, test);

	if (r.name === '/DS:enabled') enabled = r.payload;
	else if (r.name === '/DS:autonomous') auto = r.payload;
	else if (r.name === '/DS:test') test = r.payload;
	else continue;

	if (wasEnabled && (!enabled || modeOf(auto, test) !== prevMode)) {
		totals[prevMode] += r.timestamp - (enabledSince ?? r.timestamp);
		enabledSince = enabled ? r.timestamp : null;
	} else if (!wasEnabled && enabled) {
		enabledSince = r.timestamp;
	}
}

// Durations in microseconds; divide by 1_000_000n for seconds.
console.log(totals);

Comments

Markdown supported