blob: 7b5e0f4d92fc69293bb8e4f598e978876ce5da84 [file] [log] [blame]
Niko Sohmers3860f8a2024-01-12 21:05:19 -08001import {ByteBuffer} from 'flatbuffers'
2import {ClientStatistics} from '../../aos/network/message_bridge_client_generated'
3import {ServerStatistics, State as ConnectionState} from '../../aos/network/message_bridge_server_generated'
4import {Connection} from '../../aos/network/www/proxy'
5import {ZeroingError} from '../../frc971/control_loops/control_loops_generated'
6import {Position as DrivetrainPosition} from '../../frc971/control_loops/drivetrain/drivetrain_position_generated'
7import {CANPosition as DrivetrainCANPosition} from '../../frc971/control_loops/drivetrain/drivetrain_can_position_generated'
8import {Status as DrivetrainStatus} from '../../frc971/control_loops/drivetrain/drivetrain_status_generated'
Niko Sohmersed83b6b2024-03-02 20:05:19 -08009import {SuperstructureState, IntakeRollerStatus, CatapultState, TransferRollerStatus, ExtendRollerStatus, ExtendStatus, NoteStatus, Status as SuperstructureStatus} from '../control_loops/superstructure/superstructure_status_generated'
Niko Sohmers3860f8a2024-01-12 21:05:19 -080010import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated'
11import {TargetMap} from '../../frc971/vision/target_map_generated'
Niko Sohmersf1b9eb92024-03-03 15:30:07 -080012import {RejectionReason} from '../localizer/status_generated'
13import {TargetEstimateDebug, Visualization} from '../localizer/visualization_generated'
Niko Sohmers3860f8a2024-01-12 21:05:19 -080014
15
16import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
17
18// (0,0) is field center, +X is toward red DS
19const FIELD_SIDE_Y = FIELD_WIDTH / 2;
20const FIELD_EDGE_X = FIELD_LENGTH / 2;
21
Niko Sohmers2d108762024-02-02 20:21:14 -080022const ROBOT_WIDTH = 29 * IN_TO_M;
Niko Sohmers3860f8a2024-01-12 21:05:19 -080023const ROBOT_LENGTH = 32 * IN_TO_M;
24
Niko Sohmersf1b9eb92024-03-03 15:30:07 -080025const CAMERA_COLORS = ['#ff00ff', '#ffff00', '#00ffff', '#ffa500'];
26const CAMERAS = ['/orin1/camera0', '/orin1/camera1', '/imu/camera0', '/imu/camera1'];
27
Niko Sohmers3860f8a2024-01-12 21:05:19 -080028export class FieldHandler {
Niko Sohmers2d108762024-02-02 20:21:14 -080029 private canvas = document.createElement('canvas');
Mirabel Wang66546642024-02-10 16:37:05 -080030 private localizerOutput: LocalizerOutput|null = null;
31 private drivetrainStatus: DrivetrainStatus|null = null;
32 private drivetrainPosition: DrivetrainPosition|null = null;
33 private drivetrainCANPosition: DrivetrainCANPosition|null = null;
Filip Kujawa1a2e9e02024-02-24 18:30:29 -080034 private superstructureStatus: SuperstructureStatus|null = null;
Mirabel Wang66546642024-02-10 16:37:05 -080035
Niko Sohmersf1b9eb92024-03-03 15:30:07 -080036 // Image information indexed by timestamp (seconds since the epoch), so that
37 // we can stop displaying images after a certain amount of time.
38 private localizerImageMatches = new Map<number, Visualization>();
Mirabel Wang66546642024-02-10 16:37:05 -080039 private x: HTMLElement = (document.getElementById('x') as HTMLElement);
40 private y: HTMLElement = (document.getElementById('y') as HTMLElement);
41 private theta: HTMLElement =
42 (document.getElementById('theta') as HTMLElement);
43
Niko Sohmersf1b9eb92024-03-03 15:30:07 -080044 private imagesAcceptedCounter: HTMLElement =
45 (document.getElementById('images_accepted') as HTMLElement);
46 // HTML elements for rejection reasons for individual cameras. Indices
47 // corresponding to RejectionReason enum values will be for those reasons. The
48 // final row will account for images rejected by the aprilrobotics detector
49 // instead of the localizer.
50 private rejectionReasonCells: HTMLElement[][] = [];
51 private messageBridgeDiv: HTMLElement =
52 (document.getElementById('message_bridge_status') as HTMLElement);
53 private clientStatuses = new Map<string, HTMLElement>();
54 private serverStatuses = new Map<string, HTMLElement>();
55
Niko Sohmers2d108762024-02-02 20:21:14 -080056 private fieldImage: HTMLImageElement = new Image();
Mirabel Wang66546642024-02-10 16:37:05 -080057
Filip Kujawa1a2e9e02024-02-24 18:30:29 -080058 private zeroingFaults: HTMLElement =
59 (document.getElementById('zeroing_faults') as HTMLElement);
60
61 private superstructureState: HTMLElement =
62 (document.getElementById('superstructure_state') as HTMLElement);
63
64 private intakeRollerState: HTMLElement =
65 (document.getElementById('intake_roller_state') as HTMLElement);
66 private transferRollerState: HTMLElement =
67 (document.getElementById('transfer_roller_state') as HTMLElement);
68 private extendState: HTMLElement =
69 (document.getElementById('extend_state') as HTMLElement);
70 private extendRollerState: HTMLElement =
71 (document.getElementById('extend_roller_state') as HTMLElement);
72 private catapultState: HTMLElement =
73 (document.getElementById('catapult_state') as HTMLElement);
Niko Sohmersed83b6b2024-03-02 20:05:19 -080074 private uncompletedNoteGoal: HTMLElement =
75 (document.getElementById('uncompleted_note_goal') as HTMLElement);
76
77 private extend_beambreak: HTMLElement =
78 (document.getElementById('extend_beambreak') as HTMLElement);
79 private catapult_beambreak: HTMLElement =
80 (document.getElementById('catapult_beambreak') as HTMLElement);
Niko Sohmers2ac60c52024-03-16 18:44:54 -070081 private transfer_beambreak: HTMLElement =
82 (document.getElementById('transfer_beambreak') as HTMLElement);
Niko Sohmersed83b6b2024-03-02 20:05:19 -080083
84 private extend_at_retracted: HTMLElement =
85 (document.getElementById('extend_at_retracted') as HTMLElement);
86 private extend_ready_for_transfer: HTMLElement =
87 (document.getElementById('extend_ready_for_transfer') as HTMLElement);
88 private extend_ready_for_catapult_transfer: HTMLElement =
89 (document.getElementById('extend_ready_for_catapult_transfer') as HTMLElement);
90 private turret_ready_for_load: HTMLElement =
91 (document.getElementById('turret_ready_for_load') as HTMLElement);
92 private altitude_ready_for_load: HTMLElement =
93 (document.getElementById('altitude_ready_for_load') as HTMLElement);
94
Niko Sohmerscc3aa452024-03-03 17:20:04 -080095 private turret_in_range: HTMLElement =
96 (document.getElementById('turret_in_range') as HTMLElement);
97 private altitude_in_range: HTMLElement =
98 (document.getElementById('altitude_in_range') as HTMLElement);
99 private altitude_above_min_angle: HTMLElement =
100 (document.getElementById('altitude_above_min_angle') as HTMLElement);
101
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800102
103 private intakePivot: HTMLElement =
104 (document.getElementById('intake_pivot') as HTMLElement);
105 private intakePivotAbs: HTMLElement =
106 (document.getElementById('intake_pivot_abs') as HTMLElement);
107
108 private climber: HTMLElement =
109 (document.getElementById('climber') as HTMLElement);
110 private climberAbs: HTMLElement =
111 (document.getElementById('climber_abs') as HTMLElement);
112 private climberPot: HTMLElement =
113 (document.getElementById('climber_pot') as HTMLElement);
114
115 private extend: HTMLElement =
116 (document.getElementById('extend') as HTMLElement);
117 private extendAbs: HTMLElement =
118 (document.getElementById('extend_abs') as HTMLElement);
119 private extendPot: HTMLElement =
120 (document.getElementById('extend_pot') as HTMLElement);
121
122 private turret: HTMLElement =
123 (document.getElementById('turret') as HTMLElement);
124 private turretAbs: HTMLElement =
125 (document.getElementById('turret_abs') as HTMLElement);
126 private turretPot: HTMLElement =
127 (document.getElementById('turret_pot') as HTMLElement);
128
129 private catapult: HTMLElement =
130 (document.getElementById('catapult') as HTMLElement);
131 private catapultAbs: HTMLElement =
James Kuszmaul51a677e2024-03-01 19:59:00 -0800132 (document.getElementById('catapult_abs') as HTMLElement);
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800133 private catapultPot: HTMLElement =
134 (document.getElementById('catapult_pot') as HTMLElement);
135
136 private altitude: HTMLElement =
137 (document.getElementById('altitude') as HTMLElement);
138 private altitudeAbs: HTMLElement =
139 (document.getElementById('altitude_abs') as HTMLElement);
140 private altitudePot: HTMLElement =
141 (document.getElementById('altitude_pot') as HTMLElement);
142
143 private turret_position: HTMLElement =
144 (document.getElementById('turret_position') as HTMLElement);
145 private turret_velocity: HTMLElement =
146 (document.getElementById('turret_velocity') as HTMLElement);
147 private target_distance: HTMLElement =
148 (document.getElementById('target_distance') as HTMLElement);
149 private shot_distance: HTMLElement =
150 (document.getElementById('shot_distance') as HTMLElement);
151
Mirabel Wang66546642024-02-10 16:37:05 -0800152 private leftDrivetrainEncoder: HTMLElement =
153 (document.getElementById('left_drivetrain_encoder') as HTMLElement);
154 private rightDrivetrainEncoder: HTMLElement =
155 (document.getElementById('right_drivetrain_encoder') as HTMLElement);
156 private falconRightFrontPosition: HTMLElement =
157 (document.getElementById('falcon_right_front') as HTMLElement);
158 private falconRightBackPosition: HTMLElement =
159 (document.getElementById('falcon_right_back') as HTMLElement);
160 private falconLeftFrontPosition: HTMLElement =
161 (document.getElementById('falcon_left_front') as HTMLElement);
162 private falconLeftBackPosition: HTMLElement =
163 (document.getElementById('falcon_left_back') as HTMLElement);
164
Niko Sohmers3860f8a2024-01-12 21:05:19 -0800165 constructor(private readonly connection: Connection) {
Niko Sohmers2d108762024-02-02 20:21:14 -0800166 (document.getElementById('field') as HTMLElement).appendChild(this.canvas);
167
168 this.fieldImage.src = '2024.png';
Mirabel Wang66546642024-02-10 16:37:05 -0800169
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800170 // Construct a table header.
171 {
172 const row = document.createElement('div');
173 const nameCell = document.createElement('div');
174 nameCell.innerHTML = 'Rejection Reason';
175 row.appendChild(nameCell);
176 for (const camera of CAMERAS) {
177 const nodeCell = document.createElement('div');
178 nodeCell.innerHTML = camera;
179 row.appendChild(nodeCell);
180 }
181 document.getElementById('vision_readouts').appendChild(row);
182 }
183
184 for (const value in RejectionReason) {
185 // Typescript generates an iterator that produces both numbers and
186 // strings... don't do anything on the string iterations.
187 if (isNaN(Number(value))) {
188 continue;
189 }
190 const row = document.createElement('div');
191 const nameCell = document.createElement('div');
192 nameCell.innerHTML = RejectionReason[value];
193 row.appendChild(nameCell);
194 this.rejectionReasonCells.push([]);
195 for (const camera of CAMERAS) {
196 const valueCell = document.createElement('div');
197 valueCell.innerHTML = 'NA';
198 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
199 valueCell);
200 row.appendChild(valueCell);
201 }
202 document.getElementById('vision_readouts').appendChild(row);
203 }
204
205 // Add rejection reason row for aprilrobotics rejections.
206 {
207 const row = document.createElement('div');
208 const nameCell = document.createElement('div');
209 nameCell.innerHTML = 'Rejected by aprilrobotics';
210 row.appendChild(nameCell);
211 this.rejectionReasonCells.push([]);
212 for (const camera of CAMERAS) {
213 const valueCell = document.createElement('div');
214 valueCell.innerHTML = 'NA';
215 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
216 valueCell);
217 row.appendChild(valueCell);
218 }
219 document.getElementById('vision_readouts').appendChild(row);
220 }
221
222 for (let ii = 0; ii < CAMERA_COLORS.length; ++ii) {
223 const legendEntry = document.createElement('div');
224 legendEntry.style.color = CAMERA_COLORS[ii];
225 legendEntry.innerHTML = CAMERAS[ii];
226 document.getElementById('legend').appendChild(legendEntry);
227 }
228
Mirabel Wang66546642024-02-10 16:37:05 -0800229 this.connection.addConfigHandler(() => {
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800230 // Visualization message is reliable so that we can see *all* the vision
231 // matches.
232 for (const camera in CAMERAS) {
233 this.connection.addHandler(
234 CAMERAS[camera], 'y2024.localizer.Visualization',
235 (data) => {
236 this.handleLocalizerDebug(Number(camera), data);
237 });
238 }
239 for (const camera in CAMERAS) {
240 // Make unreliable to reduce network spam.
241 this.connection.addHandler(
242 CAMERAS[camera], 'frc971.vision.TargetMap', (data) => {
243 this.handleCameraTargetMap(camera, data);
244 });
245 }
Mirabel Wang66546642024-02-10 16:37:05 -0800246
247 this.connection.addHandler(
248 '/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
249 this.handleDrivetrainStatus(data);
250 });
251 this.connection.addHandler(
252 '/drivetrain', 'frc971.control_loops.drivetrain.Position', (data) => {
253 this.handleDrivetrainPosition(data);
254 });
255 this.connection.addHandler(
256 '/drivetrain', 'frc971.control_loops.drivetrain.CANPosition', (data) => {
257 this.handleDrivetrainCANPosition(data);
258 });
259 this.connection.addHandler(
260 '/localizer', 'frc971.controls.LocalizerOutput', (data) => {
261 this.handleLocalizerOutput(data);
262 });
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800263 this.connection.addHandler(
264 '/superstructure', "y2024.control_loops.superstructure.Status",
265 (data) => {
266 this.handleSuperstructureStatus(data)
267 });
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800268 this.connection.addHandler(
269 '/aos', 'aos.message_bridge.ServerStatistics',
270 (data) => {this.handleServerStatistics(data)});
271 this.connection.addHandler(
272 '/aos', 'aos.message_bridge.ClientStatistics',
273 (data) => {this.handleClientStatistics(data)});
Mirabel Wang66546642024-02-10 16:37:05 -0800274 });
275 }
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800276 private handleLocalizerDebug(camera: number, data: Uint8Array): void {
277 const now = Date.now() / 1000.0;
278
279 const fbBuffer = new ByteBuffer(data);
280 this.localizerImageMatches.set(
281 now, Visualization.getRootAsVisualization(fbBuffer));
282
283 const debug = this.localizerImageMatches.get(now);
284
285 if (debug.statistics()) {
286 if ((debug.statistics().rejectionReasonsLength() + 1) ==
287 this.rejectionReasonCells.length) {
288 for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
289 ++ii) {
290 this.rejectionReasonCells[ii][camera].innerHTML =
291 debug.statistics().rejectionReasons(ii).count().toString();
292 }
293 } else {
294 console.error('Unexpected number of rejection reasons in counter.');
295 }
296 }
297 }
298
299 private handleCameraTargetMap(pi: string, data: Uint8Array): void {
300 const fbBuffer = new ByteBuffer(data);
301 const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
302 this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
303 .innerHTML = targetMap.rejections().toString();
304 }
Mirabel Wang66546642024-02-10 16:37:05 -0800305
306 private handleDrivetrainStatus(data: Uint8Array): void {
307 const fbBuffer = new ByteBuffer(data);
308 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
309 }
310
311 private handleDrivetrainPosition(data: Uint8Array): void {
312 const fbBuffer = new ByteBuffer(data);
313 this.drivetrainPosition = DrivetrainPosition.getRootAsPosition(fbBuffer);
314 }
315
316 private handleDrivetrainCANPosition(data: Uint8Array): void {
317 const fbBuffer = new ByteBuffer(data);
318 this.drivetrainCANPosition = DrivetrainCANPosition.getRootAsCANPosition(fbBuffer);
319 }
320
321 private handleLocalizerOutput(data: Uint8Array): void {
322 const fbBuffer = new ByteBuffer(data);
323 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
Niko Sohmers2d108762024-02-02 20:21:14 -0800324 }
325
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800326 private handleSuperstructureStatus(data: Uint8Array): void {
327 const fbBuffer = new ByteBuffer(data);
328 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
329 }
330
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800331 private populateNodeConnections(nodeName: string): void {
332 const row = document.createElement('div');
333 this.messageBridgeDiv.appendChild(row);
334 const nodeDiv = document.createElement('div');
335 nodeDiv.innerHTML = nodeName;
336 row.appendChild(nodeDiv);
337 const clientDiv = document.createElement('div');
338 clientDiv.innerHTML = 'N/A';
339 row.appendChild(clientDiv);
340 const serverDiv = document.createElement('div');
341 serverDiv.innerHTML = 'N/A';
342 row.appendChild(serverDiv);
343 this.serverStatuses.set(nodeName, serverDiv);
344 this.clientStatuses.set(nodeName, clientDiv);
345 }
346
347 private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
348 void {
349 if (state === ConnectionState.CONNECTED) {
350 element.innerHTML = ConnectionState[state];
351 element.classList.remove('faulted');
352 element.classList.add('connected');
353 } else {
354 element.innerHTML = ConnectionState[state];
355 element.classList.remove('connected');
356 element.classList.add('faulted');
357 }
358 }
359
360 private handleServerStatistics(data: Uint8Array): void {
361 const fbBuffer = new ByteBuffer(data);
362 const serverStatistics =
363 ServerStatistics.getRootAsServerStatistics(fbBuffer);
364
365 for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
366 const connection = serverStatistics.connections(ii);
367 const nodeName = connection.node().name();
368 if (!this.serverStatuses.has(nodeName)) {
369 this.populateNodeConnections(nodeName);
370 }
371 this.setCurrentNodeState(
372 this.serverStatuses.get(nodeName), connection.state());
373 }
374 }
375
376 private handleClientStatistics(data: Uint8Array): void {
377 const fbBuffer = new ByteBuffer(data);
378 const clientStatistics =
379 ClientStatistics.getRootAsClientStatistics(fbBuffer);
380
381 for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
382 const connection = clientStatistics.connections(ii);
383 const nodeName = connection.node().name();
384 if (!this.clientStatuses.has(nodeName)) {
385 this.populateNodeConnections(nodeName);
386 }
387 this.setCurrentNodeState(
388 this.clientStatuses.get(nodeName), connection.state());
389 }
390 }
391
Niko Sohmers2d108762024-02-02 20:21:14 -0800392 drawField(): void {
393 const ctx = this.canvas.getContext('2d');
394 ctx.save();
395 ctx.scale(1.0, -1.0);
396 ctx.drawImage(
397 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
398 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
399 ctx.restore();
400 }
401
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800402 drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
403 void {
404 const ctx = this.canvas.getContext('2d');
405 ctx.save();
406 ctx.translate(x, y);
407 ctx.rotate(theta);
408 ctx.strokeStyle = color;
409 ctx.beginPath();
410 ctx.moveTo(0.5, 0.5);
411 ctx.lineTo(0, 0);
412 ctx.lineTo(0.5, -0.5);
413 ctx.stroke();
414 ctx.beginPath();
415 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
416 ctx.stroke();
417 ctx.restore();
418 }
419
Mirabel Wang66546642024-02-10 16:37:05 -0800420 drawRobot(
421 x: number, y: number, theta: number, color: string = 'blue',
422 dashed: boolean = false): void {
423 const ctx = this.canvas.getContext('2d');
424 ctx.save();
425 ctx.translate(x, y);
426 ctx.rotate(theta);
427 ctx.strokeStyle = color;
428 ctx.lineWidth = ROBOT_WIDTH / 10.0;
429 if (dashed) {
430 ctx.setLineDash([0.05, 0.05]);
431 } else {
432 // Empty array = solid line.
433 ctx.setLineDash([]);
434 }
435 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
436 ctx.stroke();
437
438 // Draw line indicating which direction is forwards on the robot.
439 ctx.beginPath();
440 ctx.moveTo(0, 0);
441 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
442 ctx.stroke();
443
444 ctx.restore();
445}
446
447 setZeroing(div: HTMLElement): void {
448 div.innerHTML = 'zeroing';
449 div.classList.remove('faulted');
450 div.classList.add('zeroing');
451 div.classList.remove('near');
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800452 }
453
454 setEstopped(div: HTMLElement): void {
455 div.innerHTML = 'estopped';
456 div.classList.add('faulted');
457 div.classList.remove('zeroing');
458 div.classList.remove('near');
459 }
460
461 setTargetValue(
462 div: HTMLElement, target: number, val: number, tolerance: number): void {
463 div.innerHTML = val.toFixed(4);
464 div.classList.remove('faulted');
465 div.classList.remove('zeroing');
466 if (Math.abs(target - val) < tolerance) {
467 div.classList.add('near');
468 } else {
469 div.classList.remove('near');
470 }
471 }
Mirabel Wang66546642024-02-10 16:37:05 -0800472
473 setValue(div: HTMLElement, val: number): void {
474 div.innerHTML = val.toFixed(4);
475 div.classList.remove('faulted');
476 div.classList.remove('zeroing');
477 div.classList.remove('near');
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800478 }
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800479
480 setBoolean(div: HTMLElement, triggered: boolean): void {
481 div.innerHTML = ((triggered) ? "TRUE" : "FALSE")
Niko Sohmerse92516c2024-03-23 18:40:30 -0700482 div.className = '';
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800483 if (triggered) {
Niko Sohmerse92516c2024-03-23 18:40:30 -0700484 div.classList.add('lightgreen');
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800485 } else {
Niko Sohmerse92516c2024-03-23 18:40:30 -0700486 div.classList.add('lightcoral');
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800487 }
488 }
489
Niko Sohmers2d108762024-02-02 20:21:14 -0800490 draw(): void {
491 this.reset();
492 this.drawField();
493
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800494 // Draw the matches with debugging information from the localizer.
495 const now = Date.now() / 1000.0;
496
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800497 if (this.superstructureStatus) {
498 this.superstructureState.innerHTML =
499 SuperstructureState[this.superstructureStatus.state()];
Mirabel Wang66546642024-02-10 16:37:05 -0800500
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800501 this.intakeRollerState.innerHTML =
502 IntakeRollerStatus[this.superstructureStatus.intakeRoller()];
503 this.transferRollerState.innerHTML =
504 TransferRollerStatus[this.superstructureStatus.transferRoller()];
505 this.extendState.innerHTML =
506 ExtendStatus[this.superstructureStatus.extendStatus()];
507 this.extendRollerState.innerHTML =
508 ExtendRollerStatus[this.superstructureStatus.extendRoller()];
509 this.catapultState.innerHTML =
510 CatapultState[this.superstructureStatus.shooter().catapultState()];
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800511 this.uncompletedNoteGoal.innerHTML =
512 NoteStatus[this.superstructureStatus.uncompletedNoteGoal()];
513
514 this.setBoolean(this.extend_beambreak, this.superstructureStatus.extendBeambreak());
515
516 this.setBoolean(this.catapult_beambreak, this.superstructureStatus.catapultBeambreak());
517
Niko Sohmers2ac60c52024-03-16 18:44:54 -0700518 this.setBoolean(this.transfer_beambreak, this.superstructureStatus.transferBeambreak());
519
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800520 this.setBoolean(this.extend_ready_for_transfer, this.superstructureStatus.extendReadyForTransfer());
521
522 this.setBoolean(this.extend_at_retracted, this.superstructureStatus.extendAtRetracted());
523
524 this.setBoolean(this.turret_ready_for_load, this.superstructureStatus.turretReadyForLoad());
525
526 this.setBoolean(this.altitude_ready_for_load, this.superstructureStatus.altitudeReadyForLoad());
527
528 this.setBoolean(this.extend_ready_for_catapult_transfer, this.superstructureStatus.extendReadyForCatapultTransfer());
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800529
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800530 this.setBoolean(this.turret_in_range, this.superstructureStatus.shooter().turretInRange())
531
532 this.setBoolean(this.altitude_in_range, this.superstructureStatus.shooter().altitudeInRange())
533
534 this.setBoolean(this.altitude_above_min_angle, this.superstructureStatus.shooter().altitudeAboveMinAngle())
535
James Kuszmaul5d1b75e2024-02-25 14:37:00 -0800536 if (this.superstructureStatus.shooter() &&
537 this.superstructureStatus.shooter().aimer()) {
538 this.turret_position.innerHTML = this.superstructureStatus.shooter()
539 .aimer()
540 .turretPosition()
541 .toString();
542 this.turret_velocity.innerHTML = this.superstructureStatus.shooter()
543 .aimer()
544 .turretVelocity()
545 .toString();
546 this.target_distance.innerHTML = this.superstructureStatus.shooter()
547 .aimer()
548 .targetDistance()
549 .toString();
550 this.shot_distance.innerHTML = this.superstructureStatus.shooter()
551 .aimer()
552 .shotDistance()
553 .toString();
554 }
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800555
556 if (!this.superstructureStatus.intakePivot() ||
557 !this.superstructureStatus.intakePivot().zeroed()) {
558 this.setZeroing(this.intakePivot);
559 } else if (this.superstructureStatus.intakePivot().estopped()) {
560 this.setEstopped(this.intakePivot);
561 } else {
562 this.setTargetValue(
563 this.intakePivot,
564 this.superstructureStatus.intakePivot().unprofiledGoalPosition(),
565 this.superstructureStatus.intakePivot().estimatorState().position(),
566 1e-3);
567 }
568
569 this.intakePivotAbs.innerHTML = this.superstructureStatus.intakePivot().estimatorState().absolutePosition().toString();
570
571 if (!this.superstructureStatus.climber() ||
572 !this.superstructureStatus.climber().zeroed()) {
573 this.setZeroing(this.climber);
574 } else if (this.superstructureStatus.climber().estopped()) {
575 this.setEstopped(this.climber);
576 } else {
577 this.setTargetValue(
578 this.climber,
579 this.superstructureStatus.climber().unprofiledGoalPosition(),
580 this.superstructureStatus.climber().estimatorState().position(),
581 1e-3);
582 }
583
584 this.climberAbs.innerHTML = this.superstructureStatus.climber().estimatorState().absolutePosition().toString();
585 this.climberPot.innerHTML = this.superstructureStatus.climber().estimatorState().potPosition().toString();
586
587 if (!this.superstructureStatus.extend() ||
588 !this.superstructureStatus.extend().zeroed()) {
589 this.setZeroing(this.extend);
590 } else if (this.superstructureStatus.extend().estopped()) {
591 this.setEstopped(this.extend);
592 } else {
593 this.setTargetValue(
James Kuszmaul5d1b75e2024-02-25 14:37:00 -0800594 this.extend,
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800595 this.superstructureStatus.extend().unprofiledGoalPosition(),
596 this.superstructureStatus.extend().estimatorState().position(),
597 1e-3);
598 }
599
600 this.extendAbs.innerHTML = this.superstructureStatus.extend().estimatorState().absolutePosition().toString();
601 this.extendPot.innerHTML = this.superstructureStatus.extend().estimatorState().potPosition().toString();
602
603 if (!this.superstructureStatus.shooter().turret() ||
604 !this.superstructureStatus.shooter().turret().zeroed()) {
605 this.setZeroing(this.turret);
606 } else if (this.superstructureStatus.shooter().turret().estopped()) {
607 this.setEstopped(this.turret);
608 } else {
609 this.setTargetValue(
610 this.turret,
611 this.superstructureStatus.shooter().turret().unprofiledGoalPosition(),
612 this.superstructureStatus.shooter().turret().estimatorState().position(),
613 1e-3);
614 }
615
616 this.turretAbs.innerHTML = this.superstructureStatus.shooter().turret().estimatorState().absolutePosition().toString();
617 this.turretPot.innerHTML = this.superstructureStatus.shooter().turret().estimatorState().potPosition().toString();
618
619 if (!this.superstructureStatus.shooter().catapult() ||
620 !this.superstructureStatus.shooter().catapult().zeroed()) {
621 this.setZeroing(this.catapult);
622 } else if (this.superstructureStatus.shooter().catapult().estopped()) {
623 this.setEstopped(this.catapult);
624 } else {
625 this.setTargetValue(
626 this.catapult,
627 this.superstructureStatus.shooter().catapult().unprofiledGoalPosition(),
628 this.superstructureStatus.shooter().catapult().estimatorState().position(),
629 1e-3);
630 }
631
632 this.catapultAbs.innerHTML = this.superstructureStatus.shooter().catapult().estimatorState().absolutePosition().toString();
633 this.catapultPot.innerHTML = this.superstructureStatus.shooter().catapult().estimatorState().potPosition().toString();
634
635 if (!this.superstructureStatus.shooter().altitude() ||
636 !this.superstructureStatus.shooter().altitude().zeroed()) {
637 this.setZeroing(this.altitude);
638 } else if (this.superstructureStatus.shooter().altitude().estopped()) {
639 this.setEstopped(this.altitude);
640 } else {
641 this.setTargetValue(
642 this.altitude,
643 this.superstructureStatus.shooter().altitude().unprofiledGoalPosition(),
644 this.superstructureStatus.shooter().altitude().estimatorState().position(),
645 1e-3);
646 }
647
648 this.altitudeAbs.innerHTML = this.superstructureStatus.shooter().altitude().estimatorState().absolutePosition().toString();
649 this.altitudePot.innerHTML = this.superstructureStatus.shooter().altitude().estimatorState().potPosition().toString();
650
651 let zeroingErrors: string = 'Intake Pivot Errors:' +
652 '<br/>';
653 for (let i = 0; i < this.superstructureStatus.intakePivot()
654 .estimatorState()
655 .errorsLength();
656 i++) {
657 zeroingErrors += ZeroingError[this.superstructureStatus.intakePivot()
658 .estimatorState()
659 .errors(i)] +
660 '<br/>';
661 }
662 zeroingErrors += '<br/>' +
663 'Climber Errors:' +
664 '<br/>';
665 for (let i = 0; i < this.superstructureStatus.climber().estimatorState().errorsLength();
666 i++) {
667 zeroingErrors += ZeroingError[this.superstructureStatus.climber().estimatorState().errors(i)] +
668 '<br/>';
669 }
670 zeroingErrors += '<br/>' +
671 'Extend Errors:' +
672 '<br/>';
673 for (let i = 0; i < this.superstructureStatus.extend().estimatorState().errorsLength();
674 i++) {
675 zeroingErrors += ZeroingError[this.superstructureStatus.extend().estimatorState().errors(i)] +
676 '<br/>';
677 }
678 zeroingErrors += '<br/>' +
679 'Turret Errors:' +
680 '<br/>';
681 for (let i = 0; i < this.superstructureStatus.shooter().turret().estimatorState().errorsLength();
682 i++) {
683 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().turret().estimatorState().errors(i)] +
684 '<br/>';
685 }
686 zeroingErrors += '<br/>' +
687 'Catapult Errors:' +
688 '<br/>';
689 for (let i = 0; i < this.superstructureStatus.shooter().catapult().estimatorState().errorsLength();
690 i++) {
691 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().catapult().estimatorState().errors(i)] +
692 '<br/>';
693 }
694 zeroingErrors += '<br/>' +
695 'Altitude Errors:' +
696 '<br/>';
697 for (let i = 0; i < this.superstructureStatus.shooter().altitude().estimatorState().errorsLength();
698 i++) {
699 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().altitude().estimatorState().errors(i)] +
700 '<br/>';
701 }
702 this.zeroingFaults.innerHTML = zeroingErrors;
703 }
704
705 if (this.drivetrainPosition) {
706 this.leftDrivetrainEncoder.innerHTML =
707 this.drivetrainPosition.leftEncoder().toString();
708
709 this.rightDrivetrainEncoder.innerHTML =
710 this.drivetrainPosition.rightEncoder().toString();
Mirabel Wang66546642024-02-10 16:37:05 -0800711 }
712
713 if (this.drivetrainCANPosition) {
714 this.falconRightFrontPosition.innerHTML =
715 this.drivetrainCANPosition.talonfxs(0).position().toString();
716
717 this.falconRightBackPosition.innerHTML =
718 this.drivetrainCANPosition.talonfxs(1).position().toString();
719
720 this.falconLeftFrontPosition.innerHTML =
721 this.drivetrainCANPosition.talonfxs(2).position().toString();
722
723 this.falconLeftBackPosition.innerHTML =
724 this.drivetrainCANPosition.talonfxs(3).position().toString();
725 }
726
727 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
728 this.drawRobot(
729 this.drivetrainStatus.trajectoryLogging().x(),
730 this.drivetrainStatus.trajectoryLogging().y(),
731 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
732 false);
733 }
734
735 if (this.localizerOutput) {
736 if (!this.localizerOutput.zeroed()) {
737 this.setZeroing(this.x);
738 this.setZeroing(this.y);
739 this.setZeroing(this.theta);
740 } else {
741 this.setValue(this.x, this.localizerOutput.x());
742 this.setValue(this.y, this.localizerOutput.y());
743 this.setValue(this.theta, this.localizerOutput.theta());
744 }
745
746 this.drawRobot(
747 this.localizerOutput.x(), this.localizerOutput.y(),
748 this.localizerOutput.theta());
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800749
750 this.imagesAcceptedCounter.innerHTML =
751 this.localizerOutput.imageAcceptedCount().toString();
752 }
753
754 for (const [time, value] of this.localizerImageMatches) {
755 const age = now - time;
756 const kRemovalAge = 1.0;
757 if (age > kRemovalAge) {
758 this.localizerImageMatches.delete(time);
759 continue;
760 }
761 const kMaxImageAlpha = 0.5;
762 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
763 for (let i = 0; i < value.targetsLength(); i++) {
764 const imageDebug = value.targets(i);
765 const x = imageDebug.impliedRobotX();
766 const y = imageDebug.impliedRobotY();
767 const theta = imageDebug.impliedRobotTheta();
768 const cameraX = imageDebug.cameraX();
769 const cameraY = imageDebug.cameraY();
770 const cameraTheta = imageDebug.cameraTheta();
771 const accepted = imageDebug.accepted();
772 // Make camera readings fade over time.
773 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
774 const dashed = false;
775 const cameraRgb = CAMERA_COLORS[imageDebug.camera()];
776 const cameraRgba = cameraRgb + alpha;
777 this.drawRobot(x, y, theta, cameraRgba, dashed);
778 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
779 }
Mirabel Wang66546642024-02-10 16:37:05 -0800780 }
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800781
Niko Sohmers2d108762024-02-02 20:21:14 -0800782 window.requestAnimationFrame(() => this.draw());
783 }
784
785 reset(): void {
786 const ctx = this.canvas.getContext('2d');
787 // Empty space from the canvas boundary to the image
788 const IMAGE_PADDING = 10;
789 ctx.setTransform(1, 0, 0, 1, 0, 0);
790 const size = window.innerHeight * 0.9;
791 ctx.canvas.height = size;
792 const width = size / 2 + 20;
793 ctx.canvas.width = width;
794 ctx.clearRect(0, 0, size, width);
795
796 // Translate to center of display.
797 ctx.translate(width / 2, size / 2);
798 // Coordinate system is:
799 // x -> forward.
800 // y -> to the left.
801 ctx.rotate(-Math.PI / 2);
802 ctx.scale(1, -1);
803
804 const M_TO_PX = (size - IMAGE_PADDING) / FIELD_LENGTH;
805 ctx.scale(M_TO_PX, M_TO_PX);
806 ctx.lineWidth = 1 / M_TO_PX;
Niko Sohmers3860f8a2024-01-12 21:05:19 -0800807 }
808}