blob: b478538862cc795c1f084952b7a47a7390c88774 [file] [log] [blame]
Milo Lin13906572023-03-15 20:55:22 -07001import {ByteBuffer} from 'flatbuffers'
James Kuszmaulf7b5d622023-03-11 15:14:53 -08002import {ClientStatistics} from '../../aos/network/message_bridge_client_generated'
3import {ServerStatistics, State as ConnectionState} from '../../aos/network/message_bridge_server_generated'
Milo Lin13906572023-03-15 20:55:22 -07004import {Connection} from '../../aos/network/www/proxy'
5import {ZeroingError} from '../../frc971/control_loops/control_loops_generated'
Niko Sohmers76f47562023-12-20 20:59:06 -08006import {Position as DrivetrainPosition} from '../../frc971/control_loops/drivetrain/drivetrain_position_generated'
7import {CANPosition as DrivetrainCANPosition} from '../../frc971/control_loops/drivetrain/drivetrain_can_position_generated'
Milo Lin13906572023-03-15 20:55:22 -07008import {Status as DrivetrainStatus} from '../../frc971/control_loops/drivetrain/drivetrain_status_generated'
9import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated'
10import {TargetMap} from '../../frc971/vision/target_map_generated'
James Kuszmaulf7b5d622023-03-11 15:14:53 -080011import {ArmState, ArmStatus, EndEffectorState, Status as SuperstructureStatus} from '../control_loops/superstructure/superstructure_status_generated'
Milo Lin13906572023-03-15 20:55:22 -070012import {RejectionReason} from '../localizer/status_generated'
13import {TargetEstimateDebug, Visualization} from '../localizer/visualization_generated'
James Kuszmaulf7b5d622023-03-11 15:14:53 -080014import {Class} from '../vision/game_pieces_generated'
Maxwell Hendersonad312342023-01-10 12:07:47 -080015
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
James Kuszmaulfb894572023-02-23 17:25:06 -080022const ROBOT_WIDTH = 25 * IN_TO_M;
23const ROBOT_LENGTH = 32 * IN_TO_M;
Maxwell Hendersonad312342023-01-10 12:07:47 -080024
25const PI_COLORS = ['#ff00ff', '#ffff00', '#00ffff', '#ffa500'];
James Kuszmaulfb894572023-02-23 17:25:06 -080026const PIS = ['pi1', 'pi2', 'pi3', 'pi4'];
Maxwell Hendersonad312342023-01-10 12:07:47 -080027
28export class FieldHandler {
29 private canvas = document.createElement('canvas');
James Kuszmaulfb894572023-02-23 17:25:06 -080030 private localizerOutput: LocalizerOutput|null = null;
Maxwell Hendersonad312342023-01-10 12:07:47 -080031 private drivetrainStatus: DrivetrainStatus|null = null;
Niko Sohmers76f47562023-12-20 20:59:06 -080032 private drivetrainPosition: DrivetrainPosition|null = null;
33 private drivetrainCANPosition: DrivetrainCANPosition|null = null;
Milo Line6571c02023-03-04 21:08:20 -080034 private superstructureStatus: SuperstructureStatus|null = null;
Maxwell Hendersonad312342023-01-10 12:07:47 -080035
36 // Image information indexed by timestamp (seconds since the epoch), so that
37 // we can stop displaying images after a certain amount of time.
James Kuszmaulfb894572023-02-23 17:25:06 -080038 private localizerImageMatches = new Map<number, Visualization>();
39 private x: HTMLElement = (document.getElementById('x') as HTMLElement);
Maxwell Hendersonad312342023-01-10 12:07:47 -080040 private y: HTMLElement = (document.getElementById('y') as HTMLElement);
41 private theta: HTMLElement =
42 (document.getElementById('theta') as HTMLElement);
Maxwell Hendersonad312342023-01-10 12:07:47 -080043 private imagesAcceptedCounter: HTMLElement =
44 (document.getElementById('images_accepted') as HTMLElement);
Milo Lin13906572023-03-15 20:55:22 -070045 // HTML elements for rejection reasons for individual pis. Indices
46 // corresponding to RejectionReason enum values will be for those reasons. The
47 // final row will account for images rejected by the aprilrobotics detector
48 // instead of the localizer.
James Kuszmaul4f0e4362023-03-12 20:03:59 -070049 private rejectionReasonCells: HTMLElement[][] = [];
James Kuszmaulf7b5d622023-03-11 15:14:53 -080050 private messageBridgeDiv: HTMLElement =
51 (document.getElementById('message_bridge_status') as HTMLElement);
52 private clientStatuses = new Map<string, HTMLElement>();
53 private serverStatuses = new Map<string, HTMLElement>();
Maxwell Hendersonad312342023-01-10 12:07:47 -080054 private fieldImage: HTMLImageElement = new Image();
Milo Line6571c02023-03-04 21:08:20 -080055 private endEffectorState: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080056 (document.getElementById('end_effector_state') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080057 private wrist: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080058 (document.getElementById('wrist') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080059 private armState: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080060 (document.getElementById('arm_state') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080061 private gamePiece: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080062 (document.getElementById('game_piece') as HTMLElement);
James Kuszmaulef6b7402023-04-09 14:29:38 -070063 private gamePiecePosition: HTMLElement =
64 (document.getElementById('game_piece_position') as HTMLElement);
James Kuszmaulf7b5d622023-03-11 15:14:53 -080065 private armX: HTMLElement = (document.getElementById('arm_x') as HTMLElement);
66 private armY: HTMLElement = (document.getElementById('arm_y') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080067 private circularIndex: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080068 (document.getElementById('arm_circular_index') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080069 private roll: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080070 (document.getElementById('arm_roll') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080071 private proximal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080072 (document.getElementById('arm_proximal') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080073 private distal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080074 (document.getElementById('arm_distal') as HTMLElement);
Milo Lin72fb9012023-03-10 19:53:19 -080075 private zeroingFaults: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080076 (document.getElementById('zeroing_faults') as HTMLElement);
Niko Sohmers76f47562023-12-20 20:59:06 -080077
78 private leftDrivetrainEncoder: HTMLElement =
79 (document.getElementById('left_drivetrain_encoder') as HTMLElement);
80 private rightDrivetrainEncoder: HTMLElement =
81 (document.getElementById('right_drivetrain_encoder') as HTMLElement);
82 private falconRightFrontPosition: HTMLElement =
83 (document.getElementById('falcon_right_front') as HTMLElement);
84 private falconRightBackPosition: HTMLElement =
85 (document.getElementById('falcon_right_back') as HTMLElement);
86 private falconRightUnderPosition: HTMLElement =
87 (document.getElementById('falcon_right_under') as HTMLElement);
88 private falconLeftFrontPosition: HTMLElement =
89 (document.getElementById('falcon_left_front') as HTMLElement);
90 private falconLeftBackPosition: HTMLElement =
91 (document.getElementById('falcon_left_back') as HTMLElement);
92 private falconLeftUnderPosition: HTMLElement =
93 (document.getElementById('falcon_left_under') as HTMLElement);
94
Maxwell Hendersonad312342023-01-10 12:07:47 -080095 constructor(private readonly connection: Connection) {
96 (document.getElementById('field') as HTMLElement).appendChild(this.canvas);
97
James Kuszmaulf7b5d622023-03-11 15:14:53 -080098 this.fieldImage.src = '2023.png';
James Kuszmaulfb894572023-02-23 17:25:06 -080099
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700100 // Construct a table header.
101 {
102 const row = document.createElement('div');
103 const nameCell = document.createElement('div');
Milo Lin13906572023-03-15 20:55:22 -0700104 nameCell.innerHTML = 'Rejection Reason';
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700105 row.appendChild(nameCell);
106 for (const pi of PIS) {
107 const nodeCell = document.createElement('div');
108 nodeCell.innerHTML = pi;
109 row.appendChild(nodeCell);
110 }
111 document.getElementById('vision_readouts').appendChild(row);
112 }
James Kuszmaulfb894572023-02-23 17:25:06 -0800113 for (const value in RejectionReason) {
114 // Typescript generates an iterator that produces both numbers and
115 // strings... don't do anything on the string iterations.
116 if (isNaN(Number(value))) {
117 continue;
118 }
119 const row = document.createElement('div');
120 const nameCell = document.createElement('div');
121 nameCell.innerHTML = RejectionReason[value];
122 row.appendChild(nameCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700123 this.rejectionReasonCells.push([]);
124 for (const pi of PIS) {
125 const valueCell = document.createElement('div');
126 valueCell.innerHTML = 'NA';
Milo Lin13906572023-03-15 20:55:22 -0700127 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
128 valueCell);
129 row.appendChild(valueCell);
130 }
131 document.getElementById('vision_readouts').appendChild(row);
132 }
133
134 // Add rejection reason row for aprilrobotics rejections.
135 {
136 const row = document.createElement('div');
137 const nameCell = document.createElement('div');
138 nameCell.innerHTML = 'Rejected by aprilrobotics';
139 row.appendChild(nameCell);
140 this.rejectionReasonCells.push([]);
141 for (const pi of PIS) {
142 const valueCell = document.createElement('div');
143 valueCell.innerHTML = 'NA';
144 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
145 valueCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700146 row.appendChild(valueCell);
147 }
James Kuszmaulfb894572023-02-23 17:25:06 -0800148 document.getElementById('vision_readouts').appendChild(row);
149 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800150
151 for (let ii = 0; ii < PI_COLORS.length; ++ii) {
152 const legendEntry = document.createElement('div');
153 legendEntry.style.color = PI_COLORS[ii];
154 legendEntry.innerHTML = 'PI' + (ii + 1).toString()
155 document.getElementById('legend').appendChild(legendEntry);
156 }
157
158 this.connection.addConfigHandler(() => {
159 // Visualization message is reliable so that we can see *all* the vision
160 // matches.
James Kuszmaulfb894572023-02-23 17:25:06 -0800161 for (const pi in PIS) {
162 this.connection.addReliableHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800163 '/' + PIS[pi] + '/camera', 'y2023.localizer.Visualization',
James Kuszmaulfb894572023-02-23 17:25:06 -0800164 (data) => {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700165 this.handleLocalizerDebug(Number(pi), data);
James Kuszmaulfb894572023-02-23 17:25:06 -0800166 });
167 }
Milo Lin13906572023-03-15 20:55:22 -0700168 for (const pi in PIS) {
169 // Make unreliable to reduce network spam.
170 this.connection.addHandler(
171 '/' + PIS[pi] + '/camera', 'frc971.vision.TargetMap', (data) => {
172 this.handlePiTargetMap(pi, data);
173 });
174 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800175 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800176 '/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800177 this.handleDrivetrainStatus(data);
178 });
179 this.connection.addHandler(
Niko Sohmers76f47562023-12-20 20:59:06 -0800180 '/drivetrain', 'frc971.control_loops.drivetrain.Position', (data) => {
181 this.handleDrivetrainPosition(data);
182 });
183 this.connection.addHandler(
184 '/drivetrain', 'y2023.control_loops.drivetrain.CANPosition', (data) => {
185 this.handleDrivetrainCANPosition(data);
186 });
187 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800188 '/localizer', 'frc971.controls.LocalizerOutput', (data) => {
James Kuszmaulfb894572023-02-23 17:25:06 -0800189 this.handleLocalizerOutput(data);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800190 });
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800191 this.connection.addHandler(
192 '/superstructure', 'y2023.control_loops.superstructure.Status',
193 (data) => {this.handleSuperstructureStatus(data)});
194 this.connection.addHandler(
195 '/aos', 'aos.message_bridge.ServerStatistics',
196 (data) => {this.handleServerStatistics(data)});
197 this.connection.addHandler(
198 '/aos', 'aos.message_bridge.ClientStatistics',
199 (data) => {this.handleClientStatistics(data)});
Maxwell Hendersonad312342023-01-10 12:07:47 -0800200 });
201 }
202
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700203 private handleLocalizerDebug(pi: number, data: Uint8Array): void {
James Kuszmaulfb894572023-02-23 17:25:06 -0800204 const now = Date.now() / 1000.0;
205
206 const fbBuffer = new ByteBuffer(data);
207 this.localizerImageMatches.set(
208 now, Visualization.getRootAsVisualization(fbBuffer));
209
210 const debug = this.localizerImageMatches.get(now);
211
212 if (debug.statistics()) {
Milo Lin13906572023-03-15 20:55:22 -0700213 if ((debug.statistics().rejectionReasonsLength() + 1) ==
James Kuszmaulfb894572023-02-23 17:25:06 -0800214 this.rejectionReasonCells.length) {
215 for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
216 ++ii) {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700217 this.rejectionReasonCells[ii][pi].innerHTML =
James Kuszmaulfb894572023-02-23 17:25:06 -0800218 debug.statistics().rejectionReasons(ii).count().toString();
219 }
220 } else {
221 console.error('Unexpected number of rejection reasons in counter.');
222 }
223 }
224 }
225
Milo Lin13906572023-03-15 20:55:22 -0700226 private handlePiTargetMap(pi: string, data: Uint8Array): void {
227 const fbBuffer = new ByteBuffer(data);
228 const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
229 this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
230 .innerHTML = targetMap.rejections().toString();
231 }
232
James Kuszmaulfb894572023-02-23 17:25:06 -0800233 private handleLocalizerOutput(data: Uint8Array): void {
234 const fbBuffer = new ByteBuffer(data);
235 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
236 }
237
Maxwell Hendersonad312342023-01-10 12:07:47 -0800238 private handleDrivetrainStatus(data: Uint8Array): void {
239 const fbBuffer = new ByteBuffer(data);
240 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
241 }
242
Niko Sohmers76f47562023-12-20 20:59:06 -0800243 private handleDrivetrainPosition(data: Uint8Array): void {
244 const fbBuffer = new ByteBuffer(data);
245 this.drivetrainPosition = DrivetrainPosition.getRootAsPosition(fbBuffer);
246 }
247
248 private handleDrivetrainCANPosition(data: Uint8Array): void {
249 const fbBuffer = new ByteBuffer(data);
250 this.drivetrainCANPosition = DrivetrainCANPosition.getRootAsCANPosition(fbBuffer);
251 }
252
Milo Line6571c02023-03-04 21:08:20 -0800253 private handleSuperstructureStatus(data: Uint8Array): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800254 const fbBuffer = new ByteBuffer(data);
255 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
256 }
257
258 private populateNodeConnections(nodeName: string): void {
259 const row = document.createElement('div');
260 this.messageBridgeDiv.appendChild(row);
261 const nodeDiv = document.createElement('div');
262 nodeDiv.innerHTML = nodeName;
263 row.appendChild(nodeDiv);
264 const clientDiv = document.createElement('div');
265 clientDiv.innerHTML = 'N/A';
266 row.appendChild(clientDiv);
267 const serverDiv = document.createElement('div');
268 serverDiv.innerHTML = 'N/A';
269 row.appendChild(serverDiv);
270 this.serverStatuses.set(nodeName, serverDiv);
271 this.clientStatuses.set(nodeName, clientDiv);
272 }
273
274 private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
275 void {
276 if (state === ConnectionState.CONNECTED) {
277 element.innerHTML = ConnectionState[state];
278 element.classList.remove('faulted');
279 element.classList.add('connected');
280 } else {
281 element.innerHTML = ConnectionState[state];
282 element.classList.remove('connected');
283 element.classList.add('faulted');
284 }
285 }
286
287 private handleServerStatistics(data: Uint8Array): void {
288 const fbBuffer = new ByteBuffer(data);
289 const serverStatistics =
290 ServerStatistics.getRootAsServerStatistics(fbBuffer);
291
292 for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
293 const connection = serverStatistics.connections(ii);
294 const nodeName = connection.node().name();
295 if (!this.serverStatuses.has(nodeName)) {
296 this.populateNodeConnections(nodeName);
297 }
298 this.setCurrentNodeState(
299 this.serverStatuses.get(nodeName), connection.state());
300 }
301 }
302
303 private handleClientStatistics(data: Uint8Array): void {
304 const fbBuffer = new ByteBuffer(data);
305 const clientStatistics =
306 ClientStatistics.getRootAsClientStatistics(fbBuffer);
307
308 for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
309 const connection = clientStatistics.connections(ii);
310 const nodeName = connection.node().name();
311 if (!this.clientStatuses.has(nodeName)) {
312 this.populateNodeConnections(nodeName);
313 }
314 this.setCurrentNodeState(
315 this.clientStatuses.get(nodeName), connection.state());
316 }
Milo Line6571c02023-03-04 21:08:20 -0800317 }
318
Maxwell Hendersonad312342023-01-10 12:07:47 -0800319 drawField(): void {
320 const ctx = this.canvas.getContext('2d');
321 ctx.save();
James Kuszmaulfb894572023-02-23 17:25:06 -0800322 ctx.scale(1.0, -1.0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800323 ctx.drawImage(
324 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
325 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
326 ctx.restore();
327 }
328
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800329 drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
330 void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800331 const ctx = this.canvas.getContext('2d');
332 ctx.save();
333 ctx.translate(x, y);
334 ctx.rotate(theta);
335 ctx.strokeStyle = color;
336 ctx.beginPath();
337 ctx.moveTo(0.5, 0.5);
338 ctx.lineTo(0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800339 ctx.lineTo(0.5, -0.5);
340 ctx.stroke();
341 ctx.beginPath();
342 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
343 ctx.stroke();
344 ctx.restore();
345 }
346
347 drawRobot(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800348 x: number, y: number, theta: number, color: string = 'blue',
349 dashed: boolean = false): void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800350 const ctx = this.canvas.getContext('2d');
351 ctx.save();
352 ctx.translate(x, y);
353 ctx.rotate(theta);
354 ctx.strokeStyle = color;
355 ctx.lineWidth = ROBOT_WIDTH / 10.0;
356 if (dashed) {
357 ctx.setLineDash([0.05, 0.05]);
358 } else {
359 // Empty array = solid line.
360 ctx.setLineDash([]);
361 }
362 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
363 ctx.stroke();
364
365 // Draw line indicating which direction is forwards on the robot.
366 ctx.beginPath();
367 ctx.moveTo(0, 0);
James Kuszmaulfb894572023-02-23 17:25:06 -0800368 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800369 ctx.stroke();
370
371 ctx.restore();
372 }
373
374 setZeroing(div: HTMLElement): void {
375 div.innerHTML = 'zeroing';
376 div.classList.remove('faulted');
377 div.classList.add('zeroing');
378 div.classList.remove('near');
379 }
380
Milo Line6571c02023-03-04 21:08:20 -0800381 setEstopped(div: HTMLElement): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800382 div.innerHTML = 'estopped';
383 div.classList.add('faulted');
384 div.classList.remove('zeroing');
385 div.classList.remove('near');
Milo Line6571c02023-03-04 21:08:20 -0800386 }
387
388 setTargetValue(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800389 div: HTMLElement, target: number, val: number, tolerance: number): void {
390 div.innerHTML = val.toFixed(4);
391 div.classList.remove('faulted');
392 div.classList.remove('zeroing');
393 if (Math.abs(target - val) < tolerance) {
394 div.classList.add('near');
395 } else {
396 div.classList.remove('near');
397 }
Milo Line6571c02023-03-04 21:08:20 -0800398 }
399
Maxwell Hendersonad312342023-01-10 12:07:47 -0800400 setValue(div: HTMLElement, val: number): void {
401 div.innerHTML = val.toFixed(4);
402 div.classList.remove('faulted');
403 div.classList.remove('zeroing');
404 div.classList.remove('near');
405 }
406
407 draw(): void {
408 this.reset();
409 this.drawField();
410
411 // Draw the matches with debugging information from the localizer.
412 const now = Date.now() / 1000.0;
James Kuszmaulfb894572023-02-23 17:25:06 -0800413
Milo Line6571c02023-03-04 21:08:20 -0800414 if (this.superstructureStatus) {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800415 this.endEffectorState.innerHTML =
416 EndEffectorState[this.superstructureStatus.endEffectorState()];
417 if (!this.superstructureStatus.wrist() ||
418 !this.superstructureStatus.wrist().zeroed()) {
419 this.setZeroing(this.wrist);
420 } else if (this.superstructureStatus.wrist().estopped()) {
421 this.setEstopped(this.wrist);
422 } else {
423 this.setTargetValue(
424 this.wrist,
425 this.superstructureStatus.wrist().unprofiledGoalPosition(),
426 this.superstructureStatus.wrist().estimatorState().position(),
427 1e-3);
428 }
429 this.armState.innerHTML =
430 ArmState[this.superstructureStatus.arm().state()];
431 this.gamePiece.innerHTML = Class[this.superstructureStatus.gamePiece()];
James Kuszmaulef6b7402023-04-09 14:29:38 -0700432 this.gamePiecePosition.innerHTML =
433 this.superstructureStatus.gamePiecePosition().toFixed(4);
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800434 this.armX.innerHTML = this.superstructureStatus.arm().armX().toFixed(2);
435 this.armY.innerHTML = this.superstructureStatus.arm().armY().toFixed(2);
436 this.circularIndex.innerHTML =
437 this.superstructureStatus.arm().armCircularIndex().toFixed(0);
438 this.roll.innerHTML = this.superstructureStatus.arm()
439 .rollJointEstimatorState()
440 .position()
441 .toFixed(2);
442 this.proximal.innerHTML = this.superstructureStatus.arm()
443 .proximalEstimatorState()
444 .position()
445 .toFixed(2);
446 this.distal.innerHTML = this.superstructureStatus.arm()
447 .distalEstimatorState()
448 .position()
449 .toFixed(2);
450 let zeroingErrors: string = 'Roll Joint Errors:' +
451 '<br/>';
452 for (let i = 0; i < this.superstructureStatus.arm()
453 .rollJointEstimatorState()
Niko Sohmersec76b132023-11-25 16:40:21 -0800454 .errorsLength();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800455 i++) {
456 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
457 .rollJointEstimatorState()
458 .errors(i)] +
459 '<br/>';
460 }
461 zeroingErrors += '<br/>' +
462 'Proximal Joint Errors:' +
463 '<br/>';
464 for (let i = 0; i < this.superstructureStatus.arm()
465 .proximalEstimatorState()
Niko Sohmersec76b132023-11-25 16:40:21 -0800466 .errorsLength();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800467 i++) {
468 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
469 .proximalEstimatorState()
470 .errors(i)] +
471 '<br/>';
472 }
473 zeroingErrors += '<br/>' +
474 'Distal Joint Errors:' +
475 '<br/>';
476 for (let i = 0; i <
Niko Sohmersec76b132023-11-25 16:40:21 -0800477 this.superstructureStatus.arm().distalEstimatorState().errorsLength();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800478 i++) {
479 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
480 .distalEstimatorState()
481 .errors(i)] +
482 '<br/>';
483 }
484 zeroingErrors += '<br/>' +
485 'Wrist Errors:' +
486 '<br/>';
487 for (let i = 0;
Niko Sohmersec76b132023-11-25 16:40:21 -0800488 i < this.superstructureStatus.wrist().estimatorState().errorsLength();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800489 i++) {
490 zeroingErrors += ZeroingError[this.superstructureStatus.wrist()
491 .estimatorState()
492 .errors(i)] +
493 '<br/>';
494 }
495 this.zeroingFaults.innerHTML = zeroingErrors;
Milo Line6571c02023-03-04 21:08:20 -0800496 }
497
Maxwell Hendersonad312342023-01-10 12:07:47 -0800498 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
499 this.drawRobot(
500 this.drivetrainStatus.trajectoryLogging().x(),
501 this.drivetrainStatus.trajectoryLogging().y(),
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800502 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
Maxwell Hendersonad312342023-01-10 12:07:47 -0800503 false);
504 }
505
Niko Sohmers76f47562023-12-20 20:59:06 -0800506 if (this.drivetrainPosition) {
507 this.leftDrivetrainEncoder.innerHTML =
508 this.drivetrainPosition.leftEncoder().toString();
509
510 this.rightDrivetrainEncoder.innerHTML =
511 this.drivetrainPosition.rightEncoder().toString();
512 }
513
514 if (this.drivetrainCANPosition) {
515 this.falconRightFrontPosition.innerHTML = //TODO: (niko) Improve this so that falcons are not hard-coded
516 this.drivetrainCANPosition.falcons(0).position().toString();
517
518 this.falconRightBackPosition.innerHTML =
519 this.drivetrainCANPosition.falcons(1).position().toString();
520
521 this.falconRightUnderPosition.innerHTML =
522 this.drivetrainCANPosition.falcons(2).position().toString();
523
524 this.falconLeftFrontPosition.innerHTML =
525 this.drivetrainCANPosition.falcons(3).position().toString();
526
527 this.falconLeftBackPosition.innerHTML =
528 this.drivetrainCANPosition.falcons(4).position().toString();
529
530 this.falconLeftUnderPosition.innerHTML =
531 this.drivetrainCANPosition.falcons(5).position().toString();
532 }
533
James Kuszmaulfb894572023-02-23 17:25:06 -0800534 if (this.localizerOutput) {
535 if (!this.localizerOutput.zeroed()) {
536 this.setZeroing(this.x);
537 this.setZeroing(this.y);
538 this.setZeroing(this.theta);
539 } else {
540 this.setValue(this.x, this.localizerOutput.x());
541 this.setValue(this.y, this.localizerOutput.y());
542 this.setValue(this.theta, this.localizerOutput.theta());
543 }
544
545 this.drawRobot(
546 this.localizerOutput.x(), this.localizerOutput.y(),
547 this.localizerOutput.theta());
548
549 this.imagesAcceptedCounter.innerHTML =
550 this.localizerOutput.imageAcceptedCount().toString();
551 }
552
553 for (const [time, value] of this.localizerImageMatches) {
554 const age = now - time;
555 const kRemovalAge = 1.0;
556 if (age > kRemovalAge) {
557 this.localizerImageMatches.delete(time);
558 continue;
559 }
560 const kMaxImageAlpha = 0.5;
561 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
562 for (let i = 0; i < value.targetsLength(); i++) {
563 const imageDebug = value.targets(i);
564 const x = imageDebug.impliedRobotX();
565 const y = imageDebug.impliedRobotY();
566 const theta = imageDebug.impliedRobotTheta();
567 const cameraX = imageDebug.cameraX();
568 const cameraY = imageDebug.cameraY();
569 const cameraTheta = imageDebug.cameraTheta();
570 const accepted = imageDebug.accepted();
571 // Make camera readings fade over time.
572 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
573 const dashed = false;
James Kuszmaulfb894572023-02-23 17:25:06 -0800574 const cameraRgb = PI_COLORS[imageDebug.camera()];
575 const cameraRgba = cameraRgb + alpha;
James Kuszmaul122a22b2023-02-25 18:14:15 -0800576 this.drawRobot(x, y, theta, cameraRgba, dashed);
James Kuszmaulfb894572023-02-23 17:25:06 -0800577 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
578 }
579 }
580
Maxwell Hendersonad312342023-01-10 12:07:47 -0800581 window.requestAnimationFrame(() => this.draw());
582 }
583
584 reset(): void {
585 const ctx = this.canvas.getContext('2d');
586 ctx.setTransform(1, 0, 0, 1, 0, 0);
587 const size = window.innerHeight * 0.9;
588 ctx.canvas.height = size;
589 const width = size / 2 + 20;
590 ctx.canvas.width = width;
591 ctx.clearRect(0, 0, size, width);
592
593 // Translate to center of display.
594 ctx.translate(width / 2, size / 2);
595 // Coordinate system is:
596 // x -> forward.
597 // y -> to the left.
598 ctx.rotate(-Math.PI / 2);
599 ctx.scale(1, -1);
600
601 const M_TO_PX = (size - 10) / FIELD_LENGTH;
602 ctx.scale(M_TO_PX, M_TO_PX);
603 ctx.lineWidth = 1 / M_TO_PX;
604 }
605}