blob: 349008171c00588cbe64a54c51807f6e6d25cdfb [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);
81
82 private extend_at_retracted: HTMLElement =
83 (document.getElementById('extend_at_retracted') as HTMLElement);
84 private extend_ready_for_transfer: HTMLElement =
85 (document.getElementById('extend_ready_for_transfer') as HTMLElement);
86 private extend_ready_for_catapult_transfer: HTMLElement =
87 (document.getElementById('extend_ready_for_catapult_transfer') as HTMLElement);
88 private turret_ready_for_load: HTMLElement =
89 (document.getElementById('turret_ready_for_load') as HTMLElement);
90 private altitude_ready_for_load: HTMLElement =
91 (document.getElementById('altitude_ready_for_load') as HTMLElement);
92
Filip Kujawa1a2e9e02024-02-24 18:30:29 -080093
94 private intakePivot: HTMLElement =
95 (document.getElementById('intake_pivot') as HTMLElement);
96 private intakePivotAbs: HTMLElement =
97 (document.getElementById('intake_pivot_abs') as HTMLElement);
98
99 private climber: HTMLElement =
100 (document.getElementById('climber') as HTMLElement);
101 private climberAbs: HTMLElement =
102 (document.getElementById('climber_abs') as HTMLElement);
103 private climberPot: HTMLElement =
104 (document.getElementById('climber_pot') as HTMLElement);
105
106 private extend: HTMLElement =
107 (document.getElementById('extend') as HTMLElement);
108 private extendAbs: HTMLElement =
109 (document.getElementById('extend_abs') as HTMLElement);
110 private extendPot: HTMLElement =
111 (document.getElementById('extend_pot') as HTMLElement);
112
113 private turret: HTMLElement =
114 (document.getElementById('turret') as HTMLElement);
115 private turretAbs: HTMLElement =
116 (document.getElementById('turret_abs') as HTMLElement);
117 private turretPot: HTMLElement =
118 (document.getElementById('turret_pot') as HTMLElement);
119
120 private catapult: HTMLElement =
121 (document.getElementById('catapult') as HTMLElement);
122 private catapultAbs: HTMLElement =
James Kuszmaul51a677e2024-03-01 19:59:00 -0800123 (document.getElementById('catapult_abs') as HTMLElement);
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800124 private catapultPot: HTMLElement =
125 (document.getElementById('catapult_pot') as HTMLElement);
126
127 private altitude: HTMLElement =
128 (document.getElementById('altitude') as HTMLElement);
129 private altitudeAbs: HTMLElement =
130 (document.getElementById('altitude_abs') as HTMLElement);
131 private altitudePot: HTMLElement =
132 (document.getElementById('altitude_pot') as HTMLElement);
133
134 private turret_position: HTMLElement =
135 (document.getElementById('turret_position') as HTMLElement);
136 private turret_velocity: HTMLElement =
137 (document.getElementById('turret_velocity') as HTMLElement);
138 private target_distance: HTMLElement =
139 (document.getElementById('target_distance') as HTMLElement);
140 private shot_distance: HTMLElement =
141 (document.getElementById('shot_distance') as HTMLElement);
142
Mirabel Wang66546642024-02-10 16:37:05 -0800143 private leftDrivetrainEncoder: HTMLElement =
144 (document.getElementById('left_drivetrain_encoder') as HTMLElement);
145 private rightDrivetrainEncoder: HTMLElement =
146 (document.getElementById('right_drivetrain_encoder') as HTMLElement);
147 private falconRightFrontPosition: HTMLElement =
148 (document.getElementById('falcon_right_front') as HTMLElement);
149 private falconRightBackPosition: HTMLElement =
150 (document.getElementById('falcon_right_back') as HTMLElement);
151 private falconLeftFrontPosition: HTMLElement =
152 (document.getElementById('falcon_left_front') as HTMLElement);
153 private falconLeftBackPosition: HTMLElement =
154 (document.getElementById('falcon_left_back') as HTMLElement);
155
Niko Sohmers3860f8a2024-01-12 21:05:19 -0800156 constructor(private readonly connection: Connection) {
Niko Sohmers2d108762024-02-02 20:21:14 -0800157 (document.getElementById('field') as HTMLElement).appendChild(this.canvas);
158
159 this.fieldImage.src = '2024.png';
Mirabel Wang66546642024-02-10 16:37:05 -0800160
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800161 // Construct a table header.
162 {
163 const row = document.createElement('div');
164 const nameCell = document.createElement('div');
165 nameCell.innerHTML = 'Rejection Reason';
166 row.appendChild(nameCell);
167 for (const camera of CAMERAS) {
168 const nodeCell = document.createElement('div');
169 nodeCell.innerHTML = camera;
170 row.appendChild(nodeCell);
171 }
172 document.getElementById('vision_readouts').appendChild(row);
173 }
174
175 for (const value in RejectionReason) {
176 // Typescript generates an iterator that produces both numbers and
177 // strings... don't do anything on the string iterations.
178 if (isNaN(Number(value))) {
179 continue;
180 }
181 const row = document.createElement('div');
182 const nameCell = document.createElement('div');
183 nameCell.innerHTML = RejectionReason[value];
184 row.appendChild(nameCell);
185 this.rejectionReasonCells.push([]);
186 for (const camera of CAMERAS) {
187 const valueCell = document.createElement('div');
188 valueCell.innerHTML = 'NA';
189 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
190 valueCell);
191 row.appendChild(valueCell);
192 }
193 document.getElementById('vision_readouts').appendChild(row);
194 }
195
196 // Add rejection reason row for aprilrobotics rejections.
197 {
198 const row = document.createElement('div');
199 const nameCell = document.createElement('div');
200 nameCell.innerHTML = 'Rejected by aprilrobotics';
201 row.appendChild(nameCell);
202 this.rejectionReasonCells.push([]);
203 for (const camera of CAMERAS) {
204 const valueCell = document.createElement('div');
205 valueCell.innerHTML = 'NA';
206 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
207 valueCell);
208 row.appendChild(valueCell);
209 }
210 document.getElementById('vision_readouts').appendChild(row);
211 }
212
213 for (let ii = 0; ii < CAMERA_COLORS.length; ++ii) {
214 const legendEntry = document.createElement('div');
215 legendEntry.style.color = CAMERA_COLORS[ii];
216 legendEntry.innerHTML = CAMERAS[ii];
217 document.getElementById('legend').appendChild(legendEntry);
218 }
219
Mirabel Wang66546642024-02-10 16:37:05 -0800220 this.connection.addConfigHandler(() => {
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800221 // Visualization message is reliable so that we can see *all* the vision
222 // matches.
223 for (const camera in CAMERAS) {
224 this.connection.addHandler(
225 CAMERAS[camera], 'y2024.localizer.Visualization',
226 (data) => {
227 this.handleLocalizerDebug(Number(camera), data);
228 });
229 }
230 for (const camera in CAMERAS) {
231 // Make unreliable to reduce network spam.
232 this.connection.addHandler(
233 CAMERAS[camera], 'frc971.vision.TargetMap', (data) => {
234 this.handleCameraTargetMap(camera, data);
235 });
236 }
Mirabel Wang66546642024-02-10 16:37:05 -0800237
238 this.connection.addHandler(
239 '/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
240 this.handleDrivetrainStatus(data);
241 });
242 this.connection.addHandler(
243 '/drivetrain', 'frc971.control_loops.drivetrain.Position', (data) => {
244 this.handleDrivetrainPosition(data);
245 });
246 this.connection.addHandler(
247 '/drivetrain', 'frc971.control_loops.drivetrain.CANPosition', (data) => {
248 this.handleDrivetrainCANPosition(data);
249 });
250 this.connection.addHandler(
251 '/localizer', 'frc971.controls.LocalizerOutput', (data) => {
252 this.handleLocalizerOutput(data);
253 });
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800254 this.connection.addHandler(
255 '/superstructure', "y2024.control_loops.superstructure.Status",
256 (data) => {
257 this.handleSuperstructureStatus(data)
258 });
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800259 this.connection.addHandler(
260 '/aos', 'aos.message_bridge.ServerStatistics',
261 (data) => {this.handleServerStatistics(data)});
262 this.connection.addHandler(
263 '/aos', 'aos.message_bridge.ClientStatistics',
264 (data) => {this.handleClientStatistics(data)});
Mirabel Wang66546642024-02-10 16:37:05 -0800265 });
266 }
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800267 private handleLocalizerDebug(camera: number, data: Uint8Array): void {
268 const now = Date.now() / 1000.0;
269
270 const fbBuffer = new ByteBuffer(data);
271 this.localizerImageMatches.set(
272 now, Visualization.getRootAsVisualization(fbBuffer));
273
274 const debug = this.localizerImageMatches.get(now);
275
276 if (debug.statistics()) {
277 if ((debug.statistics().rejectionReasonsLength() + 1) ==
278 this.rejectionReasonCells.length) {
279 for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
280 ++ii) {
281 this.rejectionReasonCells[ii][camera].innerHTML =
282 debug.statistics().rejectionReasons(ii).count().toString();
283 }
284 } else {
285 console.error('Unexpected number of rejection reasons in counter.');
286 }
287 }
288 }
289
290 private handleCameraTargetMap(pi: string, data: Uint8Array): void {
291 const fbBuffer = new ByteBuffer(data);
292 const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
293 this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
294 .innerHTML = targetMap.rejections().toString();
295 }
Mirabel Wang66546642024-02-10 16:37:05 -0800296
297 private handleDrivetrainStatus(data: Uint8Array): void {
298 const fbBuffer = new ByteBuffer(data);
299 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
300 }
301
302 private handleDrivetrainPosition(data: Uint8Array): void {
303 const fbBuffer = new ByteBuffer(data);
304 this.drivetrainPosition = DrivetrainPosition.getRootAsPosition(fbBuffer);
305 }
306
307 private handleDrivetrainCANPosition(data: Uint8Array): void {
308 const fbBuffer = new ByteBuffer(data);
309 this.drivetrainCANPosition = DrivetrainCANPosition.getRootAsCANPosition(fbBuffer);
310 }
311
312 private handleLocalizerOutput(data: Uint8Array): void {
313 const fbBuffer = new ByteBuffer(data);
314 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
Niko Sohmers2d108762024-02-02 20:21:14 -0800315 }
316
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800317 private handleSuperstructureStatus(data: Uint8Array): void {
318 const fbBuffer = new ByteBuffer(data);
319 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
320 }
321
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800322 private populateNodeConnections(nodeName: string): void {
323 const row = document.createElement('div');
324 this.messageBridgeDiv.appendChild(row);
325 const nodeDiv = document.createElement('div');
326 nodeDiv.innerHTML = nodeName;
327 row.appendChild(nodeDiv);
328 const clientDiv = document.createElement('div');
329 clientDiv.innerHTML = 'N/A';
330 row.appendChild(clientDiv);
331 const serverDiv = document.createElement('div');
332 serverDiv.innerHTML = 'N/A';
333 row.appendChild(serverDiv);
334 this.serverStatuses.set(nodeName, serverDiv);
335 this.clientStatuses.set(nodeName, clientDiv);
336 }
337
338 private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
339 void {
340 if (state === ConnectionState.CONNECTED) {
341 element.innerHTML = ConnectionState[state];
342 element.classList.remove('faulted');
343 element.classList.add('connected');
344 } else {
345 element.innerHTML = ConnectionState[state];
346 element.classList.remove('connected');
347 element.classList.add('faulted');
348 }
349 }
350
351 private handleServerStatistics(data: Uint8Array): void {
352 const fbBuffer = new ByteBuffer(data);
353 const serverStatistics =
354 ServerStatistics.getRootAsServerStatistics(fbBuffer);
355
356 for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
357 const connection = serverStatistics.connections(ii);
358 const nodeName = connection.node().name();
359 if (!this.serverStatuses.has(nodeName)) {
360 this.populateNodeConnections(nodeName);
361 }
362 this.setCurrentNodeState(
363 this.serverStatuses.get(nodeName), connection.state());
364 }
365 }
366
367 private handleClientStatistics(data: Uint8Array): void {
368 const fbBuffer = new ByteBuffer(data);
369 const clientStatistics =
370 ClientStatistics.getRootAsClientStatistics(fbBuffer);
371
372 for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
373 const connection = clientStatistics.connections(ii);
374 const nodeName = connection.node().name();
375 if (!this.clientStatuses.has(nodeName)) {
376 this.populateNodeConnections(nodeName);
377 }
378 this.setCurrentNodeState(
379 this.clientStatuses.get(nodeName), connection.state());
380 }
381 }
382
Niko Sohmers2d108762024-02-02 20:21:14 -0800383 drawField(): void {
384 const ctx = this.canvas.getContext('2d');
385 ctx.save();
386 ctx.scale(1.0, -1.0);
387 ctx.drawImage(
388 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
389 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
390 ctx.restore();
391 }
392
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800393 drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
394 void {
395 const ctx = this.canvas.getContext('2d');
396 ctx.save();
397 ctx.translate(x, y);
398 ctx.rotate(theta);
399 ctx.strokeStyle = color;
400 ctx.beginPath();
401 ctx.moveTo(0.5, 0.5);
402 ctx.lineTo(0, 0);
403 ctx.lineTo(0.5, -0.5);
404 ctx.stroke();
405 ctx.beginPath();
406 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
407 ctx.stroke();
408 ctx.restore();
409 }
410
Mirabel Wang66546642024-02-10 16:37:05 -0800411 drawRobot(
412 x: number, y: number, theta: number, color: string = 'blue',
413 dashed: boolean = false): void {
414 const ctx = this.canvas.getContext('2d');
415 ctx.save();
416 ctx.translate(x, y);
417 ctx.rotate(theta);
418 ctx.strokeStyle = color;
419 ctx.lineWidth = ROBOT_WIDTH / 10.0;
420 if (dashed) {
421 ctx.setLineDash([0.05, 0.05]);
422 } else {
423 // Empty array = solid line.
424 ctx.setLineDash([]);
425 }
426 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
427 ctx.stroke();
428
429 // Draw line indicating which direction is forwards on the robot.
430 ctx.beginPath();
431 ctx.moveTo(0, 0);
432 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
433 ctx.stroke();
434
435 ctx.restore();
436}
437
438 setZeroing(div: HTMLElement): void {
439 div.innerHTML = 'zeroing';
440 div.classList.remove('faulted');
441 div.classList.add('zeroing');
442 div.classList.remove('near');
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800443 }
444
445 setEstopped(div: HTMLElement): void {
446 div.innerHTML = 'estopped';
447 div.classList.add('faulted');
448 div.classList.remove('zeroing');
449 div.classList.remove('near');
450 }
451
452 setTargetValue(
453 div: HTMLElement, target: number, val: number, tolerance: number): void {
454 div.innerHTML = val.toFixed(4);
455 div.classList.remove('faulted');
456 div.classList.remove('zeroing');
457 if (Math.abs(target - val) < tolerance) {
458 div.classList.add('near');
459 } else {
460 div.classList.remove('near');
461 }
462 }
Mirabel Wang66546642024-02-10 16:37:05 -0800463
464 setValue(div: HTMLElement, val: number): void {
465 div.innerHTML = val.toFixed(4);
466 div.classList.remove('faulted');
467 div.classList.remove('zeroing');
468 div.classList.remove('near');
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800469 }
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800470
471 setBoolean(div: HTMLElement, triggered: boolean): void {
472 div.innerHTML = ((triggered) ? "TRUE" : "FALSE")
473 if (triggered) {
474 div.classList.remove('false');
475 div.classList.add('true');
476 } else {
477 div.classList.remove('true');
478 div.classList.add('false');
479 }
480 }
481
Niko Sohmers2d108762024-02-02 20:21:14 -0800482 draw(): void {
483 this.reset();
484 this.drawField();
485
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800486 // Draw the matches with debugging information from the localizer.
487 const now = Date.now() / 1000.0;
488
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800489 if (this.superstructureStatus) {
490 this.superstructureState.innerHTML =
491 SuperstructureState[this.superstructureStatus.state()];
Mirabel Wang66546642024-02-10 16:37:05 -0800492
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800493 this.intakeRollerState.innerHTML =
494 IntakeRollerStatus[this.superstructureStatus.intakeRoller()];
495 this.transferRollerState.innerHTML =
496 TransferRollerStatus[this.superstructureStatus.transferRoller()];
497 this.extendState.innerHTML =
498 ExtendStatus[this.superstructureStatus.extendStatus()];
499 this.extendRollerState.innerHTML =
500 ExtendRollerStatus[this.superstructureStatus.extendRoller()];
501 this.catapultState.innerHTML =
502 CatapultState[this.superstructureStatus.shooter().catapultState()];
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800503 this.uncompletedNoteGoal.innerHTML =
504 NoteStatus[this.superstructureStatus.uncompletedNoteGoal()];
505
506 this.setBoolean(this.extend_beambreak, this.superstructureStatus.extendBeambreak());
507
508 this.setBoolean(this.catapult_beambreak, this.superstructureStatus.catapultBeambreak());
509
510 this.setBoolean(this.extend_ready_for_transfer, this.superstructureStatus.extendReadyForTransfer());
511
512 this.setBoolean(this.extend_at_retracted, this.superstructureStatus.extendAtRetracted());
513
514 this.setBoolean(this.turret_ready_for_load, this.superstructureStatus.turretReadyForLoad());
515
516 this.setBoolean(this.altitude_ready_for_load, this.superstructureStatus.altitudeReadyForLoad());
517
518 this.setBoolean(this.extend_ready_for_catapult_transfer, this.superstructureStatus.extendReadyForCatapultTransfer());
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800519
James Kuszmaul5d1b75e2024-02-25 14:37:00 -0800520 if (this.superstructureStatus.shooter() &&
521 this.superstructureStatus.shooter().aimer()) {
522 this.turret_position.innerHTML = this.superstructureStatus.shooter()
523 .aimer()
524 .turretPosition()
525 .toString();
526 this.turret_velocity.innerHTML = this.superstructureStatus.shooter()
527 .aimer()
528 .turretVelocity()
529 .toString();
530 this.target_distance.innerHTML = this.superstructureStatus.shooter()
531 .aimer()
532 .targetDistance()
533 .toString();
534 this.shot_distance.innerHTML = this.superstructureStatus.shooter()
535 .aimer()
536 .shotDistance()
537 .toString();
538 }
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800539
540 if (!this.superstructureStatus.intakePivot() ||
541 !this.superstructureStatus.intakePivot().zeroed()) {
542 this.setZeroing(this.intakePivot);
543 } else if (this.superstructureStatus.intakePivot().estopped()) {
544 this.setEstopped(this.intakePivot);
545 } else {
546 this.setTargetValue(
547 this.intakePivot,
548 this.superstructureStatus.intakePivot().unprofiledGoalPosition(),
549 this.superstructureStatus.intakePivot().estimatorState().position(),
550 1e-3);
551 }
552
553 this.intakePivotAbs.innerHTML = this.superstructureStatus.intakePivot().estimatorState().absolutePosition().toString();
554
555 if (!this.superstructureStatus.climber() ||
556 !this.superstructureStatus.climber().zeroed()) {
557 this.setZeroing(this.climber);
558 } else if (this.superstructureStatus.climber().estopped()) {
559 this.setEstopped(this.climber);
560 } else {
561 this.setTargetValue(
562 this.climber,
563 this.superstructureStatus.climber().unprofiledGoalPosition(),
564 this.superstructureStatus.climber().estimatorState().position(),
565 1e-3);
566 }
567
568 this.climberAbs.innerHTML = this.superstructureStatus.climber().estimatorState().absolutePosition().toString();
569 this.climberPot.innerHTML = this.superstructureStatus.climber().estimatorState().potPosition().toString();
570
571 if (!this.superstructureStatus.extend() ||
572 !this.superstructureStatus.extend().zeroed()) {
573 this.setZeroing(this.extend);
574 } else if (this.superstructureStatus.extend().estopped()) {
575 this.setEstopped(this.extend);
576 } else {
577 this.setTargetValue(
James Kuszmaul5d1b75e2024-02-25 14:37:00 -0800578 this.extend,
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800579 this.superstructureStatus.extend().unprofiledGoalPosition(),
580 this.superstructureStatus.extend().estimatorState().position(),
581 1e-3);
582 }
583
584 this.extendAbs.innerHTML = this.superstructureStatus.extend().estimatorState().absolutePosition().toString();
585 this.extendPot.innerHTML = this.superstructureStatus.extend().estimatorState().potPosition().toString();
586
587 if (!this.superstructureStatus.shooter().turret() ||
588 !this.superstructureStatus.shooter().turret().zeroed()) {
589 this.setZeroing(this.turret);
590 } else if (this.superstructureStatus.shooter().turret().estopped()) {
591 this.setEstopped(this.turret);
592 } else {
593 this.setTargetValue(
594 this.turret,
595 this.superstructureStatus.shooter().turret().unprofiledGoalPosition(),
596 this.superstructureStatus.shooter().turret().estimatorState().position(),
597 1e-3);
598 }
599
600 this.turretAbs.innerHTML = this.superstructureStatus.shooter().turret().estimatorState().absolutePosition().toString();
601 this.turretPot.innerHTML = this.superstructureStatus.shooter().turret().estimatorState().potPosition().toString();
602
603 if (!this.superstructureStatus.shooter().catapult() ||
604 !this.superstructureStatus.shooter().catapult().zeroed()) {
605 this.setZeroing(this.catapult);
606 } else if (this.superstructureStatus.shooter().catapult().estopped()) {
607 this.setEstopped(this.catapult);
608 } else {
609 this.setTargetValue(
610 this.catapult,
611 this.superstructureStatus.shooter().catapult().unprofiledGoalPosition(),
612 this.superstructureStatus.shooter().catapult().estimatorState().position(),
613 1e-3);
614 }
615
616 this.catapultAbs.innerHTML = this.superstructureStatus.shooter().catapult().estimatorState().absolutePosition().toString();
617 this.catapultPot.innerHTML = this.superstructureStatus.shooter().catapult().estimatorState().potPosition().toString();
618
619 if (!this.superstructureStatus.shooter().altitude() ||
620 !this.superstructureStatus.shooter().altitude().zeroed()) {
621 this.setZeroing(this.altitude);
622 } else if (this.superstructureStatus.shooter().altitude().estopped()) {
623 this.setEstopped(this.altitude);
624 } else {
625 this.setTargetValue(
626 this.altitude,
627 this.superstructureStatus.shooter().altitude().unprofiledGoalPosition(),
628 this.superstructureStatus.shooter().altitude().estimatorState().position(),
629 1e-3);
630 }
631
632 this.altitudeAbs.innerHTML = this.superstructureStatus.shooter().altitude().estimatorState().absolutePosition().toString();
633 this.altitudePot.innerHTML = this.superstructureStatus.shooter().altitude().estimatorState().potPosition().toString();
634
635 let zeroingErrors: string = 'Intake Pivot Errors:' +
636 '<br/>';
637 for (let i = 0; i < this.superstructureStatus.intakePivot()
638 .estimatorState()
639 .errorsLength();
640 i++) {
641 zeroingErrors += ZeroingError[this.superstructureStatus.intakePivot()
642 .estimatorState()
643 .errors(i)] +
644 '<br/>';
645 }
646 zeroingErrors += '<br/>' +
647 'Climber Errors:' +
648 '<br/>';
649 for (let i = 0; i < this.superstructureStatus.climber().estimatorState().errorsLength();
650 i++) {
651 zeroingErrors += ZeroingError[this.superstructureStatus.climber().estimatorState().errors(i)] +
652 '<br/>';
653 }
654 zeroingErrors += '<br/>' +
655 'Extend Errors:' +
656 '<br/>';
657 for (let i = 0; i < this.superstructureStatus.extend().estimatorState().errorsLength();
658 i++) {
659 zeroingErrors += ZeroingError[this.superstructureStatus.extend().estimatorState().errors(i)] +
660 '<br/>';
661 }
662 zeroingErrors += '<br/>' +
663 'Turret Errors:' +
664 '<br/>';
665 for (let i = 0; i < this.superstructureStatus.shooter().turret().estimatorState().errorsLength();
666 i++) {
667 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().turret().estimatorState().errors(i)] +
668 '<br/>';
669 }
670 zeroingErrors += '<br/>' +
671 'Catapult Errors:' +
672 '<br/>';
673 for (let i = 0; i < this.superstructureStatus.shooter().catapult().estimatorState().errorsLength();
674 i++) {
675 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().catapult().estimatorState().errors(i)] +
676 '<br/>';
677 }
678 zeroingErrors += '<br/>' +
679 'Altitude Errors:' +
680 '<br/>';
681 for (let i = 0; i < this.superstructureStatus.shooter().altitude().estimatorState().errorsLength();
682 i++) {
683 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().altitude().estimatorState().errors(i)] +
684 '<br/>';
685 }
686 this.zeroingFaults.innerHTML = zeroingErrors;
687 }
688
689 if (this.drivetrainPosition) {
690 this.leftDrivetrainEncoder.innerHTML =
691 this.drivetrainPosition.leftEncoder().toString();
692
693 this.rightDrivetrainEncoder.innerHTML =
694 this.drivetrainPosition.rightEncoder().toString();
Mirabel Wang66546642024-02-10 16:37:05 -0800695 }
696
697 if (this.drivetrainCANPosition) {
698 this.falconRightFrontPosition.innerHTML =
699 this.drivetrainCANPosition.talonfxs(0).position().toString();
700
701 this.falconRightBackPosition.innerHTML =
702 this.drivetrainCANPosition.talonfxs(1).position().toString();
703
704 this.falconLeftFrontPosition.innerHTML =
705 this.drivetrainCANPosition.talonfxs(2).position().toString();
706
707 this.falconLeftBackPosition.innerHTML =
708 this.drivetrainCANPosition.talonfxs(3).position().toString();
709 }
710
711 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
712 this.drawRobot(
713 this.drivetrainStatus.trajectoryLogging().x(),
714 this.drivetrainStatus.trajectoryLogging().y(),
715 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
716 false);
717 }
718
719 if (this.localizerOutput) {
720 if (!this.localizerOutput.zeroed()) {
721 this.setZeroing(this.x);
722 this.setZeroing(this.y);
723 this.setZeroing(this.theta);
724 } else {
725 this.setValue(this.x, this.localizerOutput.x());
726 this.setValue(this.y, this.localizerOutput.y());
727 this.setValue(this.theta, this.localizerOutput.theta());
728 }
729
730 this.drawRobot(
731 this.localizerOutput.x(), this.localizerOutput.y(),
732 this.localizerOutput.theta());
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800733
734 this.imagesAcceptedCounter.innerHTML =
735 this.localizerOutput.imageAcceptedCount().toString();
736 }
737
738 for (const [time, value] of this.localizerImageMatches) {
739 const age = now - time;
740 const kRemovalAge = 1.0;
741 if (age > kRemovalAge) {
742 this.localizerImageMatches.delete(time);
743 continue;
744 }
745 const kMaxImageAlpha = 0.5;
746 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
747 for (let i = 0; i < value.targetsLength(); i++) {
748 const imageDebug = value.targets(i);
749 const x = imageDebug.impliedRobotX();
750 const y = imageDebug.impliedRobotY();
751 const theta = imageDebug.impliedRobotTheta();
752 const cameraX = imageDebug.cameraX();
753 const cameraY = imageDebug.cameraY();
754 const cameraTheta = imageDebug.cameraTheta();
755 const accepted = imageDebug.accepted();
756 // Make camera readings fade over time.
757 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
758 const dashed = false;
759 const cameraRgb = CAMERA_COLORS[imageDebug.camera()];
760 const cameraRgba = cameraRgb + alpha;
761 this.drawRobot(x, y, theta, cameraRgba, dashed);
762 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
763 }
Mirabel Wang66546642024-02-10 16:37:05 -0800764 }
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800765
Niko Sohmers2d108762024-02-02 20:21:14 -0800766 window.requestAnimationFrame(() => this.draw());
767 }
768
769 reset(): void {
770 const ctx = this.canvas.getContext('2d');
771 // Empty space from the canvas boundary to the image
772 const IMAGE_PADDING = 10;
773 ctx.setTransform(1, 0, 0, 1, 0, 0);
774 const size = window.innerHeight * 0.9;
775 ctx.canvas.height = size;
776 const width = size / 2 + 20;
777 ctx.canvas.width = width;
778 ctx.clearRect(0, 0, size, width);
779
780 // Translate to center of display.
781 ctx.translate(width / 2, size / 2);
782 // Coordinate system is:
783 // x -> forward.
784 // y -> to the left.
785 ctx.rotate(-Math.PI / 2);
786 ctx.scale(1, -1);
787
788 const M_TO_PX = (size - IMAGE_PADDING) / FIELD_LENGTH;
789 ctx.scale(M_TO_PX, M_TO_PX);
790 ctx.lineWidth = 1 / M_TO_PX;
Niko Sohmers3860f8a2024-01-12 21:05:19 -0800791 }
792}