blob: d6ff47976ebe3319cbfccd3add211d8143080c99 [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);
Niko Sohmersf6bf7a52023-12-20 21:04:52 -080059 private wristAbsoluteEncoder: HTMLElement =
60 (document.getElementById('wrist_encoder') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080061 private armState: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080062 (document.getElementById('arm_state') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080063 private gamePiece: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080064 (document.getElementById('game_piece') as HTMLElement);
James Kuszmaulef6b7402023-04-09 14:29:38 -070065 private gamePiecePosition: HTMLElement =
66 (document.getElementById('game_piece_position') as HTMLElement);
James Kuszmaulf7b5d622023-03-11 15:14:53 -080067 private armX: HTMLElement = (document.getElementById('arm_x') as HTMLElement);
68 private armY: HTMLElement = (document.getElementById('arm_y') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080069 private circularIndex: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080070 (document.getElementById('arm_circular_index') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080071 private roll: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080072 (document.getElementById('arm_roll') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080073 private proximal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080074 (document.getElementById('arm_proximal') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080075 private distal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080076 (document.getElementById('arm_distal') as HTMLElement);
Niko Sohmersf6bf7a52023-12-20 21:04:52 -080077 private rollPotentiometer: HTMLElement =
78 (document.getElementById('arm_roll_pot') as HTMLElement);
79 private rollAbsoluteEncoder: HTMLElement =
80 (document.getElementById('arm_roll_encoder') as HTMLElement);
81 private proximalPotentiometer: HTMLElement =
82 (document.getElementById('arm_proximal_pot') as HTMLElement);
83 private proximalAbsoluteEncoder: HTMLElement =
84 (document.getElementById('arm_proximal_encoder') as HTMLElement);
85 private distalPotentiometer: HTMLElement =
86 (document.getElementById('arm_distal_pot') as HTMLElement);
87 private distalAbsoluteEncoder: HTMLElement =
88 (document.getElementById('arm_distal_encoder') as HTMLElement);
Milo Lin72fb9012023-03-10 19:53:19 -080089 private zeroingFaults: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080090 (document.getElementById('zeroing_faults') as HTMLElement);
Niko Sohmers76f47562023-12-20 20:59:06 -080091
92 private leftDrivetrainEncoder: HTMLElement =
93 (document.getElementById('left_drivetrain_encoder') as HTMLElement);
94 private rightDrivetrainEncoder: HTMLElement =
95 (document.getElementById('right_drivetrain_encoder') as HTMLElement);
96 private falconRightFrontPosition: HTMLElement =
97 (document.getElementById('falcon_right_front') as HTMLElement);
98 private falconRightBackPosition: HTMLElement =
99 (document.getElementById('falcon_right_back') as HTMLElement);
100 private falconRightUnderPosition: HTMLElement =
101 (document.getElementById('falcon_right_under') as HTMLElement);
102 private falconLeftFrontPosition: HTMLElement =
103 (document.getElementById('falcon_left_front') as HTMLElement);
104 private falconLeftBackPosition: HTMLElement =
105 (document.getElementById('falcon_left_back') as HTMLElement);
106 private falconLeftUnderPosition: HTMLElement =
107 (document.getElementById('falcon_left_under') as HTMLElement);
108
Maxwell Hendersonad312342023-01-10 12:07:47 -0800109 constructor(private readonly connection: Connection) {
110 (document.getElementById('field') as HTMLElement).appendChild(this.canvas);
111
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800112 this.fieldImage.src = '2023.png';
James Kuszmaulfb894572023-02-23 17:25:06 -0800113
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700114 // Construct a table header.
115 {
116 const row = document.createElement('div');
117 const nameCell = document.createElement('div');
Milo Lin13906572023-03-15 20:55:22 -0700118 nameCell.innerHTML = 'Rejection Reason';
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700119 row.appendChild(nameCell);
120 for (const pi of PIS) {
121 const nodeCell = document.createElement('div');
122 nodeCell.innerHTML = pi;
123 row.appendChild(nodeCell);
124 }
125 document.getElementById('vision_readouts').appendChild(row);
126 }
James Kuszmaulfb894572023-02-23 17:25:06 -0800127 for (const value in RejectionReason) {
128 // Typescript generates an iterator that produces both numbers and
129 // strings... don't do anything on the string iterations.
130 if (isNaN(Number(value))) {
131 continue;
132 }
133 const row = document.createElement('div');
134 const nameCell = document.createElement('div');
135 nameCell.innerHTML = RejectionReason[value];
136 row.appendChild(nameCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700137 this.rejectionReasonCells.push([]);
138 for (const pi of PIS) {
139 const valueCell = document.createElement('div');
140 valueCell.innerHTML = 'NA';
Milo Lin13906572023-03-15 20:55:22 -0700141 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
142 valueCell);
143 row.appendChild(valueCell);
144 }
145 document.getElementById('vision_readouts').appendChild(row);
146 }
147
148 // Add rejection reason row for aprilrobotics rejections.
149 {
150 const row = document.createElement('div');
151 const nameCell = document.createElement('div');
152 nameCell.innerHTML = 'Rejected by aprilrobotics';
153 row.appendChild(nameCell);
154 this.rejectionReasonCells.push([]);
155 for (const pi of PIS) {
156 const valueCell = document.createElement('div');
157 valueCell.innerHTML = 'NA';
158 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
159 valueCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700160 row.appendChild(valueCell);
161 }
James Kuszmaulfb894572023-02-23 17:25:06 -0800162 document.getElementById('vision_readouts').appendChild(row);
163 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800164
165 for (let ii = 0; ii < PI_COLORS.length; ++ii) {
166 const legendEntry = document.createElement('div');
167 legendEntry.style.color = PI_COLORS[ii];
168 legendEntry.innerHTML = 'PI' + (ii + 1).toString()
169 document.getElementById('legend').appendChild(legendEntry);
170 }
171
172 this.connection.addConfigHandler(() => {
173 // Visualization message is reliable so that we can see *all* the vision
174 // matches.
James Kuszmaulfb894572023-02-23 17:25:06 -0800175 for (const pi in PIS) {
176 this.connection.addReliableHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800177 '/' + PIS[pi] + '/camera', 'y2023.localizer.Visualization',
James Kuszmaulfb894572023-02-23 17:25:06 -0800178 (data) => {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700179 this.handleLocalizerDebug(Number(pi), data);
James Kuszmaulfb894572023-02-23 17:25:06 -0800180 });
181 }
Milo Lin13906572023-03-15 20:55:22 -0700182 for (const pi in PIS) {
183 // Make unreliable to reduce network spam.
184 this.connection.addHandler(
185 '/' + PIS[pi] + '/camera', 'frc971.vision.TargetMap', (data) => {
186 this.handlePiTargetMap(pi, data);
187 });
188 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800189 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800190 '/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800191 this.handleDrivetrainStatus(data);
192 });
193 this.connection.addHandler(
Niko Sohmers76f47562023-12-20 20:59:06 -0800194 '/drivetrain', 'frc971.control_loops.drivetrain.Position', (data) => {
195 this.handleDrivetrainPosition(data);
196 });
197 this.connection.addHandler(
198 '/drivetrain', 'y2023.control_loops.drivetrain.CANPosition', (data) => {
199 this.handleDrivetrainCANPosition(data);
200 });
201 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800202 '/localizer', 'frc971.controls.LocalizerOutput', (data) => {
James Kuszmaulfb894572023-02-23 17:25:06 -0800203 this.handleLocalizerOutput(data);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800204 });
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800205 this.connection.addHandler(
206 '/superstructure', 'y2023.control_loops.superstructure.Status',
207 (data) => {this.handleSuperstructureStatus(data)});
208 this.connection.addHandler(
209 '/aos', 'aos.message_bridge.ServerStatistics',
210 (data) => {this.handleServerStatistics(data)});
211 this.connection.addHandler(
212 '/aos', 'aos.message_bridge.ClientStatistics',
213 (data) => {this.handleClientStatistics(data)});
Maxwell Hendersonad312342023-01-10 12:07:47 -0800214 });
215 }
216
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700217 private handleLocalizerDebug(pi: number, data: Uint8Array): void {
James Kuszmaulfb894572023-02-23 17:25:06 -0800218 const now = Date.now() / 1000.0;
219
220 const fbBuffer = new ByteBuffer(data);
221 this.localizerImageMatches.set(
222 now, Visualization.getRootAsVisualization(fbBuffer));
223
224 const debug = this.localizerImageMatches.get(now);
225
226 if (debug.statistics()) {
Milo Lin13906572023-03-15 20:55:22 -0700227 if ((debug.statistics().rejectionReasonsLength() + 1) ==
James Kuszmaulfb894572023-02-23 17:25:06 -0800228 this.rejectionReasonCells.length) {
229 for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
230 ++ii) {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700231 this.rejectionReasonCells[ii][pi].innerHTML =
James Kuszmaulfb894572023-02-23 17:25:06 -0800232 debug.statistics().rejectionReasons(ii).count().toString();
233 }
234 } else {
235 console.error('Unexpected number of rejection reasons in counter.');
236 }
237 }
238 }
239
Milo Lin13906572023-03-15 20:55:22 -0700240 private handlePiTargetMap(pi: string, data: Uint8Array): void {
241 const fbBuffer = new ByteBuffer(data);
242 const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
243 this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
244 .innerHTML = targetMap.rejections().toString();
245 }
246
James Kuszmaulfb894572023-02-23 17:25:06 -0800247 private handleLocalizerOutput(data: Uint8Array): void {
248 const fbBuffer = new ByteBuffer(data);
249 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
250 }
251
Maxwell Hendersonad312342023-01-10 12:07:47 -0800252 private handleDrivetrainStatus(data: Uint8Array): void {
253 const fbBuffer = new ByteBuffer(data);
254 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
255 }
256
Niko Sohmers76f47562023-12-20 20:59:06 -0800257 private handleDrivetrainPosition(data: Uint8Array): void {
258 const fbBuffer = new ByteBuffer(data);
259 this.drivetrainPosition = DrivetrainPosition.getRootAsPosition(fbBuffer);
260 }
261
262 private handleDrivetrainCANPosition(data: Uint8Array): void {
263 const fbBuffer = new ByteBuffer(data);
264 this.drivetrainCANPosition = DrivetrainCANPosition.getRootAsCANPosition(fbBuffer);
265 }
266
Milo Line6571c02023-03-04 21:08:20 -0800267 private handleSuperstructureStatus(data: Uint8Array): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800268 const fbBuffer = new ByteBuffer(data);
269 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
270 }
271
272 private populateNodeConnections(nodeName: string): void {
273 const row = document.createElement('div');
274 this.messageBridgeDiv.appendChild(row);
275 const nodeDiv = document.createElement('div');
276 nodeDiv.innerHTML = nodeName;
277 row.appendChild(nodeDiv);
278 const clientDiv = document.createElement('div');
279 clientDiv.innerHTML = 'N/A';
280 row.appendChild(clientDiv);
281 const serverDiv = document.createElement('div');
282 serverDiv.innerHTML = 'N/A';
283 row.appendChild(serverDiv);
284 this.serverStatuses.set(nodeName, serverDiv);
285 this.clientStatuses.set(nodeName, clientDiv);
286 }
287
288 private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
289 void {
290 if (state === ConnectionState.CONNECTED) {
291 element.innerHTML = ConnectionState[state];
292 element.classList.remove('faulted');
293 element.classList.add('connected');
294 } else {
295 element.innerHTML = ConnectionState[state];
296 element.classList.remove('connected');
297 element.classList.add('faulted');
298 }
299 }
300
301 private handleServerStatistics(data: Uint8Array): void {
302 const fbBuffer = new ByteBuffer(data);
303 const serverStatistics =
304 ServerStatistics.getRootAsServerStatistics(fbBuffer);
305
306 for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
307 const connection = serverStatistics.connections(ii);
308 const nodeName = connection.node().name();
309 if (!this.serverStatuses.has(nodeName)) {
310 this.populateNodeConnections(nodeName);
311 }
312 this.setCurrentNodeState(
313 this.serverStatuses.get(nodeName), connection.state());
314 }
315 }
316
317 private handleClientStatistics(data: Uint8Array): void {
318 const fbBuffer = new ByteBuffer(data);
319 const clientStatistics =
320 ClientStatistics.getRootAsClientStatistics(fbBuffer);
321
322 for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
323 const connection = clientStatistics.connections(ii);
324 const nodeName = connection.node().name();
325 if (!this.clientStatuses.has(nodeName)) {
326 this.populateNodeConnections(nodeName);
327 }
328 this.setCurrentNodeState(
329 this.clientStatuses.get(nodeName), connection.state());
330 }
Milo Line6571c02023-03-04 21:08:20 -0800331 }
332
Maxwell Hendersonad312342023-01-10 12:07:47 -0800333 drawField(): void {
334 const ctx = this.canvas.getContext('2d');
335 ctx.save();
James Kuszmaulfb894572023-02-23 17:25:06 -0800336 ctx.scale(1.0, -1.0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800337 ctx.drawImage(
338 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
339 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
340 ctx.restore();
341 }
342
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800343 drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
344 void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800345 const ctx = this.canvas.getContext('2d');
346 ctx.save();
347 ctx.translate(x, y);
348 ctx.rotate(theta);
349 ctx.strokeStyle = color;
350 ctx.beginPath();
351 ctx.moveTo(0.5, 0.5);
352 ctx.lineTo(0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800353 ctx.lineTo(0.5, -0.5);
354 ctx.stroke();
355 ctx.beginPath();
356 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
357 ctx.stroke();
358 ctx.restore();
359 }
360
361 drawRobot(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800362 x: number, y: number, theta: number, color: string = 'blue',
363 dashed: boolean = false): void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800364 const ctx = this.canvas.getContext('2d');
365 ctx.save();
366 ctx.translate(x, y);
367 ctx.rotate(theta);
368 ctx.strokeStyle = color;
369 ctx.lineWidth = ROBOT_WIDTH / 10.0;
370 if (dashed) {
371 ctx.setLineDash([0.05, 0.05]);
372 } else {
373 // Empty array = solid line.
374 ctx.setLineDash([]);
375 }
376 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
377 ctx.stroke();
378
379 // Draw line indicating which direction is forwards on the robot.
380 ctx.beginPath();
381 ctx.moveTo(0, 0);
James Kuszmaulfb894572023-02-23 17:25:06 -0800382 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800383 ctx.stroke();
384
385 ctx.restore();
386 }
387
388 setZeroing(div: HTMLElement): void {
389 div.innerHTML = 'zeroing';
390 div.classList.remove('faulted');
391 div.classList.add('zeroing');
392 div.classList.remove('near');
393 }
394
Milo Line6571c02023-03-04 21:08:20 -0800395 setEstopped(div: HTMLElement): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800396 div.innerHTML = 'estopped';
397 div.classList.add('faulted');
398 div.classList.remove('zeroing');
399 div.classList.remove('near');
Milo Line6571c02023-03-04 21:08:20 -0800400 }
401
402 setTargetValue(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800403 div: HTMLElement, target: number, val: number, tolerance: number): void {
404 div.innerHTML = val.toFixed(4);
405 div.classList.remove('faulted');
406 div.classList.remove('zeroing');
407 if (Math.abs(target - val) < tolerance) {
408 div.classList.add('near');
409 } else {
410 div.classList.remove('near');
411 }
Milo Line6571c02023-03-04 21:08:20 -0800412 }
413
Maxwell Hendersonad312342023-01-10 12:07:47 -0800414 setValue(div: HTMLElement, val: number): void {
415 div.innerHTML = val.toFixed(4);
416 div.classList.remove('faulted');
417 div.classList.remove('zeroing');
418 div.classList.remove('near');
419 }
420
421 draw(): void {
422 this.reset();
423 this.drawField();
424
425 // Draw the matches with debugging information from the localizer.
426 const now = Date.now() / 1000.0;
James Kuszmaulfb894572023-02-23 17:25:06 -0800427
Milo Line6571c02023-03-04 21:08:20 -0800428 if (this.superstructureStatus) {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800429 this.endEffectorState.innerHTML =
430 EndEffectorState[this.superstructureStatus.endEffectorState()];
431 if (!this.superstructureStatus.wrist() ||
432 !this.superstructureStatus.wrist().zeroed()) {
433 this.setZeroing(this.wrist);
434 } else if (this.superstructureStatus.wrist().estopped()) {
435 this.setEstopped(this.wrist);
436 } else {
437 this.setTargetValue(
438 this.wrist,
439 this.superstructureStatus.wrist().unprofiledGoalPosition(),
440 this.superstructureStatus.wrist().estimatorState().position(),
441 1e-3);
442 }
Niko Sohmersf6bf7a52023-12-20 21:04:52 -0800443 this.wristAbsoluteEncoder.innerHTML = this.superstructureStatus.wrist()
444 .estimatorState().position().toString();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800445 this.armState.innerHTML =
446 ArmState[this.superstructureStatus.arm().state()];
447 this.gamePiece.innerHTML = Class[this.superstructureStatus.gamePiece()];
James Kuszmaulef6b7402023-04-09 14:29:38 -0700448 this.gamePiecePosition.innerHTML =
449 this.superstructureStatus.gamePiecePosition().toFixed(4);
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800450 this.armX.innerHTML = this.superstructureStatus.arm().armX().toFixed(2);
451 this.armY.innerHTML = this.superstructureStatus.arm().armY().toFixed(2);
452 this.circularIndex.innerHTML =
453 this.superstructureStatus.arm().armCircularIndex().toFixed(0);
454 this.roll.innerHTML = this.superstructureStatus.arm()
455 .rollJointEstimatorState()
456 .position()
457 .toFixed(2);
Niko Sohmersf6bf7a52023-12-20 21:04:52 -0800458 this.rollPotentiometer.innerHTML = this.superstructureStatus.arm()
459 .rollJointEstimatorState()
460 .potPosition()
461 .toString();
462 this.rollAbsoluteEncoder.innerHTML = this.superstructureStatus.arm()
463 .rollJointEstimatorState()
464 .absolutePosition()
465 .toString();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800466 this.proximal.innerHTML = this.superstructureStatus.arm()
467 .proximalEstimatorState()
468 .position()
469 .toFixed(2);
470 this.distal.innerHTML = this.superstructureStatus.arm()
471 .distalEstimatorState()
472 .position()
473 .toFixed(2);
Niko Sohmersf6bf7a52023-12-20 21:04:52 -0800474 this.proximalPotentiometer.innerHTML = this.superstructureStatus.arm()
475 .proximalEstimatorState()
476 .potPosition()
477 .toString();
478 this.proximalAbsoluteEncoder.innerHTML = this.superstructureStatus.arm()
479 .proximalEstimatorState()
480 .absolutePosition()
481 .toString();
482 this.distalPotentiometer.innerHTML = this.superstructureStatus.arm()
483 .distalEstimatorState()
484 .potPosition()
485 .toString();
486 this.distalAbsoluteEncoder.innerHTML = this.superstructureStatus.arm()
487 .distalEstimatorState()
488 .absolutePosition()
489 .toString();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800490 let zeroingErrors: string = 'Roll Joint Errors:' +
491 '<br/>';
492 for (let i = 0; i < this.superstructureStatus.arm()
493 .rollJointEstimatorState()
Niko Sohmersec76b132023-11-25 16:40:21 -0800494 .errorsLength();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800495 i++) {
496 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
497 .rollJointEstimatorState()
498 .errors(i)] +
499 '<br/>';
500 }
501 zeroingErrors += '<br/>' +
502 'Proximal Joint Errors:' +
503 '<br/>';
504 for (let i = 0; i < this.superstructureStatus.arm()
505 .proximalEstimatorState()
Niko Sohmersec76b132023-11-25 16:40:21 -0800506 .errorsLength();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800507 i++) {
508 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
509 .proximalEstimatorState()
510 .errors(i)] +
511 '<br/>';
512 }
513 zeroingErrors += '<br/>' +
514 'Distal Joint Errors:' +
515 '<br/>';
516 for (let i = 0; i <
Niko Sohmersec76b132023-11-25 16:40:21 -0800517 this.superstructureStatus.arm().distalEstimatorState().errorsLength();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800518 i++) {
519 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
520 .distalEstimatorState()
521 .errors(i)] +
522 '<br/>';
523 }
524 zeroingErrors += '<br/>' +
525 'Wrist Errors:' +
526 '<br/>';
527 for (let i = 0;
Niko Sohmersec76b132023-11-25 16:40:21 -0800528 i < this.superstructureStatus.wrist().estimatorState().errorsLength();
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800529 i++) {
530 zeroingErrors += ZeroingError[this.superstructureStatus.wrist()
531 .estimatorState()
532 .errors(i)] +
533 '<br/>';
534 }
535 this.zeroingFaults.innerHTML = zeroingErrors;
Milo Line6571c02023-03-04 21:08:20 -0800536 }
537
Maxwell Hendersonad312342023-01-10 12:07:47 -0800538 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
539 this.drawRobot(
540 this.drivetrainStatus.trajectoryLogging().x(),
541 this.drivetrainStatus.trajectoryLogging().y(),
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800542 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
Maxwell Hendersonad312342023-01-10 12:07:47 -0800543 false);
544 }
545
Niko Sohmers76f47562023-12-20 20:59:06 -0800546 if (this.drivetrainPosition) {
547 this.leftDrivetrainEncoder.innerHTML =
548 this.drivetrainPosition.leftEncoder().toString();
549
550 this.rightDrivetrainEncoder.innerHTML =
551 this.drivetrainPosition.rightEncoder().toString();
552 }
553
554 if (this.drivetrainCANPosition) {
555 this.falconRightFrontPosition.innerHTML = //TODO: (niko) Improve this so that falcons are not hard-coded
556 this.drivetrainCANPosition.falcons(0).position().toString();
557
558 this.falconRightBackPosition.innerHTML =
559 this.drivetrainCANPosition.falcons(1).position().toString();
560
561 this.falconRightUnderPosition.innerHTML =
562 this.drivetrainCANPosition.falcons(2).position().toString();
563
564 this.falconLeftFrontPosition.innerHTML =
565 this.drivetrainCANPosition.falcons(3).position().toString();
566
567 this.falconLeftBackPosition.innerHTML =
568 this.drivetrainCANPosition.falcons(4).position().toString();
569
570 this.falconLeftUnderPosition.innerHTML =
571 this.drivetrainCANPosition.falcons(5).position().toString();
572 }
573
James Kuszmaulfb894572023-02-23 17:25:06 -0800574 if (this.localizerOutput) {
575 if (!this.localizerOutput.zeroed()) {
576 this.setZeroing(this.x);
577 this.setZeroing(this.y);
578 this.setZeroing(this.theta);
579 } else {
580 this.setValue(this.x, this.localizerOutput.x());
581 this.setValue(this.y, this.localizerOutput.y());
582 this.setValue(this.theta, this.localizerOutput.theta());
583 }
584
585 this.drawRobot(
586 this.localizerOutput.x(), this.localizerOutput.y(),
587 this.localizerOutput.theta());
588
589 this.imagesAcceptedCounter.innerHTML =
590 this.localizerOutput.imageAcceptedCount().toString();
591 }
592
593 for (const [time, value] of this.localizerImageMatches) {
594 const age = now - time;
595 const kRemovalAge = 1.0;
596 if (age > kRemovalAge) {
597 this.localizerImageMatches.delete(time);
598 continue;
599 }
600 const kMaxImageAlpha = 0.5;
601 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
602 for (let i = 0; i < value.targetsLength(); i++) {
603 const imageDebug = value.targets(i);
604 const x = imageDebug.impliedRobotX();
605 const y = imageDebug.impliedRobotY();
606 const theta = imageDebug.impliedRobotTheta();
607 const cameraX = imageDebug.cameraX();
608 const cameraY = imageDebug.cameraY();
609 const cameraTheta = imageDebug.cameraTheta();
610 const accepted = imageDebug.accepted();
611 // Make camera readings fade over time.
612 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
613 const dashed = false;
James Kuszmaulfb894572023-02-23 17:25:06 -0800614 const cameraRgb = PI_COLORS[imageDebug.camera()];
615 const cameraRgba = cameraRgb + alpha;
James Kuszmaul122a22b2023-02-25 18:14:15 -0800616 this.drawRobot(x, y, theta, cameraRgba, dashed);
James Kuszmaulfb894572023-02-23 17:25:06 -0800617 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
618 }
619 }
620
Maxwell Hendersonad312342023-01-10 12:07:47 -0800621 window.requestAnimationFrame(() => this.draw());
622 }
623
624 reset(): void {
625 const ctx = this.canvas.getContext('2d');
626 ctx.setTransform(1, 0, 0, 1, 0, 0);
627 const size = window.innerHeight * 0.9;
628 ctx.canvas.height = size;
629 const width = size / 2 + 20;
630 ctx.canvas.width = width;
631 ctx.clearRect(0, 0, size, width);
632
633 // Translate to center of display.
634 ctx.translate(width / 2, size / 2);
635 // Coordinate system is:
636 // x -> forward.
637 // y -> to the left.
638 ctx.rotate(-Math.PI / 2);
639 ctx.scale(1, -1);
640
641 const M_TO_PX = (size - 10) / FIELD_LENGTH;
642 ctx.scale(M_TO_PX, M_TO_PX);
643 ctx.lineWidth = 1 / M_TO_PX;
644 }
645}