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