blob: 2f62c7de5a87fa5f826918a9912c35e825505750 [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);
59 private armX: HTMLElement = (document.getElementById('arm_x') as HTMLElement);
60 private armY: HTMLElement = (document.getElementById('arm_y') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080061 private circularIndex: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080062 (document.getElementById('arm_circular_index') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080063 private roll: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080064 (document.getElementById('arm_roll') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080065 private proximal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080066 (document.getElementById('arm_proximal') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080067 private distal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080068 (document.getElementById('arm_distal') as HTMLElement);
Milo Lin72fb9012023-03-10 19:53:19 -080069 private zeroingFaults: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080070 (document.getElementById('zeroing_faults') as HTMLElement);
Maxwell Hendersonad312342023-01-10 12:07:47 -080071 constructor(private readonly connection: Connection) {
72 (document.getElementById('field') as HTMLElement).appendChild(this.canvas);
73
James Kuszmaulf7b5d622023-03-11 15:14:53 -080074 this.fieldImage.src = '2023.png';
James Kuszmaulfb894572023-02-23 17:25:06 -080075
James Kuszmaul4f0e4362023-03-12 20:03:59 -070076 // Construct a table header.
77 {
78 const row = document.createElement('div');
79 const nameCell = document.createElement('div');
Milo Lin13906572023-03-15 20:55:22 -070080 nameCell.innerHTML = 'Rejection Reason';
James Kuszmaul4f0e4362023-03-12 20:03:59 -070081 row.appendChild(nameCell);
82 for (const pi of PIS) {
83 const nodeCell = document.createElement('div');
84 nodeCell.innerHTML = pi;
85 row.appendChild(nodeCell);
86 }
87 document.getElementById('vision_readouts').appendChild(row);
88 }
James Kuszmaulfb894572023-02-23 17:25:06 -080089 for (const value in RejectionReason) {
90 // Typescript generates an iterator that produces both numbers and
91 // strings... don't do anything on the string iterations.
92 if (isNaN(Number(value))) {
93 continue;
94 }
95 const row = document.createElement('div');
96 const nameCell = document.createElement('div');
97 nameCell.innerHTML = RejectionReason[value];
98 row.appendChild(nameCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -070099 this.rejectionReasonCells.push([]);
100 for (const pi of PIS) {
101 const valueCell = document.createElement('div');
102 valueCell.innerHTML = 'NA';
Milo Lin13906572023-03-15 20:55:22 -0700103 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
104 valueCell);
105 row.appendChild(valueCell);
106 }
107 document.getElementById('vision_readouts').appendChild(row);
108 }
109
110 // Add rejection reason row for aprilrobotics rejections.
111 {
112 const row = document.createElement('div');
113 const nameCell = document.createElement('div');
114 nameCell.innerHTML = 'Rejected by aprilrobotics';
115 row.appendChild(nameCell);
116 this.rejectionReasonCells.push([]);
117 for (const pi of PIS) {
118 const valueCell = document.createElement('div');
119 valueCell.innerHTML = 'NA';
120 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
121 valueCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700122 row.appendChild(valueCell);
123 }
James Kuszmaulfb894572023-02-23 17:25:06 -0800124 document.getElementById('vision_readouts').appendChild(row);
125 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800126
127 for (let ii = 0; ii < PI_COLORS.length; ++ii) {
128 const legendEntry = document.createElement('div');
129 legendEntry.style.color = PI_COLORS[ii];
130 legendEntry.innerHTML = 'PI' + (ii + 1).toString()
131 document.getElementById('legend').appendChild(legendEntry);
132 }
133
134 this.connection.addConfigHandler(() => {
135 // Visualization message is reliable so that we can see *all* the vision
136 // matches.
James Kuszmaulfb894572023-02-23 17:25:06 -0800137 for (const pi in PIS) {
138 this.connection.addReliableHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800139 '/' + PIS[pi] + '/camera', 'y2023.localizer.Visualization',
James Kuszmaulfb894572023-02-23 17:25:06 -0800140 (data) => {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700141 this.handleLocalizerDebug(Number(pi), data);
James Kuszmaulfb894572023-02-23 17:25:06 -0800142 });
143 }
Milo Lin13906572023-03-15 20:55:22 -0700144 for (const pi in PIS) {
145 // Make unreliable to reduce network spam.
146 this.connection.addHandler(
147 '/' + PIS[pi] + '/camera', 'frc971.vision.TargetMap', (data) => {
148 this.handlePiTargetMap(pi, data);
149 });
150 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800151 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800152 '/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800153 this.handleDrivetrainStatus(data);
154 });
155 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800156 '/localizer', 'frc971.controls.LocalizerOutput', (data) => {
James Kuszmaulfb894572023-02-23 17:25:06 -0800157 this.handleLocalizerOutput(data);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800158 });
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800159 this.connection.addHandler(
160 '/superstructure', 'y2023.control_loops.superstructure.Status',
161 (data) => {this.handleSuperstructureStatus(data)});
162 this.connection.addHandler(
163 '/aos', 'aos.message_bridge.ServerStatistics',
164 (data) => {this.handleServerStatistics(data)});
165 this.connection.addHandler(
166 '/aos', 'aos.message_bridge.ClientStatistics',
167 (data) => {this.handleClientStatistics(data)});
Maxwell Hendersonad312342023-01-10 12:07:47 -0800168 });
169 }
170
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700171 private handleLocalizerDebug(pi: number, data: Uint8Array): void {
James Kuszmaulfb894572023-02-23 17:25:06 -0800172 const now = Date.now() / 1000.0;
173
174 const fbBuffer = new ByteBuffer(data);
175 this.localizerImageMatches.set(
176 now, Visualization.getRootAsVisualization(fbBuffer));
177
178 const debug = this.localizerImageMatches.get(now);
179
180 if (debug.statistics()) {
Milo Lin13906572023-03-15 20:55:22 -0700181 if ((debug.statistics().rejectionReasonsLength() + 1) ==
James Kuszmaulfb894572023-02-23 17:25:06 -0800182 this.rejectionReasonCells.length) {
183 for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
184 ++ii) {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700185 this.rejectionReasonCells[ii][pi].innerHTML =
James Kuszmaulfb894572023-02-23 17:25:06 -0800186 debug.statistics().rejectionReasons(ii).count().toString();
187 }
188 } else {
189 console.error('Unexpected number of rejection reasons in counter.');
190 }
191 }
192 }
193
Milo Lin13906572023-03-15 20:55:22 -0700194 private handlePiTargetMap(pi: string, data: Uint8Array): void {
195 const fbBuffer = new ByteBuffer(data);
196 const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
197 this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
198 .innerHTML = targetMap.rejections().toString();
199 }
200
James Kuszmaulfb894572023-02-23 17:25:06 -0800201 private handleLocalizerOutput(data: Uint8Array): void {
202 const fbBuffer = new ByteBuffer(data);
203 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
204 }
205
Maxwell Hendersonad312342023-01-10 12:07:47 -0800206 private handleDrivetrainStatus(data: Uint8Array): void {
207 const fbBuffer = new ByteBuffer(data);
208 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
209 }
210
Milo Line6571c02023-03-04 21:08:20 -0800211 private handleSuperstructureStatus(data: Uint8Array): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800212 const fbBuffer = new ByteBuffer(data);
213 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
214 }
215
216 private populateNodeConnections(nodeName: string): void {
217 const row = document.createElement('div');
218 this.messageBridgeDiv.appendChild(row);
219 const nodeDiv = document.createElement('div');
220 nodeDiv.innerHTML = nodeName;
221 row.appendChild(nodeDiv);
222 const clientDiv = document.createElement('div');
223 clientDiv.innerHTML = 'N/A';
224 row.appendChild(clientDiv);
225 const serverDiv = document.createElement('div');
226 serverDiv.innerHTML = 'N/A';
227 row.appendChild(serverDiv);
228 this.serverStatuses.set(nodeName, serverDiv);
229 this.clientStatuses.set(nodeName, clientDiv);
230 }
231
232 private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
233 void {
234 if (state === ConnectionState.CONNECTED) {
235 element.innerHTML = ConnectionState[state];
236 element.classList.remove('faulted');
237 element.classList.add('connected');
238 } else {
239 element.innerHTML = ConnectionState[state];
240 element.classList.remove('connected');
241 element.classList.add('faulted');
242 }
243 }
244
245 private handleServerStatistics(data: Uint8Array): void {
246 const fbBuffer = new ByteBuffer(data);
247 const serverStatistics =
248 ServerStatistics.getRootAsServerStatistics(fbBuffer);
249
250 for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
251 const connection = serverStatistics.connections(ii);
252 const nodeName = connection.node().name();
253 if (!this.serverStatuses.has(nodeName)) {
254 this.populateNodeConnections(nodeName);
255 }
256 this.setCurrentNodeState(
257 this.serverStatuses.get(nodeName), connection.state());
258 }
259 }
260
261 private handleClientStatistics(data: Uint8Array): void {
262 const fbBuffer = new ByteBuffer(data);
263 const clientStatistics =
264 ClientStatistics.getRootAsClientStatistics(fbBuffer);
265
266 for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
267 const connection = clientStatistics.connections(ii);
268 const nodeName = connection.node().name();
269 if (!this.clientStatuses.has(nodeName)) {
270 this.populateNodeConnections(nodeName);
271 }
272 this.setCurrentNodeState(
273 this.clientStatuses.get(nodeName), connection.state());
274 }
Milo Line6571c02023-03-04 21:08:20 -0800275 }
276
Maxwell Hendersonad312342023-01-10 12:07:47 -0800277 drawField(): void {
278 const ctx = this.canvas.getContext('2d');
279 ctx.save();
James Kuszmaulfb894572023-02-23 17:25:06 -0800280 ctx.scale(1.0, -1.0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800281 ctx.drawImage(
282 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
283 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
284 ctx.restore();
285 }
286
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800287 drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
288 void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800289 const ctx = this.canvas.getContext('2d');
290 ctx.save();
291 ctx.translate(x, y);
292 ctx.rotate(theta);
293 ctx.strokeStyle = color;
294 ctx.beginPath();
295 ctx.moveTo(0.5, 0.5);
296 ctx.lineTo(0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800297 ctx.lineTo(0.5, -0.5);
298 ctx.stroke();
299 ctx.beginPath();
300 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
301 ctx.stroke();
302 ctx.restore();
303 }
304
305 drawRobot(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800306 x: number, y: number, theta: number, color: string = 'blue',
307 dashed: boolean = false): void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800308 const ctx = this.canvas.getContext('2d');
309 ctx.save();
310 ctx.translate(x, y);
311 ctx.rotate(theta);
312 ctx.strokeStyle = color;
313 ctx.lineWidth = ROBOT_WIDTH / 10.0;
314 if (dashed) {
315 ctx.setLineDash([0.05, 0.05]);
316 } else {
317 // Empty array = solid line.
318 ctx.setLineDash([]);
319 }
320 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
321 ctx.stroke();
322
323 // Draw line indicating which direction is forwards on the robot.
324 ctx.beginPath();
325 ctx.moveTo(0, 0);
James Kuszmaulfb894572023-02-23 17:25:06 -0800326 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800327 ctx.stroke();
328
329 ctx.restore();
330 }
331
332 setZeroing(div: HTMLElement): void {
333 div.innerHTML = 'zeroing';
334 div.classList.remove('faulted');
335 div.classList.add('zeroing');
336 div.classList.remove('near');
337 }
338
Milo Line6571c02023-03-04 21:08:20 -0800339 setEstopped(div: HTMLElement): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800340 div.innerHTML = 'estopped';
341 div.classList.add('faulted');
342 div.classList.remove('zeroing');
343 div.classList.remove('near');
Milo Line6571c02023-03-04 21:08:20 -0800344 }
345
346 setTargetValue(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800347 div: HTMLElement, target: number, val: number, tolerance: number): void {
348 div.innerHTML = val.toFixed(4);
349 div.classList.remove('faulted');
350 div.classList.remove('zeroing');
351 if (Math.abs(target - val) < tolerance) {
352 div.classList.add('near');
353 } else {
354 div.classList.remove('near');
355 }
Milo Line6571c02023-03-04 21:08:20 -0800356 }
357
Maxwell Hendersonad312342023-01-10 12:07:47 -0800358 setValue(div: HTMLElement, val: number): void {
359 div.innerHTML = val.toFixed(4);
360 div.classList.remove('faulted');
361 div.classList.remove('zeroing');
362 div.classList.remove('near');
363 }
364
365 draw(): void {
366 this.reset();
367 this.drawField();
368
369 // Draw the matches with debugging information from the localizer.
370 const now = Date.now() / 1000.0;
James Kuszmaulfb894572023-02-23 17:25:06 -0800371
Milo Line6571c02023-03-04 21:08:20 -0800372 if (this.superstructureStatus) {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800373 this.endEffectorState.innerHTML =
374 EndEffectorState[this.superstructureStatus.endEffectorState()];
375 if (!this.superstructureStatus.wrist() ||
376 !this.superstructureStatus.wrist().zeroed()) {
377 this.setZeroing(this.wrist);
378 } else if (this.superstructureStatus.wrist().estopped()) {
379 this.setEstopped(this.wrist);
380 } else {
381 this.setTargetValue(
382 this.wrist,
383 this.superstructureStatus.wrist().unprofiledGoalPosition(),
384 this.superstructureStatus.wrist().estimatorState().position(),
385 1e-3);
386 }
387 this.armState.innerHTML =
388 ArmState[this.superstructureStatus.arm().state()];
389 this.gamePiece.innerHTML = Class[this.superstructureStatus.gamePiece()];
390 this.armX.innerHTML = this.superstructureStatus.arm().armX().toFixed(2);
391 this.armY.innerHTML = this.superstructureStatus.arm().armY().toFixed(2);
392 this.circularIndex.innerHTML =
393 this.superstructureStatus.arm().armCircularIndex().toFixed(0);
394 this.roll.innerHTML = this.superstructureStatus.arm()
395 .rollJointEstimatorState()
396 .position()
397 .toFixed(2);
398 this.proximal.innerHTML = this.superstructureStatus.arm()
399 .proximalEstimatorState()
400 .position()
401 .toFixed(2);
402 this.distal.innerHTML = this.superstructureStatus.arm()
403 .distalEstimatorState()
404 .position()
405 .toFixed(2);
406 let zeroingErrors: string = 'Roll Joint Errors:' +
407 '<br/>';
408 for (let i = 0; i < this.superstructureStatus.arm()
409 .rollJointEstimatorState()
410 .errors.length;
411 i++) {
412 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
413 .rollJointEstimatorState()
414 .errors(i)] +
415 '<br/>';
416 }
417 zeroingErrors += '<br/>' +
418 'Proximal Joint Errors:' +
419 '<br/>';
420 for (let i = 0; i < this.superstructureStatus.arm()
421 .proximalEstimatorState()
422 .errors.length;
423 i++) {
424 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
425 .proximalEstimatorState()
426 .errors(i)] +
427 '<br/>';
428 }
429 zeroingErrors += '<br/>' +
430 'Distal Joint Errors:' +
431 '<br/>';
432 for (let i = 0; i <
433 this.superstructureStatus.arm().distalEstimatorState().errors.length;
434 i++) {
435 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
436 .distalEstimatorState()
437 .errors(i)] +
438 '<br/>';
439 }
440 zeroingErrors += '<br/>' +
441 'Wrist Errors:' +
442 '<br/>';
443 for (let i = 0;
444 i < this.superstructureStatus.wrist().estimatorState().errors.length;
445 i++) {
446 zeroingErrors += ZeroingError[this.superstructureStatus.wrist()
447 .estimatorState()
448 .errors(i)] +
449 '<br/>';
450 }
451 this.zeroingFaults.innerHTML = zeroingErrors;
Milo Line6571c02023-03-04 21:08:20 -0800452 }
453
Maxwell Hendersonad312342023-01-10 12:07:47 -0800454 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
455 this.drawRobot(
456 this.drivetrainStatus.trajectoryLogging().x(),
457 this.drivetrainStatus.trajectoryLogging().y(),
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800458 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
Maxwell Hendersonad312342023-01-10 12:07:47 -0800459 false);
460 }
461
James Kuszmaulfb894572023-02-23 17:25:06 -0800462 if (this.localizerOutput) {
463 if (!this.localizerOutput.zeroed()) {
464 this.setZeroing(this.x);
465 this.setZeroing(this.y);
466 this.setZeroing(this.theta);
467 } else {
468 this.setValue(this.x, this.localizerOutput.x());
469 this.setValue(this.y, this.localizerOutput.y());
470 this.setValue(this.theta, this.localizerOutput.theta());
471 }
472
473 this.drawRobot(
474 this.localizerOutput.x(), this.localizerOutput.y(),
475 this.localizerOutput.theta());
476
477 this.imagesAcceptedCounter.innerHTML =
478 this.localizerOutput.imageAcceptedCount().toString();
479 }
480
481 for (const [time, value] of this.localizerImageMatches) {
482 const age = now - time;
483 const kRemovalAge = 1.0;
484 if (age > kRemovalAge) {
485 this.localizerImageMatches.delete(time);
486 continue;
487 }
488 const kMaxImageAlpha = 0.5;
489 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
490 for (let i = 0; i < value.targetsLength(); i++) {
491 const imageDebug = value.targets(i);
492 const x = imageDebug.impliedRobotX();
493 const y = imageDebug.impliedRobotY();
494 const theta = imageDebug.impliedRobotTheta();
495 const cameraX = imageDebug.cameraX();
496 const cameraY = imageDebug.cameraY();
497 const cameraTheta = imageDebug.cameraTheta();
498 const accepted = imageDebug.accepted();
499 // Make camera readings fade over time.
500 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
501 const dashed = false;
James Kuszmaulfb894572023-02-23 17:25:06 -0800502 const cameraRgb = PI_COLORS[imageDebug.camera()];
503 const cameraRgba = cameraRgb + alpha;
James Kuszmaul122a22b2023-02-25 18:14:15 -0800504 this.drawRobot(x, y, theta, cameraRgba, dashed);
James Kuszmaulfb894572023-02-23 17:25:06 -0800505 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
506 }
507 }
508
Maxwell Hendersonad312342023-01-10 12:07:47 -0800509 window.requestAnimationFrame(() => this.draw());
510 }
511
512 reset(): void {
513 const ctx = this.canvas.getContext('2d');
514 ctx.setTransform(1, 0, 0, 1, 0, 0);
515 const size = window.innerHeight * 0.9;
516 ctx.canvas.height = size;
517 const width = size / 2 + 20;
518 ctx.canvas.width = width;
519 ctx.clearRect(0, 0, size, width);
520
521 // Translate to center of display.
522 ctx.translate(width / 2, size / 2);
523 // Coordinate system is:
524 // x -> forward.
525 // y -> to the left.
526 ctx.rotate(-Math.PI / 2);
527 ctx.scale(1, -1);
528
529 const M_TO_PX = (size - 10) / FIELD_LENGTH;
530 ctx.scale(M_TO_PX, M_TO_PX);
531 ctx.lineWidth = 1 / M_TO_PX;
532 }
533}