blob: 6573e8dcd61c0427e2d7e7a3fb6315cc415ce65b [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")
482 if (triggered) {
483 div.classList.remove('false');
484 div.classList.add('true');
485 } else {
486 div.classList.remove('true');
487 div.classList.add('false');
488 }
489 }
490
Niko Sohmers2d108762024-02-02 20:21:14 -0800491 draw(): void {
492 this.reset();
493 this.drawField();
494
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800495 // Draw the matches with debugging information from the localizer.
496 const now = Date.now() / 1000.0;
497
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800498 if (this.superstructureStatus) {
499 this.superstructureState.innerHTML =
500 SuperstructureState[this.superstructureStatus.state()];
Mirabel Wang66546642024-02-10 16:37:05 -0800501
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800502 this.intakeRollerState.innerHTML =
503 IntakeRollerStatus[this.superstructureStatus.intakeRoller()];
504 this.transferRollerState.innerHTML =
505 TransferRollerStatus[this.superstructureStatus.transferRoller()];
506 this.extendState.innerHTML =
507 ExtendStatus[this.superstructureStatus.extendStatus()];
508 this.extendRollerState.innerHTML =
509 ExtendRollerStatus[this.superstructureStatus.extendRoller()];
510 this.catapultState.innerHTML =
511 CatapultState[this.superstructureStatus.shooter().catapultState()];
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800512 this.uncompletedNoteGoal.innerHTML =
513 NoteStatus[this.superstructureStatus.uncompletedNoteGoal()];
514
515 this.setBoolean(this.extend_beambreak, this.superstructureStatus.extendBeambreak());
516
517 this.setBoolean(this.catapult_beambreak, this.superstructureStatus.catapultBeambreak());
518
Niko Sohmers2ac60c52024-03-16 18:44:54 -0700519 this.setBoolean(this.transfer_beambreak, this.superstructureStatus.transferBeambreak());
520
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800521 this.setBoolean(this.extend_ready_for_transfer, this.superstructureStatus.extendReadyForTransfer());
522
523 this.setBoolean(this.extend_at_retracted, this.superstructureStatus.extendAtRetracted());
524
525 this.setBoolean(this.turret_ready_for_load, this.superstructureStatus.turretReadyForLoad());
526
527 this.setBoolean(this.altitude_ready_for_load, this.superstructureStatus.altitudeReadyForLoad());
528
529 this.setBoolean(this.extend_ready_for_catapult_transfer, this.superstructureStatus.extendReadyForCatapultTransfer());
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800530
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800531 this.setBoolean(this.turret_in_range, this.superstructureStatus.shooter().turretInRange())
532
533 this.setBoolean(this.altitude_in_range, this.superstructureStatus.shooter().altitudeInRange())
534
535 this.setBoolean(this.altitude_above_min_angle, this.superstructureStatus.shooter().altitudeAboveMinAngle())
536
James Kuszmaul5d1b75e2024-02-25 14:37:00 -0800537 if (this.superstructureStatus.shooter() &&
538 this.superstructureStatus.shooter().aimer()) {
539 this.turret_position.innerHTML = this.superstructureStatus.shooter()
540 .aimer()
541 .turretPosition()
542 .toString();
543 this.turret_velocity.innerHTML = this.superstructureStatus.shooter()
544 .aimer()
545 .turretVelocity()
546 .toString();
547 this.target_distance.innerHTML = this.superstructureStatus.shooter()
548 .aimer()
549 .targetDistance()
550 .toString();
551 this.shot_distance.innerHTML = this.superstructureStatus.shooter()
552 .aimer()
553 .shotDistance()
554 .toString();
555 }
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800556
557 if (!this.superstructureStatus.intakePivot() ||
558 !this.superstructureStatus.intakePivot().zeroed()) {
559 this.setZeroing(this.intakePivot);
560 } else if (this.superstructureStatus.intakePivot().estopped()) {
561 this.setEstopped(this.intakePivot);
562 } else {
563 this.setTargetValue(
564 this.intakePivot,
565 this.superstructureStatus.intakePivot().unprofiledGoalPosition(),
566 this.superstructureStatus.intakePivot().estimatorState().position(),
567 1e-3);
568 }
569
570 this.intakePivotAbs.innerHTML = this.superstructureStatus.intakePivot().estimatorState().absolutePosition().toString();
571
572 if (!this.superstructureStatus.climber() ||
573 !this.superstructureStatus.climber().zeroed()) {
574 this.setZeroing(this.climber);
575 } else if (this.superstructureStatus.climber().estopped()) {
576 this.setEstopped(this.climber);
577 } else {
578 this.setTargetValue(
579 this.climber,
580 this.superstructureStatus.climber().unprofiledGoalPosition(),
581 this.superstructureStatus.climber().estimatorState().position(),
582 1e-3);
583 }
584
585 this.climberAbs.innerHTML = this.superstructureStatus.climber().estimatorState().absolutePosition().toString();
586 this.climberPot.innerHTML = this.superstructureStatus.climber().estimatorState().potPosition().toString();
587
588 if (!this.superstructureStatus.extend() ||
589 !this.superstructureStatus.extend().zeroed()) {
590 this.setZeroing(this.extend);
591 } else if (this.superstructureStatus.extend().estopped()) {
592 this.setEstopped(this.extend);
593 } else {
594 this.setTargetValue(
James Kuszmaul5d1b75e2024-02-25 14:37:00 -0800595 this.extend,
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800596 this.superstructureStatus.extend().unprofiledGoalPosition(),
597 this.superstructureStatus.extend().estimatorState().position(),
598 1e-3);
599 }
600
601 this.extendAbs.innerHTML = this.superstructureStatus.extend().estimatorState().absolutePosition().toString();
602 this.extendPot.innerHTML = this.superstructureStatus.extend().estimatorState().potPosition().toString();
603
604 if (!this.superstructureStatus.shooter().turret() ||
605 !this.superstructureStatus.shooter().turret().zeroed()) {
606 this.setZeroing(this.turret);
607 } else if (this.superstructureStatus.shooter().turret().estopped()) {
608 this.setEstopped(this.turret);
609 } else {
610 this.setTargetValue(
611 this.turret,
612 this.superstructureStatus.shooter().turret().unprofiledGoalPosition(),
613 this.superstructureStatus.shooter().turret().estimatorState().position(),
614 1e-3);
615 }
616
617 this.turretAbs.innerHTML = this.superstructureStatus.shooter().turret().estimatorState().absolutePosition().toString();
618 this.turretPot.innerHTML = this.superstructureStatus.shooter().turret().estimatorState().potPosition().toString();
619
620 if (!this.superstructureStatus.shooter().catapult() ||
621 !this.superstructureStatus.shooter().catapult().zeroed()) {
622 this.setZeroing(this.catapult);
623 } else if (this.superstructureStatus.shooter().catapult().estopped()) {
624 this.setEstopped(this.catapult);
625 } else {
626 this.setTargetValue(
627 this.catapult,
628 this.superstructureStatus.shooter().catapult().unprofiledGoalPosition(),
629 this.superstructureStatus.shooter().catapult().estimatorState().position(),
630 1e-3);
631 }
632
633 this.catapultAbs.innerHTML = this.superstructureStatus.shooter().catapult().estimatorState().absolutePosition().toString();
634 this.catapultPot.innerHTML = this.superstructureStatus.shooter().catapult().estimatorState().potPosition().toString();
635
636 if (!this.superstructureStatus.shooter().altitude() ||
637 !this.superstructureStatus.shooter().altitude().zeroed()) {
638 this.setZeroing(this.altitude);
639 } else if (this.superstructureStatus.shooter().altitude().estopped()) {
640 this.setEstopped(this.altitude);
641 } else {
642 this.setTargetValue(
643 this.altitude,
644 this.superstructureStatus.shooter().altitude().unprofiledGoalPosition(),
645 this.superstructureStatus.shooter().altitude().estimatorState().position(),
646 1e-3);
647 }
648
649 this.altitudeAbs.innerHTML = this.superstructureStatus.shooter().altitude().estimatorState().absolutePosition().toString();
650 this.altitudePot.innerHTML = this.superstructureStatus.shooter().altitude().estimatorState().potPosition().toString();
651
652 let zeroingErrors: string = 'Intake Pivot Errors:' +
653 '<br/>';
654 for (let i = 0; i < this.superstructureStatus.intakePivot()
655 .estimatorState()
656 .errorsLength();
657 i++) {
658 zeroingErrors += ZeroingError[this.superstructureStatus.intakePivot()
659 .estimatorState()
660 .errors(i)] +
661 '<br/>';
662 }
663 zeroingErrors += '<br/>' +
664 'Climber Errors:' +
665 '<br/>';
666 for (let i = 0; i < this.superstructureStatus.climber().estimatorState().errorsLength();
667 i++) {
668 zeroingErrors += ZeroingError[this.superstructureStatus.climber().estimatorState().errors(i)] +
669 '<br/>';
670 }
671 zeroingErrors += '<br/>' +
672 'Extend Errors:' +
673 '<br/>';
674 for (let i = 0; i < this.superstructureStatus.extend().estimatorState().errorsLength();
675 i++) {
676 zeroingErrors += ZeroingError[this.superstructureStatus.extend().estimatorState().errors(i)] +
677 '<br/>';
678 }
679 zeroingErrors += '<br/>' +
680 'Turret Errors:' +
681 '<br/>';
682 for (let i = 0; i < this.superstructureStatus.shooter().turret().estimatorState().errorsLength();
683 i++) {
684 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().turret().estimatorState().errors(i)] +
685 '<br/>';
686 }
687 zeroingErrors += '<br/>' +
688 'Catapult Errors:' +
689 '<br/>';
690 for (let i = 0; i < this.superstructureStatus.shooter().catapult().estimatorState().errorsLength();
691 i++) {
692 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().catapult().estimatorState().errors(i)] +
693 '<br/>';
694 }
695 zeroingErrors += '<br/>' +
696 'Altitude Errors:' +
697 '<br/>';
698 for (let i = 0; i < this.superstructureStatus.shooter().altitude().estimatorState().errorsLength();
699 i++) {
700 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().altitude().estimatorState().errors(i)] +
701 '<br/>';
702 }
703 this.zeroingFaults.innerHTML = zeroingErrors;
704 }
705
706 if (this.drivetrainPosition) {
707 this.leftDrivetrainEncoder.innerHTML =
708 this.drivetrainPosition.leftEncoder().toString();
709
710 this.rightDrivetrainEncoder.innerHTML =
711 this.drivetrainPosition.rightEncoder().toString();
Mirabel Wang66546642024-02-10 16:37:05 -0800712 }
713
714 if (this.drivetrainCANPosition) {
715 this.falconRightFrontPosition.innerHTML =
716 this.drivetrainCANPosition.talonfxs(0).position().toString();
717
718 this.falconRightBackPosition.innerHTML =
719 this.drivetrainCANPosition.talonfxs(1).position().toString();
720
721 this.falconLeftFrontPosition.innerHTML =
722 this.drivetrainCANPosition.talonfxs(2).position().toString();
723
724 this.falconLeftBackPosition.innerHTML =
725 this.drivetrainCANPosition.talonfxs(3).position().toString();
726 }
727
728 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
729 this.drawRobot(
730 this.drivetrainStatus.trajectoryLogging().x(),
731 this.drivetrainStatus.trajectoryLogging().y(),
732 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
733 false);
734 }
735
736 if (this.localizerOutput) {
737 if (!this.localizerOutput.zeroed()) {
738 this.setZeroing(this.x);
739 this.setZeroing(this.y);
740 this.setZeroing(this.theta);
741 } else {
742 this.setValue(this.x, this.localizerOutput.x());
743 this.setValue(this.y, this.localizerOutput.y());
744 this.setValue(this.theta, this.localizerOutput.theta());
745 }
746
747 this.drawRobot(
748 this.localizerOutput.x(), this.localizerOutput.y(),
749 this.localizerOutput.theta());
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800750
751 this.imagesAcceptedCounter.innerHTML =
752 this.localizerOutput.imageAcceptedCount().toString();
753 }
754
755 for (const [time, value] of this.localizerImageMatches) {
756 const age = now - time;
757 const kRemovalAge = 1.0;
758 if (age > kRemovalAge) {
759 this.localizerImageMatches.delete(time);
760 continue;
761 }
762 const kMaxImageAlpha = 0.5;
763 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
764 for (let i = 0; i < value.targetsLength(); i++) {
765 const imageDebug = value.targets(i);
766 const x = imageDebug.impliedRobotX();
767 const y = imageDebug.impliedRobotY();
768 const theta = imageDebug.impliedRobotTheta();
769 const cameraX = imageDebug.cameraX();
770 const cameraY = imageDebug.cameraY();
771 const cameraTheta = imageDebug.cameraTheta();
772 const accepted = imageDebug.accepted();
773 // Make camera readings fade over time.
774 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
775 const dashed = false;
776 const cameraRgb = CAMERA_COLORS[imageDebug.camera()];
777 const cameraRgba = cameraRgb + alpha;
778 this.drawRobot(x, y, theta, cameraRgba, dashed);
779 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
780 }
Mirabel Wang66546642024-02-10 16:37:05 -0800781 }
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800782
Niko Sohmers2d108762024-02-02 20:21:14 -0800783 window.requestAnimationFrame(() => this.draw());
784 }
785
786 reset(): void {
787 const ctx = this.canvas.getContext('2d');
788 // Empty space from the canvas boundary to the image
789 const IMAGE_PADDING = 10;
790 ctx.setTransform(1, 0, 0, 1, 0, 0);
791 const size = window.innerHeight * 0.9;
792 ctx.canvas.height = size;
793 const width = size / 2 + 20;
794 ctx.canvas.width = width;
795 ctx.clearRect(0, 0, size, width);
796
797 // Translate to center of display.
798 ctx.translate(width / 2, size / 2);
799 // Coordinate system is:
800 // x -> forward.
801 // y -> to the left.
802 ctx.rotate(-Math.PI / 2);
803 ctx.scale(1, -1);
804
805 const M_TO_PX = (size - IMAGE_PADDING) / FIELD_LENGTH;
806 ctx.scale(M_TO_PX, M_TO_PX);
807 ctx.lineWidth = 1 / M_TO_PX;
Niko Sohmers3860f8a2024-01-12 21:05:19 -0800808 }
809}