blob: 24a55fa18686bcd6426f7e6b09adf23f44cafa15 [file] [log] [blame]
Milo Lin13906572023-03-15 20:55:22 -07001import {ByteBuffer} from 'flatbuffers'
James Kuszmaulf7b5d622023-03-11 15:14:53 -08002import {ClientStatistics} from '../../aos/network/message_bridge_client_generated'
3import {ServerStatistics, State as ConnectionState} from '../../aos/network/message_bridge_server_generated'
Milo Lin13906572023-03-15 20:55:22 -07004import {Connection} from '../../aos/network/www/proxy'
5import {ZeroingError} from '../../frc971/control_loops/control_loops_generated'
6import {Status as DrivetrainStatus} from '../../frc971/control_loops/drivetrain/drivetrain_status_generated'
7import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated'
8import {TargetMap} from '../../frc971/vision/target_map_generated'
James Kuszmaulf7b5d622023-03-11 15:14:53 -08009import {ArmState, ArmStatus, EndEffectorState, Status as SuperstructureStatus} from '../control_loops/superstructure/superstructure_status_generated'
Milo Lin13906572023-03-15 20:55:22 -070010import {RejectionReason} from '../localizer/status_generated'
11import {TargetEstimateDebug, Visualization} from '../localizer/visualization_generated'
James Kuszmaulf7b5d622023-03-11 15:14:53 -080012import {Class} from '../vision/game_pieces_generated'
Maxwell Hendersonad312342023-01-10 12:07:47 -080013
14import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
15
16// (0,0) is field center, +X is toward red DS
17const FIELD_SIDE_Y = FIELD_WIDTH / 2;
18const FIELD_EDGE_X = FIELD_LENGTH / 2;
19
James Kuszmaulfb894572023-02-23 17:25:06 -080020const ROBOT_WIDTH = 25 * IN_TO_M;
21const ROBOT_LENGTH = 32 * IN_TO_M;
Maxwell Hendersonad312342023-01-10 12:07:47 -080022
23const PI_COLORS = ['#ff00ff', '#ffff00', '#00ffff', '#ffa500'];
James Kuszmaulfb894572023-02-23 17:25:06 -080024const PIS = ['pi1', 'pi2', 'pi3', 'pi4'];
Maxwell Hendersonad312342023-01-10 12:07:47 -080025
26export class FieldHandler {
27 private canvas = document.createElement('canvas');
James Kuszmaulfb894572023-02-23 17:25:06 -080028 private localizerOutput: LocalizerOutput|null = null;
Maxwell Hendersonad312342023-01-10 12:07:47 -080029 private drivetrainStatus: DrivetrainStatus|null = null;
Milo Line6571c02023-03-04 21:08:20 -080030 private superstructureStatus: SuperstructureStatus|null = null;
Maxwell Hendersonad312342023-01-10 12:07:47 -080031
32 // Image information indexed by timestamp (seconds since the epoch), so that
33 // we can stop displaying images after a certain amount of time.
James Kuszmaulfb894572023-02-23 17:25:06 -080034 private localizerImageMatches = new Map<number, Visualization>();
35 private x: HTMLElement = (document.getElementById('x') as HTMLElement);
Maxwell Hendersonad312342023-01-10 12:07:47 -080036 private y: HTMLElement = (document.getElementById('y') as HTMLElement);
37 private theta: HTMLElement =
38 (document.getElementById('theta') as HTMLElement);
Maxwell Hendersonad312342023-01-10 12:07:47 -080039 private imagesAcceptedCounter: HTMLElement =
40 (document.getElementById('images_accepted') as HTMLElement);
Milo Lin13906572023-03-15 20:55:22 -070041 // HTML elements for rejection reasons for individual pis. Indices
42 // corresponding to RejectionReason enum values will be for those reasons. The
43 // final row will account for images rejected by the aprilrobotics detector
44 // instead of the localizer.
James Kuszmaul4f0e4362023-03-12 20:03:59 -070045 private rejectionReasonCells: HTMLElement[][] = [];
James Kuszmaulf7b5d622023-03-11 15:14:53 -080046 private messageBridgeDiv: HTMLElement =
47 (document.getElementById('message_bridge_status') as HTMLElement);
48 private clientStatuses = new Map<string, HTMLElement>();
49 private serverStatuses = new Map<string, HTMLElement>();
Maxwell Hendersonad312342023-01-10 12:07:47 -080050 private fieldImage: HTMLImageElement = new Image();
Milo Line6571c02023-03-04 21:08:20 -080051 private endEffectorState: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080052 (document.getElementById('end_effector_state') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080053 private wrist: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080054 (document.getElementById('wrist') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080055 private armState: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080056 (document.getElementById('arm_state') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080057 private gamePiece: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080058 (document.getElementById('game_piece') as HTMLElement);
James Kuszmaulef6b7402023-04-09 14:29:38 -070059 private gamePiecePosition: HTMLElement =
60 (document.getElementById('game_piece_position') as HTMLElement);
James Kuszmaulf7b5d622023-03-11 15:14:53 -080061 private armX: HTMLElement = (document.getElementById('arm_x') as HTMLElement);
62 private armY: HTMLElement = (document.getElementById('arm_y') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080063 private circularIndex: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080064 (document.getElementById('arm_circular_index') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080065 private roll: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080066 (document.getElementById('arm_roll') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080067 private proximal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080068 (document.getElementById('arm_proximal') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080069 private distal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080070 (document.getElementById('arm_distal') as HTMLElement);
Milo Lin72fb9012023-03-10 19:53:19 -080071 private zeroingFaults: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080072 (document.getElementById('zeroing_faults') as HTMLElement);
Maxwell Hendersonad312342023-01-10 12:07:47 -080073 constructor(private readonly connection: Connection) {
74 (document.getElementById('field') as HTMLElement).appendChild(this.canvas);
75
James Kuszmaulf7b5d622023-03-11 15:14:53 -080076 this.fieldImage.src = '2023.png';
James Kuszmaulfb894572023-02-23 17:25:06 -080077
James Kuszmaul4f0e4362023-03-12 20:03:59 -070078 // Construct a table header.
79 {
80 const row = document.createElement('div');
81 const nameCell = document.createElement('div');
Milo Lin13906572023-03-15 20:55:22 -070082 nameCell.innerHTML = 'Rejection Reason';
James Kuszmaul4f0e4362023-03-12 20:03:59 -070083 row.appendChild(nameCell);
84 for (const pi of PIS) {
85 const nodeCell = document.createElement('div');
86 nodeCell.innerHTML = pi;
87 row.appendChild(nodeCell);
88 }
89 document.getElementById('vision_readouts').appendChild(row);
90 }
James Kuszmaulfb894572023-02-23 17:25:06 -080091 for (const value in RejectionReason) {
92 // Typescript generates an iterator that produces both numbers and
93 // strings... don't do anything on the string iterations.
94 if (isNaN(Number(value))) {
95 continue;
96 }
97 const row = document.createElement('div');
98 const nameCell = document.createElement('div');
99 nameCell.innerHTML = RejectionReason[value];
100 row.appendChild(nameCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700101 this.rejectionReasonCells.push([]);
102 for (const pi of PIS) {
103 const valueCell = document.createElement('div');
104 valueCell.innerHTML = 'NA';
Milo Lin13906572023-03-15 20:55:22 -0700105 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
106 valueCell);
107 row.appendChild(valueCell);
108 }
109 document.getElementById('vision_readouts').appendChild(row);
110 }
111
112 // Add rejection reason row for aprilrobotics rejections.
113 {
114 const row = document.createElement('div');
115 const nameCell = document.createElement('div');
116 nameCell.innerHTML = 'Rejected by aprilrobotics';
117 row.appendChild(nameCell);
118 this.rejectionReasonCells.push([]);
119 for (const pi of PIS) {
120 const valueCell = document.createElement('div');
121 valueCell.innerHTML = 'NA';
122 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
123 valueCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700124 row.appendChild(valueCell);
125 }
James Kuszmaulfb894572023-02-23 17:25:06 -0800126 document.getElementById('vision_readouts').appendChild(row);
127 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800128
129 for (let ii = 0; ii < PI_COLORS.length; ++ii) {
130 const legendEntry = document.createElement('div');
131 legendEntry.style.color = PI_COLORS[ii];
132 legendEntry.innerHTML = 'PI' + (ii + 1).toString()
133 document.getElementById('legend').appendChild(legendEntry);
134 }
135
136 this.connection.addConfigHandler(() => {
137 // Visualization message is reliable so that we can see *all* the vision
138 // matches.
James Kuszmaulfb894572023-02-23 17:25:06 -0800139 for (const pi in PIS) {
140 this.connection.addReliableHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800141 '/' + PIS[pi] + '/camera', 'y2023.localizer.Visualization',
James Kuszmaulfb894572023-02-23 17:25:06 -0800142 (data) => {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700143 this.handleLocalizerDebug(Number(pi), data);
James Kuszmaulfb894572023-02-23 17:25:06 -0800144 });
145 }
Milo Lin13906572023-03-15 20:55:22 -0700146 for (const pi in PIS) {
147 // Make unreliable to reduce network spam.
148 this.connection.addHandler(
149 '/' + PIS[pi] + '/camera', 'frc971.vision.TargetMap', (data) => {
150 this.handlePiTargetMap(pi, data);
151 });
152 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800153 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800154 '/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800155 this.handleDrivetrainStatus(data);
156 });
157 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800158 '/localizer', 'frc971.controls.LocalizerOutput', (data) => {
James Kuszmaulfb894572023-02-23 17:25:06 -0800159 this.handleLocalizerOutput(data);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800160 });
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800161 this.connection.addHandler(
162 '/superstructure', 'y2023.control_loops.superstructure.Status',
163 (data) => {this.handleSuperstructureStatus(data)});
164 this.connection.addHandler(
165 '/aos', 'aos.message_bridge.ServerStatistics',
166 (data) => {this.handleServerStatistics(data)});
167 this.connection.addHandler(
168 '/aos', 'aos.message_bridge.ClientStatistics',
169 (data) => {this.handleClientStatistics(data)});
Maxwell Hendersonad312342023-01-10 12:07:47 -0800170 });
171 }
172
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700173 private handleLocalizerDebug(pi: number, data: Uint8Array): void {
James Kuszmaulfb894572023-02-23 17:25:06 -0800174 const now = Date.now() / 1000.0;
175
176 const fbBuffer = new ByteBuffer(data);
177 this.localizerImageMatches.set(
178 now, Visualization.getRootAsVisualization(fbBuffer));
179
180 const debug = this.localizerImageMatches.get(now);
181
182 if (debug.statistics()) {
Milo Lin13906572023-03-15 20:55:22 -0700183 if ((debug.statistics().rejectionReasonsLength() + 1) ==
James Kuszmaulfb894572023-02-23 17:25:06 -0800184 this.rejectionReasonCells.length) {
185 for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
186 ++ii) {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700187 this.rejectionReasonCells[ii][pi].innerHTML =
James Kuszmaulfb894572023-02-23 17:25:06 -0800188 debug.statistics().rejectionReasons(ii).count().toString();
189 }
190 } else {
191 console.error('Unexpected number of rejection reasons in counter.');
192 }
193 }
194 }
195
Milo Lin13906572023-03-15 20:55:22 -0700196 private handlePiTargetMap(pi: string, data: Uint8Array): void {
197 const fbBuffer = new ByteBuffer(data);
198 const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
199 this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
200 .innerHTML = targetMap.rejections().toString();
201 }
202
James Kuszmaulfb894572023-02-23 17:25:06 -0800203 private handleLocalizerOutput(data: Uint8Array): void {
204 const fbBuffer = new ByteBuffer(data);
205 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
206 }
207
Maxwell Hendersonad312342023-01-10 12:07:47 -0800208 private handleDrivetrainStatus(data: Uint8Array): void {
209 const fbBuffer = new ByteBuffer(data);
210 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
211 }
212
Milo Line6571c02023-03-04 21:08:20 -0800213 private handleSuperstructureStatus(data: Uint8Array): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800214 const fbBuffer = new ByteBuffer(data);
215 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
216 }
217
218 private populateNodeConnections(nodeName: string): void {
219 const row = document.createElement('div');
220 this.messageBridgeDiv.appendChild(row);
221 const nodeDiv = document.createElement('div');
222 nodeDiv.innerHTML = nodeName;
223 row.appendChild(nodeDiv);
224 const clientDiv = document.createElement('div');
225 clientDiv.innerHTML = 'N/A';
226 row.appendChild(clientDiv);
227 const serverDiv = document.createElement('div');
228 serverDiv.innerHTML = 'N/A';
229 row.appendChild(serverDiv);
230 this.serverStatuses.set(nodeName, serverDiv);
231 this.clientStatuses.set(nodeName, clientDiv);
232 }
233
234 private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
235 void {
236 if (state === ConnectionState.CONNECTED) {
237 element.innerHTML = ConnectionState[state];
238 element.classList.remove('faulted');
239 element.classList.add('connected');
240 } else {
241 element.innerHTML = ConnectionState[state];
242 element.classList.remove('connected');
243 element.classList.add('faulted');
244 }
245 }
246
247 private handleServerStatistics(data: Uint8Array): void {
248 const fbBuffer = new ByteBuffer(data);
249 const serverStatistics =
250 ServerStatistics.getRootAsServerStatistics(fbBuffer);
251
252 for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
253 const connection = serverStatistics.connections(ii);
254 const nodeName = connection.node().name();
255 if (!this.serverStatuses.has(nodeName)) {
256 this.populateNodeConnections(nodeName);
257 }
258 this.setCurrentNodeState(
259 this.serverStatuses.get(nodeName), connection.state());
260 }
261 }
262
263 private handleClientStatistics(data: Uint8Array): void {
264 const fbBuffer = new ByteBuffer(data);
265 const clientStatistics =
266 ClientStatistics.getRootAsClientStatistics(fbBuffer);
267
268 for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
269 const connection = clientStatistics.connections(ii);
270 const nodeName = connection.node().name();
271 if (!this.clientStatuses.has(nodeName)) {
272 this.populateNodeConnections(nodeName);
273 }
274 this.setCurrentNodeState(
275 this.clientStatuses.get(nodeName), connection.state());
276 }
Milo Line6571c02023-03-04 21:08:20 -0800277 }
278
Maxwell Hendersonad312342023-01-10 12:07:47 -0800279 drawField(): void {
280 const ctx = this.canvas.getContext('2d');
281 ctx.save();
James Kuszmaulfb894572023-02-23 17:25:06 -0800282 ctx.scale(1.0, -1.0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800283 ctx.drawImage(
284 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
285 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
286 ctx.restore();
287 }
288
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800289 drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
290 void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800291 const ctx = this.canvas.getContext('2d');
292 ctx.save();
293 ctx.translate(x, y);
294 ctx.rotate(theta);
295 ctx.strokeStyle = color;
296 ctx.beginPath();
297 ctx.moveTo(0.5, 0.5);
298 ctx.lineTo(0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800299 ctx.lineTo(0.5, -0.5);
300 ctx.stroke();
301 ctx.beginPath();
302 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
303 ctx.stroke();
304 ctx.restore();
305 }
306
307 drawRobot(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800308 x: number, y: number, theta: number, color: string = 'blue',
309 dashed: boolean = false): void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800310 const ctx = this.canvas.getContext('2d');
311 ctx.save();
312 ctx.translate(x, y);
313 ctx.rotate(theta);
314 ctx.strokeStyle = color;
315 ctx.lineWidth = ROBOT_WIDTH / 10.0;
316 if (dashed) {
317 ctx.setLineDash([0.05, 0.05]);
318 } else {
319 // Empty array = solid line.
320 ctx.setLineDash([]);
321 }
322 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
323 ctx.stroke();
324
325 // Draw line indicating which direction is forwards on the robot.
326 ctx.beginPath();
327 ctx.moveTo(0, 0);
James Kuszmaulfb894572023-02-23 17:25:06 -0800328 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800329 ctx.stroke();
330
331 ctx.restore();
332 }
333
334 setZeroing(div: HTMLElement): void {
335 div.innerHTML = 'zeroing';
336 div.classList.remove('faulted');
337 div.classList.add('zeroing');
338 div.classList.remove('near');
339 }
340
Milo Line6571c02023-03-04 21:08:20 -0800341 setEstopped(div: HTMLElement): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800342 div.innerHTML = 'estopped';
343 div.classList.add('faulted');
344 div.classList.remove('zeroing');
345 div.classList.remove('near');
Milo Line6571c02023-03-04 21:08:20 -0800346 }
347
348 setTargetValue(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800349 div: HTMLElement, target: number, val: number, tolerance: number): void {
350 div.innerHTML = val.toFixed(4);
351 div.classList.remove('faulted');
352 div.classList.remove('zeroing');
353 if (Math.abs(target - val) < tolerance) {
354 div.classList.add('near');
355 } else {
356 div.classList.remove('near');
357 }
Milo Line6571c02023-03-04 21:08:20 -0800358 }
359
Maxwell Hendersonad312342023-01-10 12:07:47 -0800360 setValue(div: HTMLElement, val: number): void {
361 div.innerHTML = val.toFixed(4);
362 div.classList.remove('faulted');
363 div.classList.remove('zeroing');
364 div.classList.remove('near');
365 }
366
367 draw(): void {
368 this.reset();
369 this.drawField();
370
371 // Draw the matches with debugging information from the localizer.
372 const now = Date.now() / 1000.0;
James Kuszmaulfb894572023-02-23 17:25:06 -0800373
Milo Line6571c02023-03-04 21:08:20 -0800374 if (this.superstructureStatus) {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800375 this.endEffectorState.innerHTML =
376 EndEffectorState[this.superstructureStatus.endEffectorState()];
377 if (!this.superstructureStatus.wrist() ||
378 !this.superstructureStatus.wrist().zeroed()) {
379 this.setZeroing(this.wrist);
380 } else if (this.superstructureStatus.wrist().estopped()) {
381 this.setEstopped(this.wrist);
382 } else {
383 this.setTargetValue(
384 this.wrist,
385 this.superstructureStatus.wrist().unprofiledGoalPosition(),
386 this.superstructureStatus.wrist().estimatorState().position(),
387 1e-3);
388 }
389 this.armState.innerHTML =
390 ArmState[this.superstructureStatus.arm().state()];
391 this.gamePiece.innerHTML = Class[this.superstructureStatus.gamePiece()];
James Kuszmaulef6b7402023-04-09 14:29:38 -0700392 this.gamePiecePosition.innerHTML =
393 this.superstructureStatus.gamePiecePosition().toFixed(4);
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800394 this.armX.innerHTML = this.superstructureStatus.arm().armX().toFixed(2);
395 this.armY.innerHTML = this.superstructureStatus.arm().armY().toFixed(2);
396 this.circularIndex.innerHTML =
397 this.superstructureStatus.arm().armCircularIndex().toFixed(0);
398 this.roll.innerHTML = this.superstructureStatus.arm()
399 .rollJointEstimatorState()
400 .position()
401 .toFixed(2);
402 this.proximal.innerHTML = this.superstructureStatus.arm()
403 .proximalEstimatorState()
404 .position()
405 .toFixed(2);
406 this.distal.innerHTML = this.superstructureStatus.arm()
407 .distalEstimatorState()
408 .position()
409 .toFixed(2);
410 let zeroingErrors: string = 'Roll Joint Errors:' +
411 '<br/>';
412 for (let i = 0; i < this.superstructureStatus.arm()
413 .rollJointEstimatorState()
414 .errors.length;
415 i++) {
416 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
417 .rollJointEstimatorState()
418 .errors(i)] +
419 '<br/>';
420 }
421 zeroingErrors += '<br/>' +
422 'Proximal Joint Errors:' +
423 '<br/>';
424 for (let i = 0; i < this.superstructureStatus.arm()
425 .proximalEstimatorState()
426 .errors.length;
427 i++) {
428 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
429 .proximalEstimatorState()
430 .errors(i)] +
431 '<br/>';
432 }
433 zeroingErrors += '<br/>' +
434 'Distal Joint Errors:' +
435 '<br/>';
436 for (let i = 0; i <
437 this.superstructureStatus.arm().distalEstimatorState().errors.length;
438 i++) {
439 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
440 .distalEstimatorState()
441 .errors(i)] +
442 '<br/>';
443 }
444 zeroingErrors += '<br/>' +
445 'Wrist Errors:' +
446 '<br/>';
447 for (let i = 0;
448 i < this.superstructureStatus.wrist().estimatorState().errors.length;
449 i++) {
450 zeroingErrors += ZeroingError[this.superstructureStatus.wrist()
451 .estimatorState()
452 .errors(i)] +
453 '<br/>';
454 }
455 this.zeroingFaults.innerHTML = zeroingErrors;
Milo Line6571c02023-03-04 21:08:20 -0800456 }
457
Maxwell Hendersonad312342023-01-10 12:07:47 -0800458 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
459 this.drawRobot(
460 this.drivetrainStatus.trajectoryLogging().x(),
461 this.drivetrainStatus.trajectoryLogging().y(),
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800462 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
Maxwell Hendersonad312342023-01-10 12:07:47 -0800463 false);
464 }
465
James Kuszmaulfb894572023-02-23 17:25:06 -0800466 if (this.localizerOutput) {
467 if (!this.localizerOutput.zeroed()) {
468 this.setZeroing(this.x);
469 this.setZeroing(this.y);
470 this.setZeroing(this.theta);
471 } else {
472 this.setValue(this.x, this.localizerOutput.x());
473 this.setValue(this.y, this.localizerOutput.y());
474 this.setValue(this.theta, this.localizerOutput.theta());
475 }
476
477 this.drawRobot(
478 this.localizerOutput.x(), this.localizerOutput.y(),
479 this.localizerOutput.theta());
480
481 this.imagesAcceptedCounter.innerHTML =
482 this.localizerOutput.imageAcceptedCount().toString();
483 }
484
485 for (const [time, value] of this.localizerImageMatches) {
486 const age = now - time;
487 const kRemovalAge = 1.0;
488 if (age > kRemovalAge) {
489 this.localizerImageMatches.delete(time);
490 continue;
491 }
492 const kMaxImageAlpha = 0.5;
493 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
494 for (let i = 0; i < value.targetsLength(); i++) {
495 const imageDebug = value.targets(i);
496 const x = imageDebug.impliedRobotX();
497 const y = imageDebug.impliedRobotY();
498 const theta = imageDebug.impliedRobotTheta();
499 const cameraX = imageDebug.cameraX();
500 const cameraY = imageDebug.cameraY();
501 const cameraTheta = imageDebug.cameraTheta();
502 const accepted = imageDebug.accepted();
503 // Make camera readings fade over time.
504 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
505 const dashed = false;
James Kuszmaulfb894572023-02-23 17:25:06 -0800506 const cameraRgb = PI_COLORS[imageDebug.camera()];
507 const cameraRgba = cameraRgb + alpha;
James Kuszmaul122a22b2023-02-25 18:14:15 -0800508 this.drawRobot(x, y, theta, cameraRgba, dashed);
James Kuszmaulfb894572023-02-23 17:25:06 -0800509 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
510 }
511 }
512
Maxwell Hendersonad312342023-01-10 12:07:47 -0800513 window.requestAnimationFrame(() => this.draw());
514 }
515
516 reset(): void {
517 const ctx = this.canvas.getContext('2d');
518 ctx.setTransform(1, 0, 0, 1, 0, 0);
519 const size = window.innerHeight * 0.9;
520 ctx.canvas.height = size;
521 const width = size / 2 + 20;
522 ctx.canvas.width = width;
523 ctx.clearRect(0, 0, size, width);
524
525 // Translate to center of display.
526 ctx.translate(width / 2, size / 2);
527 // Coordinate system is:
528 // x -> forward.
529 // y -> to the left.
530 ctx.rotate(-Math.PI / 2);
531 ctx.scale(1, -1);
532
533 const M_TO_PX = (size - 10) / FIELD_LENGTH;
534 ctx.scale(M_TO_PX, M_TO_PX);
535 ctx.lineWidth = 1 / M_TO_PX;
536 }
537}