blob: 72a68d8eb52270e01d0aa5fb16cd3a1057b10e62 [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 Sohmers7e217982024-03-24 15:38:26 -07009import {Position as SuperstructurePosition} from '../control_loops/superstructure/superstructure_position_generated'
Niko Sohmersed83b6b2024-03-02 20:05:19 -080010import {SuperstructureState, IntakeRollerStatus, CatapultState, TransferRollerStatus, ExtendRollerStatus, ExtendStatus, NoteStatus, Status as SuperstructureStatus} from '../control_loops/superstructure/superstructure_status_generated'
Niko Sohmers3860f8a2024-01-12 21:05:19 -080011import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated'
12import {TargetMap} from '../../frc971/vision/target_map_generated'
Niko Sohmersf1b9eb92024-03-03 15:30:07 -080013import {RejectionReason} from '../localizer/status_generated'
14import {TargetEstimateDebug, Visualization} from '../localizer/visualization_generated'
Niko Sohmers3860f8a2024-01-12 21:05:19 -080015
16
17import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
18
19// (0,0) is field center, +X is toward red DS
20const FIELD_SIDE_Y = FIELD_WIDTH / 2;
21const FIELD_EDGE_X = FIELD_LENGTH / 2;
22
Niko Sohmers2d108762024-02-02 20:21:14 -080023const ROBOT_WIDTH = 29 * IN_TO_M;
Niko Sohmers3860f8a2024-01-12 21:05:19 -080024const ROBOT_LENGTH = 32 * IN_TO_M;
25
Niko Sohmersf1b9eb92024-03-03 15:30:07 -080026const CAMERA_COLORS = ['#ff00ff', '#ffff00', '#00ffff', '#ffa500'];
27const CAMERAS = ['/orin1/camera0', '/orin1/camera1', '/imu/camera0', '/imu/camera1'];
28
Niko Sohmers3860f8a2024-01-12 21:05:19 -080029export class FieldHandler {
Niko Sohmers2d108762024-02-02 20:21:14 -080030 private canvas = document.createElement('canvas');
Mirabel Wang66546642024-02-10 16:37:05 -080031 private localizerOutput: LocalizerOutput|null = null;
32 private drivetrainStatus: DrivetrainStatus|null = null;
33 private drivetrainPosition: DrivetrainPosition|null = null;
34 private drivetrainCANPosition: DrivetrainCANPosition|null = null;
Filip Kujawa1a2e9e02024-02-24 18:30:29 -080035 private superstructureStatus: SuperstructureStatus|null = null;
Niko Sohmers7e217982024-03-24 15:38:26 -070036 private superstructurePosition: SuperstructurePosition|null = null;
Mirabel Wang66546642024-02-10 16:37:05 -080037
Niko Sohmersf1b9eb92024-03-03 15:30:07 -080038 // Image information indexed by timestamp (seconds since the epoch), so that
39 // we can stop displaying images after a certain amount of time.
40 private localizerImageMatches = new Map<number, Visualization>();
Mirabel Wang66546642024-02-10 16:37:05 -080041 private x: HTMLElement = (document.getElementById('x') as HTMLElement);
42 private y: HTMLElement = (document.getElementById('y') as HTMLElement);
43 private theta: HTMLElement =
44 (document.getElementById('theta') as HTMLElement);
45
Niko Sohmersf1b9eb92024-03-03 15:30:07 -080046 private imagesAcceptedCounter: HTMLElement =
47 (document.getElementById('images_accepted') as HTMLElement);
48 // HTML elements for rejection reasons for individual cameras. Indices
49 // corresponding to RejectionReason enum values will be for those reasons. The
50 // final row will account for images rejected by the aprilrobotics detector
51 // instead of the localizer.
52 private rejectionReasonCells: HTMLElement[][] = [];
53 private messageBridgeDiv: HTMLElement =
54 (document.getElementById('message_bridge_status') as HTMLElement);
55 private clientStatuses = new Map<string, HTMLElement>();
56 private serverStatuses = new Map<string, HTMLElement>();
57
Niko Sohmers2d108762024-02-02 20:21:14 -080058 private fieldImage: HTMLImageElement = new Image();
Mirabel Wang66546642024-02-10 16:37:05 -080059
Filip Kujawa1a2e9e02024-02-24 18:30:29 -080060 private zeroingFaults: HTMLElement =
61 (document.getElementById('zeroing_faults') as HTMLElement);
62
63 private superstructureState: HTMLElement =
64 (document.getElementById('superstructure_state') as HTMLElement);
65
66 private intakeRollerState: HTMLElement =
67 (document.getElementById('intake_roller_state') as HTMLElement);
68 private transferRollerState: HTMLElement =
69 (document.getElementById('transfer_roller_state') as HTMLElement);
70 private extendState: HTMLElement =
71 (document.getElementById('extend_state') as HTMLElement);
72 private extendRollerState: HTMLElement =
73 (document.getElementById('extend_roller_state') as HTMLElement);
74 private catapultState: HTMLElement =
75 (document.getElementById('catapult_state') as HTMLElement);
Niko Sohmersed83b6b2024-03-02 20:05:19 -080076 private uncompletedNoteGoal: HTMLElement =
77 (document.getElementById('uncompleted_note_goal') as HTMLElement);
78
79 private extend_beambreak: HTMLElement =
80 (document.getElementById('extend_beambreak') as HTMLElement);
81 private catapult_beambreak: HTMLElement =
82 (document.getElementById('catapult_beambreak') as HTMLElement);
Niko Sohmers2ac60c52024-03-16 18:44:54 -070083 private transfer_beambreak: HTMLElement =
84 (document.getElementById('transfer_beambreak') as HTMLElement);
Niko Sohmersed83b6b2024-03-02 20:05:19 -080085
86 private extend_at_retracted: HTMLElement =
87 (document.getElementById('extend_at_retracted') as HTMLElement);
88 private extend_ready_for_transfer: HTMLElement =
89 (document.getElementById('extend_ready_for_transfer') as HTMLElement);
90 private extend_ready_for_catapult_transfer: HTMLElement =
91 (document.getElementById('extend_ready_for_catapult_transfer') as HTMLElement);
92 private turret_ready_for_load: HTMLElement =
93 (document.getElementById('turret_ready_for_load') as HTMLElement);
94 private altitude_ready_for_load: HTMLElement =
95 (document.getElementById('altitude_ready_for_load') as HTMLElement);
96
Niko Sohmerscc3aa452024-03-03 17:20:04 -080097 private turret_in_range: HTMLElement =
98 (document.getElementById('turret_in_range') as HTMLElement);
99 private altitude_in_range: HTMLElement =
100 (document.getElementById('altitude_in_range') as HTMLElement);
101 private altitude_above_min_angle: HTMLElement =
102 (document.getElementById('altitude_above_min_angle') as HTMLElement);
103
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800104
105 private intakePivot: HTMLElement =
106 (document.getElementById('intake_pivot') as HTMLElement);
107 private intakePivotAbs: HTMLElement =
108 (document.getElementById('intake_pivot_abs') as HTMLElement);
109
Niko Sohmers7e217982024-03-24 15:38:26 -0700110 private climberPot: HTMLElement =
111 (document.getElementById('climber_pot') as HTMLElement);
112
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800113 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(
Niko Sohmers7e217982024-03-24 15:38:26 -0700267 '/superstructure', "y2024.control_loops.superstructure.Positon",
268 (data) => {
269 this.handleSuperstructurePosition(data)
270 });
271 this.connection.addHandler(
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800272 '/aos', 'aos.message_bridge.ServerStatistics',
273 (data) => {this.handleServerStatistics(data)});
274 this.connection.addHandler(
275 '/aos', 'aos.message_bridge.ClientStatistics',
276 (data) => {this.handleClientStatistics(data)});
Mirabel Wang66546642024-02-10 16:37:05 -0800277 });
278 }
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800279 private handleLocalizerDebug(camera: number, data: Uint8Array): void {
280 const now = Date.now() / 1000.0;
281
282 const fbBuffer = new ByteBuffer(data);
283 this.localizerImageMatches.set(
284 now, Visualization.getRootAsVisualization(fbBuffer));
285
286 const debug = this.localizerImageMatches.get(now);
287
288 if (debug.statistics()) {
289 if ((debug.statistics().rejectionReasonsLength() + 1) ==
290 this.rejectionReasonCells.length) {
291 for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
292 ++ii) {
293 this.rejectionReasonCells[ii][camera].innerHTML =
294 debug.statistics().rejectionReasons(ii).count().toString();
295 }
296 } else {
297 console.error('Unexpected number of rejection reasons in counter.');
298 }
299 }
300 }
301
302 private handleCameraTargetMap(pi: string, data: Uint8Array): void {
303 const fbBuffer = new ByteBuffer(data);
304 const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
305 this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
306 .innerHTML = targetMap.rejections().toString();
307 }
Mirabel Wang66546642024-02-10 16:37:05 -0800308
309 private handleDrivetrainStatus(data: Uint8Array): void {
310 const fbBuffer = new ByteBuffer(data);
311 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
312 }
313
314 private handleDrivetrainPosition(data: Uint8Array): void {
315 const fbBuffer = new ByteBuffer(data);
316 this.drivetrainPosition = DrivetrainPosition.getRootAsPosition(fbBuffer);
317 }
318
319 private handleDrivetrainCANPosition(data: Uint8Array): void {
320 const fbBuffer = new ByteBuffer(data);
321 this.drivetrainCANPosition = DrivetrainCANPosition.getRootAsCANPosition(fbBuffer);
322 }
323
324 private handleLocalizerOutput(data: Uint8Array): void {
325 const fbBuffer = new ByteBuffer(data);
326 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
Niko Sohmers2d108762024-02-02 20:21:14 -0800327 }
328
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800329 private handleSuperstructureStatus(data: Uint8Array): void {
330 const fbBuffer = new ByteBuffer(data);
331 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
332 }
333
Niko Sohmers7e217982024-03-24 15:38:26 -0700334 private handleSuperstructurePosition(data: Uint8Array): void {
335 const fbBuffer = new ByteBuffer(data);
336 this.superstructurePosition = SuperstructurePosition.getRootAsPosition(fbBuffer);
337 }
338
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800339 private populateNodeConnections(nodeName: string): void {
340 const row = document.createElement('div');
341 this.messageBridgeDiv.appendChild(row);
342 const nodeDiv = document.createElement('div');
343 nodeDiv.innerHTML = nodeName;
344 row.appendChild(nodeDiv);
345 const clientDiv = document.createElement('div');
346 clientDiv.innerHTML = 'N/A';
347 row.appendChild(clientDiv);
348 const serverDiv = document.createElement('div');
349 serverDiv.innerHTML = 'N/A';
350 row.appendChild(serverDiv);
351 this.serverStatuses.set(nodeName, serverDiv);
352 this.clientStatuses.set(nodeName, clientDiv);
353 }
354
355 private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
356 void {
357 if (state === ConnectionState.CONNECTED) {
358 element.innerHTML = ConnectionState[state];
359 element.classList.remove('faulted');
360 element.classList.add('connected');
361 } else {
362 element.innerHTML = ConnectionState[state];
363 element.classList.remove('connected');
364 element.classList.add('faulted');
365 }
366 }
367
368 private handleServerStatistics(data: Uint8Array): void {
369 const fbBuffer = new ByteBuffer(data);
370 const serverStatistics =
371 ServerStatistics.getRootAsServerStatistics(fbBuffer);
372
373 for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
374 const connection = serverStatistics.connections(ii);
375 const nodeName = connection.node().name();
376 if (!this.serverStatuses.has(nodeName)) {
377 this.populateNodeConnections(nodeName);
378 }
379 this.setCurrentNodeState(
380 this.serverStatuses.get(nodeName), connection.state());
381 }
382 }
383
384 private handleClientStatistics(data: Uint8Array): void {
385 const fbBuffer = new ByteBuffer(data);
386 const clientStatistics =
387 ClientStatistics.getRootAsClientStatistics(fbBuffer);
388
389 for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
390 const connection = clientStatistics.connections(ii);
391 const nodeName = connection.node().name();
392 if (!this.clientStatuses.has(nodeName)) {
393 this.populateNodeConnections(nodeName);
394 }
395 this.setCurrentNodeState(
396 this.clientStatuses.get(nodeName), connection.state());
397 }
398 }
399
Niko Sohmers2d108762024-02-02 20:21:14 -0800400 drawField(): void {
401 const ctx = this.canvas.getContext('2d');
402 ctx.save();
403 ctx.scale(1.0, -1.0);
404 ctx.drawImage(
405 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
406 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
407 ctx.restore();
408 }
409
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800410 drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
411 void {
412 const ctx = this.canvas.getContext('2d');
413 ctx.save();
414 ctx.translate(x, y);
415 ctx.rotate(theta);
416 ctx.strokeStyle = color;
417 ctx.beginPath();
418 ctx.moveTo(0.5, 0.5);
419 ctx.lineTo(0, 0);
420 ctx.lineTo(0.5, -0.5);
421 ctx.stroke();
422 ctx.beginPath();
423 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
424 ctx.stroke();
425 ctx.restore();
426 }
427
Mirabel Wang66546642024-02-10 16:37:05 -0800428 drawRobot(
429 x: number, y: number, theta: number, color: string = 'blue',
430 dashed: boolean = false): void {
431 const ctx = this.canvas.getContext('2d');
432 ctx.save();
433 ctx.translate(x, y);
434 ctx.rotate(theta);
435 ctx.strokeStyle = color;
436 ctx.lineWidth = ROBOT_WIDTH / 10.0;
437 if (dashed) {
438 ctx.setLineDash([0.05, 0.05]);
439 } else {
440 // Empty array = solid line.
441 ctx.setLineDash([]);
442 }
443 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
444 ctx.stroke();
445
446 // Draw line indicating which direction is forwards on the robot.
447 ctx.beginPath();
448 ctx.moveTo(0, 0);
449 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
450 ctx.stroke();
451
452 ctx.restore();
453}
454
455 setZeroing(div: HTMLElement): void {
456 div.innerHTML = 'zeroing';
457 div.classList.remove('faulted');
458 div.classList.add('zeroing');
459 div.classList.remove('near');
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800460 }
461
462 setEstopped(div: HTMLElement): void {
463 div.innerHTML = 'estopped';
464 div.classList.add('faulted');
465 div.classList.remove('zeroing');
466 div.classList.remove('near');
467 }
468
469 setTargetValue(
470 div: HTMLElement, target: number, val: number, tolerance: number): void {
471 div.innerHTML = val.toFixed(4);
472 div.classList.remove('faulted');
473 div.classList.remove('zeroing');
474 if (Math.abs(target - val) < tolerance) {
475 div.classList.add('near');
476 } else {
477 div.classList.remove('near');
478 }
479 }
Mirabel Wang66546642024-02-10 16:37:05 -0800480
481 setValue(div: HTMLElement, val: number): void {
482 div.innerHTML = val.toFixed(4);
483 div.classList.remove('faulted');
484 div.classList.remove('zeroing');
485 div.classList.remove('near');
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800486 }
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800487
488 setBoolean(div: HTMLElement, triggered: boolean): void {
489 div.innerHTML = ((triggered) ? "TRUE" : "FALSE")
Niko Sohmerse92516c2024-03-23 18:40:30 -0700490 div.className = '';
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800491 if (triggered) {
Niko Sohmerse92516c2024-03-23 18:40:30 -0700492 div.classList.add('lightgreen');
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800493 } else {
Niko Sohmerse92516c2024-03-23 18:40:30 -0700494 div.classList.add('lightcoral');
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800495 }
496 }
497
Niko Sohmers2d108762024-02-02 20:21:14 -0800498 draw(): void {
499 this.reset();
500 this.drawField();
501
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800502 // Draw the matches with debugging information from the localizer.
503 const now = Date.now() / 1000.0;
504
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800505 if (this.superstructureStatus) {
506 this.superstructureState.innerHTML =
507 SuperstructureState[this.superstructureStatus.state()];
Mirabel Wang66546642024-02-10 16:37:05 -0800508
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800509 this.intakeRollerState.innerHTML =
510 IntakeRollerStatus[this.superstructureStatus.intakeRoller()];
511 this.transferRollerState.innerHTML =
512 TransferRollerStatus[this.superstructureStatus.transferRoller()];
513 this.extendState.innerHTML =
514 ExtendStatus[this.superstructureStatus.extendStatus()];
515 this.extendRollerState.innerHTML =
516 ExtendRollerStatus[this.superstructureStatus.extendRoller()];
517 this.catapultState.innerHTML =
518 CatapultState[this.superstructureStatus.shooter().catapultState()];
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800519 this.uncompletedNoteGoal.innerHTML =
520 NoteStatus[this.superstructureStatus.uncompletedNoteGoal()];
521
522 this.setBoolean(this.extend_beambreak, this.superstructureStatus.extendBeambreak());
523
524 this.setBoolean(this.catapult_beambreak, this.superstructureStatus.catapultBeambreak());
525
Niko Sohmers2ac60c52024-03-16 18:44:54 -0700526 this.setBoolean(this.transfer_beambreak, this.superstructureStatus.transferBeambreak());
527
Niko Sohmersed83b6b2024-03-02 20:05:19 -0800528 this.setBoolean(this.extend_ready_for_transfer, this.superstructureStatus.extendReadyForTransfer());
529
530 this.setBoolean(this.extend_at_retracted, this.superstructureStatus.extendAtRetracted());
531
532 this.setBoolean(this.turret_ready_for_load, this.superstructureStatus.turretReadyForLoad());
533
534 this.setBoolean(this.altitude_ready_for_load, this.superstructureStatus.altitudeReadyForLoad());
535
536 this.setBoolean(this.extend_ready_for_catapult_transfer, this.superstructureStatus.extendReadyForCatapultTransfer());
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800537
Niko Sohmerscc3aa452024-03-03 17:20:04 -0800538 this.setBoolean(this.turret_in_range, this.superstructureStatus.shooter().turretInRange())
539
540 this.setBoolean(this.altitude_in_range, this.superstructureStatus.shooter().altitudeInRange())
541
542 this.setBoolean(this.altitude_above_min_angle, this.superstructureStatus.shooter().altitudeAboveMinAngle())
543
Niko Sohmers7e217982024-03-24 15:38:26 -0700544 if (this.superstructurePosition) {
545 this.climberPot.innerHTML = this.superstructurePosition.climber().encoder().toString();
546 }
547
James Kuszmaul5d1b75e2024-02-25 14:37:00 -0800548 if (this.superstructureStatus.shooter() &&
549 this.superstructureStatus.shooter().aimer()) {
550 this.turret_position.innerHTML = this.superstructureStatus.shooter()
551 .aimer()
552 .turretPosition()
553 .toString();
554 this.turret_velocity.innerHTML = this.superstructureStatus.shooter()
555 .aimer()
556 .turretVelocity()
557 .toString();
558 this.target_distance.innerHTML = this.superstructureStatus.shooter()
559 .aimer()
560 .targetDistance()
561 .toString();
562 this.shot_distance.innerHTML = this.superstructureStatus.shooter()
563 .aimer()
564 .shotDistance()
565 .toString();
566 }
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800567
568 if (!this.superstructureStatus.intakePivot() ||
569 !this.superstructureStatus.intakePivot().zeroed()) {
570 this.setZeroing(this.intakePivot);
571 } else if (this.superstructureStatus.intakePivot().estopped()) {
572 this.setEstopped(this.intakePivot);
573 } else {
574 this.setTargetValue(
575 this.intakePivot,
576 this.superstructureStatus.intakePivot().unprofiledGoalPosition(),
577 this.superstructureStatus.intakePivot().estimatorState().position(),
578 1e-3);
579 }
580
581 this.intakePivotAbs.innerHTML = this.superstructureStatus.intakePivot().estimatorState().absolutePosition().toString();
582
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800583 if (!this.superstructureStatus.extend() ||
584 !this.superstructureStatus.extend().zeroed()) {
585 this.setZeroing(this.extend);
586 } else if (this.superstructureStatus.extend().estopped()) {
587 this.setEstopped(this.extend);
588 } else {
589 this.setTargetValue(
James Kuszmaul5d1b75e2024-02-25 14:37:00 -0800590 this.extend,
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800591 this.superstructureStatus.extend().unprofiledGoalPosition(),
592 this.superstructureStatus.extend().estimatorState().position(),
593 1e-3);
594 }
595
596 this.extendAbs.innerHTML = this.superstructureStatus.extend().estimatorState().absolutePosition().toString();
597 this.extendPot.innerHTML = this.superstructureStatus.extend().estimatorState().potPosition().toString();
598
599 if (!this.superstructureStatus.shooter().turret() ||
600 !this.superstructureStatus.shooter().turret().zeroed()) {
601 this.setZeroing(this.turret);
602 } else if (this.superstructureStatus.shooter().turret().estopped()) {
603 this.setEstopped(this.turret);
604 } else {
605 this.setTargetValue(
606 this.turret,
607 this.superstructureStatus.shooter().turret().unprofiledGoalPosition(),
608 this.superstructureStatus.shooter().turret().estimatorState().position(),
609 1e-3);
610 }
611
612 this.turretAbs.innerHTML = this.superstructureStatus.shooter().turret().estimatorState().absolutePosition().toString();
613 this.turretPot.innerHTML = this.superstructureStatus.shooter().turret().estimatorState().potPosition().toString();
614
615 if (!this.superstructureStatus.shooter().catapult() ||
616 !this.superstructureStatus.shooter().catapult().zeroed()) {
617 this.setZeroing(this.catapult);
618 } else if (this.superstructureStatus.shooter().catapult().estopped()) {
619 this.setEstopped(this.catapult);
620 } else {
621 this.setTargetValue(
622 this.catapult,
623 this.superstructureStatus.shooter().catapult().unprofiledGoalPosition(),
624 this.superstructureStatus.shooter().catapult().estimatorState().position(),
625 1e-3);
626 }
627
628 this.catapultAbs.innerHTML = this.superstructureStatus.shooter().catapult().estimatorState().absolutePosition().toString();
629 this.catapultPot.innerHTML = this.superstructureStatus.shooter().catapult().estimatorState().potPosition().toString();
630
631 if (!this.superstructureStatus.shooter().altitude() ||
632 !this.superstructureStatus.shooter().altitude().zeroed()) {
633 this.setZeroing(this.altitude);
634 } else if (this.superstructureStatus.shooter().altitude().estopped()) {
635 this.setEstopped(this.altitude);
636 } else {
637 this.setTargetValue(
638 this.altitude,
639 this.superstructureStatus.shooter().altitude().unprofiledGoalPosition(),
640 this.superstructureStatus.shooter().altitude().estimatorState().position(),
641 1e-3);
642 }
643
644 this.altitudeAbs.innerHTML = this.superstructureStatus.shooter().altitude().estimatorState().absolutePosition().toString();
645 this.altitudePot.innerHTML = this.superstructureStatus.shooter().altitude().estimatorState().potPosition().toString();
646
647 let zeroingErrors: string = 'Intake Pivot Errors:' +
648 '<br/>';
649 for (let i = 0; i < this.superstructureStatus.intakePivot()
650 .estimatorState()
651 .errorsLength();
652 i++) {
653 zeroingErrors += ZeroingError[this.superstructureStatus.intakePivot()
654 .estimatorState()
655 .errors(i)] +
656 '<br/>';
657 }
658 zeroingErrors += '<br/>' +
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800659 'Extend Errors:' +
660 '<br/>';
661 for (let i = 0; i < this.superstructureStatus.extend().estimatorState().errorsLength();
662 i++) {
663 zeroingErrors += ZeroingError[this.superstructureStatus.extend().estimatorState().errors(i)] +
664 '<br/>';
665 }
666 zeroingErrors += '<br/>' +
667 'Turret Errors:' +
668 '<br/>';
669 for (let i = 0; i < this.superstructureStatus.shooter().turret().estimatorState().errorsLength();
670 i++) {
671 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().turret().estimatorState().errors(i)] +
672 '<br/>';
673 }
674 zeroingErrors += '<br/>' +
675 'Catapult Errors:' +
676 '<br/>';
677 for (let i = 0; i < this.superstructureStatus.shooter().catapult().estimatorState().errorsLength();
678 i++) {
679 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().catapult().estimatorState().errors(i)] +
680 '<br/>';
681 }
682 zeroingErrors += '<br/>' +
683 'Altitude Errors:' +
684 '<br/>';
685 for (let i = 0; i < this.superstructureStatus.shooter().altitude().estimatorState().errorsLength();
686 i++) {
687 zeroingErrors += ZeroingError[this.superstructureStatus.shooter().altitude().estimatorState().errors(i)] +
688 '<br/>';
689 }
690 this.zeroingFaults.innerHTML = zeroingErrors;
691 }
692
693 if (this.drivetrainPosition) {
694 this.leftDrivetrainEncoder.innerHTML =
695 this.drivetrainPosition.leftEncoder().toString();
696
697 this.rightDrivetrainEncoder.innerHTML =
698 this.drivetrainPosition.rightEncoder().toString();
Mirabel Wang66546642024-02-10 16:37:05 -0800699 }
700
701 if (this.drivetrainCANPosition) {
702 this.falconRightFrontPosition.innerHTML =
703 this.drivetrainCANPosition.talonfxs(0).position().toString();
704
705 this.falconRightBackPosition.innerHTML =
706 this.drivetrainCANPosition.talonfxs(1).position().toString();
707
708 this.falconLeftFrontPosition.innerHTML =
709 this.drivetrainCANPosition.talonfxs(2).position().toString();
710
711 this.falconLeftBackPosition.innerHTML =
712 this.drivetrainCANPosition.talonfxs(3).position().toString();
713 }
714
715 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
716 this.drawRobot(
717 this.drivetrainStatus.trajectoryLogging().x(),
718 this.drivetrainStatus.trajectoryLogging().y(),
719 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
720 false);
721 }
722
723 if (this.localizerOutput) {
724 if (!this.localizerOutput.zeroed()) {
725 this.setZeroing(this.x);
726 this.setZeroing(this.y);
727 this.setZeroing(this.theta);
728 } else {
729 this.setValue(this.x, this.localizerOutput.x());
730 this.setValue(this.y, this.localizerOutput.y());
731 this.setValue(this.theta, this.localizerOutput.theta());
732 }
733
734 this.drawRobot(
735 this.localizerOutput.x(), this.localizerOutput.y(),
736 this.localizerOutput.theta());
Niko Sohmersf1b9eb92024-03-03 15:30:07 -0800737
738 this.imagesAcceptedCounter.innerHTML =
739 this.localizerOutput.imageAcceptedCount().toString();
740 }
741
742 for (const [time, value] of this.localizerImageMatches) {
743 const age = now - time;
744 const kRemovalAge = 1.0;
745 if (age > kRemovalAge) {
746 this.localizerImageMatches.delete(time);
747 continue;
748 }
749 const kMaxImageAlpha = 0.5;
750 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
751 for (let i = 0; i < value.targetsLength(); i++) {
752 const imageDebug = value.targets(i);
753 const x = imageDebug.impliedRobotX();
754 const y = imageDebug.impliedRobotY();
755 const theta = imageDebug.impliedRobotTheta();
756 const cameraX = imageDebug.cameraX();
757 const cameraY = imageDebug.cameraY();
758 const cameraTheta = imageDebug.cameraTheta();
759 const accepted = imageDebug.accepted();
760 // Make camera readings fade over time.
761 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
762 const dashed = false;
763 const cameraRgb = CAMERA_COLORS[imageDebug.camera()];
764 const cameraRgba = cameraRgb + alpha;
765 this.drawRobot(x, y, theta, cameraRgba, dashed);
766 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
767 }
Mirabel Wang66546642024-02-10 16:37:05 -0800768 }
Filip Kujawa1a2e9e02024-02-24 18:30:29 -0800769
Niko Sohmers2d108762024-02-02 20:21:14 -0800770 window.requestAnimationFrame(() => this.draw());
771 }
772
773 reset(): void {
774 const ctx = this.canvas.getContext('2d');
775 // Empty space from the canvas boundary to the image
776 const IMAGE_PADDING = 10;
777 ctx.setTransform(1, 0, 0, 1, 0, 0);
778 const size = window.innerHeight * 0.9;
779 ctx.canvas.height = size;
780 const width = size / 2 + 20;
781 ctx.canvas.width = width;
782 ctx.clearRect(0, 0, size, width);
783
784 // Translate to center of display.
785 ctx.translate(width / 2, size / 2);
786 // Coordinate system is:
787 // x -> forward.
788 // y -> to the left.
789 ctx.rotate(-Math.PI / 2);
790 ctx.scale(1, -1);
791
792 const M_TO_PX = (size - IMAGE_PADDING) / FIELD_LENGTH;
793 ctx.scale(M_TO_PX, M_TO_PX);
794 ctx.lineWidth = 1 / M_TO_PX;
Niko Sohmers3860f8a2024-01-12 21:05:19 -0800795 }
796}