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