blob: 81050ddda17e9ee486f243837f4b6291f88a0ecb [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'
9import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated'
10import {TargetMap} from '../../frc971/vision/target_map_generated'
11
12
13import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
14
15// (0,0) is field center, +X is toward red DS
16const FIELD_SIDE_Y = FIELD_WIDTH / 2;
17const FIELD_EDGE_X = FIELD_LENGTH / 2;
18
Niko Sohmers2d108762024-02-02 20:21:14 -080019const ROBOT_WIDTH = 29 * IN_TO_M;
Niko Sohmers3860f8a2024-01-12 21:05:19 -080020const ROBOT_LENGTH = 32 * IN_TO_M;
21
22export class FieldHandler {
Niko Sohmers2d108762024-02-02 20:21:14 -080023 private canvas = document.createElement('canvas');
Mirabel Wang66546642024-02-10 16:37:05 -080024 private localizerOutput: LocalizerOutput|null = null;
25 private drivetrainStatus: DrivetrainStatus|null = null;
26 private drivetrainPosition: DrivetrainPosition|null = null;
27 private drivetrainCANPosition: DrivetrainCANPosition|null = null;
28
29 private x: HTMLElement = (document.getElementById('x') as HTMLElement);
30 private y: HTMLElement = (document.getElementById('y') as HTMLElement);
31 private theta: HTMLElement =
32 (document.getElementById('theta') as HTMLElement);
33
Niko Sohmers2d108762024-02-02 20:21:14 -080034 private fieldImage: HTMLImageElement = new Image();
Mirabel Wang66546642024-02-10 16:37:05 -080035
36 private leftDrivetrainEncoder: HTMLElement =
37 (document.getElementById('left_drivetrain_encoder') as HTMLElement);
38 private rightDrivetrainEncoder: HTMLElement =
39 (document.getElementById('right_drivetrain_encoder') as HTMLElement);
40 private falconRightFrontPosition: HTMLElement =
41 (document.getElementById('falcon_right_front') as HTMLElement);
42 private falconRightBackPosition: HTMLElement =
43 (document.getElementById('falcon_right_back') as HTMLElement);
44 private falconLeftFrontPosition: HTMLElement =
45 (document.getElementById('falcon_left_front') as HTMLElement);
46 private falconLeftBackPosition: HTMLElement =
47 (document.getElementById('falcon_left_back') as HTMLElement);
48
Niko Sohmers3860f8a2024-01-12 21:05:19 -080049 constructor(private readonly connection: Connection) {
Niko Sohmers2d108762024-02-02 20:21:14 -080050 (document.getElementById('field') as HTMLElement).appendChild(this.canvas);
51
52 this.fieldImage.src = '2024.png';
Mirabel Wang66546642024-02-10 16:37:05 -080053
54 this.connection.addConfigHandler(() => {
55
56 this.connection.addHandler(
57 '/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
58 this.handleDrivetrainStatus(data);
59 });
60 this.connection.addHandler(
61 '/drivetrain', 'frc971.control_loops.drivetrain.Position', (data) => {
62 this.handleDrivetrainPosition(data);
63 });
64 this.connection.addHandler(
65 '/drivetrain', 'frc971.control_loops.drivetrain.CANPosition', (data) => {
66 this.handleDrivetrainCANPosition(data);
67 });
68 this.connection.addHandler(
69 '/localizer', 'frc971.controls.LocalizerOutput', (data) => {
70 this.handleLocalizerOutput(data);
71 });
72 });
73 }
74
75 private handleDrivetrainStatus(data: Uint8Array): void {
76 const fbBuffer = new ByteBuffer(data);
77 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
78 }
79
80 private handleDrivetrainPosition(data: Uint8Array): void {
81 const fbBuffer = new ByteBuffer(data);
82 this.drivetrainPosition = DrivetrainPosition.getRootAsPosition(fbBuffer);
83 }
84
85 private handleDrivetrainCANPosition(data: Uint8Array): void {
86 const fbBuffer = new ByteBuffer(data);
87 this.drivetrainCANPosition = DrivetrainCANPosition.getRootAsCANPosition(fbBuffer);
88 }
89
90 private handleLocalizerOutput(data: Uint8Array): void {
91 const fbBuffer = new ByteBuffer(data);
92 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
Niko Sohmers2d108762024-02-02 20:21:14 -080093 }
94
95 drawField(): void {
96 const ctx = this.canvas.getContext('2d');
97 ctx.save();
98 ctx.scale(1.0, -1.0);
99 ctx.drawImage(
100 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
101 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
102 ctx.restore();
103 }
104
Mirabel Wang66546642024-02-10 16:37:05 -0800105 drawRobot(
106 x: number, y: number, theta: number, color: string = 'blue',
107 dashed: boolean = false): void {
108 const ctx = this.canvas.getContext('2d');
109 ctx.save();
110 ctx.translate(x, y);
111 ctx.rotate(theta);
112 ctx.strokeStyle = color;
113 ctx.lineWidth = ROBOT_WIDTH / 10.0;
114 if (dashed) {
115 ctx.setLineDash([0.05, 0.05]);
116 } else {
117 // Empty array = solid line.
118 ctx.setLineDash([]);
119 }
120 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
121 ctx.stroke();
122
123 // Draw line indicating which direction is forwards on the robot.
124 ctx.beginPath();
125 ctx.moveTo(0, 0);
126 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
127 ctx.stroke();
128
129 ctx.restore();
130}
131
132 setZeroing(div: HTMLElement): void {
133 div.innerHTML = 'zeroing';
134 div.classList.remove('faulted');
135 div.classList.add('zeroing');
136 div.classList.remove('near');
137}
138
139 setValue(div: HTMLElement, val: number): void {
140 div.innerHTML = val.toFixed(4);
141 div.classList.remove('faulted');
142 div.classList.remove('zeroing');
143 div.classList.remove('near');
144}
Niko Sohmers2d108762024-02-02 20:21:14 -0800145 draw(): void {
146 this.reset();
147 this.drawField();
148
Mirabel Wang66546642024-02-10 16:37:05 -0800149 if (this.drivetrainPosition) {
150 this.leftDrivetrainEncoder.innerHTML =
151 this.drivetrainPosition.leftEncoder().toString();
152
153 this.rightDrivetrainEncoder.innerHTML =
154 this.drivetrainPosition.rightEncoder().toString();
155 }
156
157 if (this.drivetrainCANPosition) {
158 this.falconRightFrontPosition.innerHTML =
159 this.drivetrainCANPosition.talonfxs(0).position().toString();
160
161 this.falconRightBackPosition.innerHTML =
162 this.drivetrainCANPosition.talonfxs(1).position().toString();
163
164 this.falconLeftFrontPosition.innerHTML =
165 this.drivetrainCANPosition.talonfxs(2).position().toString();
166
167 this.falconLeftBackPosition.innerHTML =
168 this.drivetrainCANPosition.talonfxs(3).position().toString();
169 }
170
171 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
172 this.drawRobot(
173 this.drivetrainStatus.trajectoryLogging().x(),
174 this.drivetrainStatus.trajectoryLogging().y(),
175 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
176 false);
177 }
178
179 if (this.localizerOutput) {
180 if (!this.localizerOutput.zeroed()) {
181 this.setZeroing(this.x);
182 this.setZeroing(this.y);
183 this.setZeroing(this.theta);
184 } else {
185 this.setValue(this.x, this.localizerOutput.x());
186 this.setValue(this.y, this.localizerOutput.y());
187 this.setValue(this.theta, this.localizerOutput.theta());
188 }
189
190 this.drawRobot(
191 this.localizerOutput.x(), this.localizerOutput.y(),
192 this.localizerOutput.theta());
193 }
Niko Sohmers2d108762024-02-02 20:21:14 -0800194 window.requestAnimationFrame(() => this.draw());
195 }
196
197 reset(): void {
198 const ctx = this.canvas.getContext('2d');
199 // Empty space from the canvas boundary to the image
200 const IMAGE_PADDING = 10;
201 ctx.setTransform(1, 0, 0, 1, 0, 0);
202 const size = window.innerHeight * 0.9;
203 ctx.canvas.height = size;
204 const width = size / 2 + 20;
205 ctx.canvas.width = width;
206 ctx.clearRect(0, 0, size, width);
207
208 // Translate to center of display.
209 ctx.translate(width / 2, size / 2);
210 // Coordinate system is:
211 // x -> forward.
212 // y -> to the left.
213 ctx.rotate(-Math.PI / 2);
214 ctx.scale(1, -1);
215
216 const M_TO_PX = (size - IMAGE_PADDING) / FIELD_LENGTH;
217 ctx.scale(M_TO_PX, M_TO_PX);
218 ctx.lineWidth = 1 / M_TO_PX;
Niko Sohmers3860f8a2024-01-12 21:05:19 -0800219 }
220}