blob: 1a0b56a072cb17d28af22405a31f9515e632228b [file] [log] [blame]
Philipp Schradere625ba22020-11-16 20:11:37 -08001import * as configuration from 'org_frc971/aos/configuration_generated';
Philipp Schradere625ba22020-11-16 20:11:37 -08002import {Connection} from 'org_frc971/aos/network/www/proxy';
3import * as flatbuffers_builder from 'org_frc971/external/com_github_google_flatbuffers/ts/builder';
4import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
5import * as drivetrain from 'org_frc971/frc971/control_loops/drivetrain/drivetrain_status_generated';
6import * as sift from 'org_frc971/y2020/vision/sift/sift_generated';
James Kuszmaul71a81932020-12-15 21:08:01 -08007import * as web_proxy from 'org_frc971/aos/network/web_proxy_generated';
James Kuszmaul5e6aa252021-08-28 22:19:29 -07008import * as ss from 'org_frc971/y2020/control_loops/superstructure/superstructure_status_generated'
Philipp Schradere625ba22020-11-16 20:11:37 -08009
10import DrivetrainStatus = drivetrain.frc971.control_loops.drivetrain.Status;
James Kuszmaul5e6aa252021-08-28 22:19:29 -070011import SuperstructureStatus = ss.y2020.control_loops.superstructure.Status;
Philipp Schradere625ba22020-11-16 20:11:37 -080012import ImageMatchResult = sift.frc971.vision.sift.ImageMatchResult;
Philipp Schradere625ba22020-11-16 20:11:37 -080013import Channel = configuration.aos.Channel;
James Kuszmaul71a81932020-12-15 21:08:01 -080014import SubscriberRequest = web_proxy.aos.web_proxy.SubscriberRequest;
15import ChannelRequest = web_proxy.aos.web_proxy.ChannelRequest;
16import TransferMethod = web_proxy.aos.web_proxy.TransferMethod;
Alex Perry5427c9a2020-02-15 17:43:45 -080017
Alex Perry2124ae82020-03-07 14:19:06 -080018import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
19
Alex Perryb49a3fb2020-02-29 15:26:54 -080020// (0,0) is field center, +X is toward red DS
Alex Perry5427c9a2020-02-15 17:43:45 -080021const FIELD_SIDE_Y = FIELD_WIDTH / 2;
Alex Perryb49a3fb2020-02-29 15:26:54 -080022const FIELD_EDGE_X = FIELD_LENGTH / 2;
Alex Perry5427c9a2020-02-15 17:43:45 -080023
24const DS_WIDTH = 69 * IN_TO_M;
25const DS_ANGLE = 20 * Math.PI / 180;
Alex Perryb49a3fb2020-02-29 15:26:54 -080026const DS_END_X = FIELD_EDGE_X - DS_WIDTH * Math.sin(DS_ANGLE);
Alex Perry5427c9a2020-02-15 17:43:45 -080027const DS_INSIDE_Y = FIELD_SIDE_Y - DS_WIDTH * Math.cos(DS_ANGLE);
28
Alex Perryb49a3fb2020-02-29 15:26:54 -080029const TRENCH_X = 108 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080030const TRENCH_WIDTH = 55.5 * IN_TO_M;
31const TRENCH_INSIDE = FIELD_SIDE_Y - TRENCH_WIDTH;
32
33const SPINNER_LENGTH = 30 * IN_TO_M;
Alex Perryb49a3fb2020-02-29 15:26:54 -080034const SPINNER_TOP_X = 374.59 * IN_TO_M - FIELD_EDGE_X;
Alex Perry5427c9a2020-02-15 17:43:45 -080035const SPINNER_BOTTOM_X = SPINNER_TOP_X - SPINNER_LENGTH;
36
Alex Perryb49a3fb2020-02-29 15:26:54 -080037const SHIELD_BOTTOM_X = -116 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080038const SHIELD_BOTTOM_Y = 43.75 * IN_TO_M;
39
Alex Perryb49a3fb2020-02-29 15:26:54 -080040const SHIELD_TOP_X = 116 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080041const SHIELD_TOP_Y = -43.75 * IN_TO_M;
42
Alex Perryb49a3fb2020-02-29 15:26:54 -080043const SHIELD_RIGHT_X = -51.06 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080044const SHIELD_RIGHT_Y = -112.88 * IN_TO_M;
45
Alex Perryb49a3fb2020-02-29 15:26:54 -080046const SHIELD_LEFT_X = 51.06 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080047const SHIELD_LEFT_Y = 112.88 * IN_TO_M;
48
49const SHIELD_CENTER_TOP_X = (SHIELD_TOP_X + SHIELD_LEFT_X) / 2
50const SHIELD_CENTER_TOP_Y = (SHIELD_TOP_Y + SHIELD_LEFT_Y) / 2
51
52const SHIELD_CENTER_BOTTOM_X = (SHIELD_BOTTOM_X + SHIELD_RIGHT_X) / 2
53const SHIELD_CENTER_BOTTOM_Y = (SHIELD_BOTTOM_Y + SHIELD_RIGHT_Y) / 2
54
Alex Perryb49a3fb2020-02-29 15:26:54 -080055const INITIATION_X = FIELD_EDGE_X - 120 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080056
Alex Perryb49a3fb2020-02-29 15:26:54 -080057const TARGET_ZONE_TIP_X = FIELD_EDGE_X - 30 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080058const TARGET_ZONE_WIDTH = 48 * IN_TO_M;
59const LOADING_ZONE_WIDTH = 60 * IN_TO_M;
60
Alex Perry2124ae82020-03-07 14:19:06 -080061const ROBOT_WIDTH = 28 * IN_TO_M;
62const ROBOT_LENGTH = 30 * IN_TO_M;
63
James Kuszmaul5e6aa252021-08-28 22:19:29 -070064
Alex Perry5427c9a2020-02-15 17:43:45 -080065export class FieldHandler {
66 private canvas = document.createElement('canvas');
James Kuszmaul5e6aa252021-08-28 22:19:29 -070067 private imageMatchResult = new Map<string, ImageMatchResult>();
Philipp Schradera227d042020-11-14 17:33:52 -080068 private drivetrainStatus: DrivetrainStatus|null = null;
James Kuszmaul5e6aa252021-08-28 22:19:29 -070069 private superstructureStatus: SuperstructureStatus|null = null;
Alex Perry5427c9a2020-02-15 17:43:45 -080070
Alex Perryb49a3fb2020-02-29 15:26:54 -080071 constructor(private readonly connection: Connection) {
Alex Perry5427c9a2020-02-15 17:43:45 -080072 document.body.appendChild(this.canvas);
Alex Perryb49a3fb2020-02-29 15:26:54 -080073
74 this.connection.addConfigHandler(() => {
James Kuszmaul5e6aa252021-08-28 22:19:29 -070075 // Go through and register handlers for both all the individual pis as
76 // well as the local pi. Depending on the node that we are running on,
77 // different subsets of these will be available.
78 for (const prefix of ['', '/pi1', '/pi2', '/pi3', '/pi4']) {
79 this.connection.addHandler(
80 prefix + '/camera', ImageMatchResult.getFullyQualifiedName(), (res) => {
81 this.handleImageMatchResult(prefix, res);
82 });
83 }
James Kuszmaul527038a2020-12-21 23:40:44 -080084 this.connection.addHandler(
85 '/drivetrain', DrivetrainStatus.getFullyQualifiedName(), (data) => {
86 this.handleDrivetrainStatus(data);
87 });
James Kuszmaul5e6aa252021-08-28 22:19:29 -070088 this.connection.addHandler(
89 '/superstructure', SuperstructureStatus.getFullyQualifiedName(),
90 (data) => {
91 this.handleSuperstructureStatus(data);
92 });
Alex Perryb49a3fb2020-02-29 15:26:54 -080093 });
Alex Perryb49a3fb2020-02-29 15:26:54 -080094 }
95
James Kuszmaul5e6aa252021-08-28 22:19:29 -070096 private handleImageMatchResult(prefix: string, data: Uint8Array): void {
Philipp Schradere625ba22020-11-16 20:11:37 -080097 const fbBuffer = new ByteBuffer(data);
James Kuszmaul5e6aa252021-08-28 22:19:29 -070098 this.imageMatchResult.set(
99 prefix,
100 ImageMatchResult.getRootAsImageMatchResult(
101 fbBuffer as unknown as flatbuffers.ByteBuffer));
Alex Perryb49a3fb2020-02-29 15:26:54 -0800102 }
103
Alex Perry2124ae82020-03-07 14:19:06 -0800104 private handleDrivetrainStatus(data: Uint8Array): void {
Philipp Schradere625ba22020-11-16 20:11:37 -0800105 const fbBuffer = new ByteBuffer(data);
106 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(
107 fbBuffer as unknown as flatbuffers.ByteBuffer);
Alex Perry2124ae82020-03-07 14:19:06 -0800108 }
109
James Kuszmaul5e6aa252021-08-28 22:19:29 -0700110 private handleSuperstructureStatus(data: Uint8Array): void {
111 const fbBuffer = new ByteBuffer(data);
112 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(
113 fbBuffer as unknown as flatbuffers.ByteBuffer);
114 }
115
Alex Perry5427c9a2020-02-15 17:43:45 -0800116 drawField(): void {
117 const MY_COLOR = 'red';
118 const OTHER_COLOR = 'blue';
119 const ctx = this.canvas.getContext('2d');
120 // draw perimiter
121 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800122 ctx.moveTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800123 ctx.lineTo(DS_END_X, FIELD_SIDE_Y);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800124 ctx.lineTo(-DS_END_X, FIELD_SIDE_Y);
125 ctx.lineTo(-FIELD_EDGE_X, DS_INSIDE_Y);
126 ctx.lineTo(-FIELD_EDGE_X, -DS_INSIDE_Y);
127 ctx.lineTo(-DS_END_X, -FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800128 ctx.lineTo(DS_END_X, -FIELD_SIDE_Y);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800129 ctx.lineTo(FIELD_EDGE_X, -DS_INSIDE_Y);
130 ctx.lineTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800131 ctx.stroke();
132
133 // draw shield generator
134 ctx.beginPath();
135 ctx.moveTo(SHIELD_BOTTOM_X, SHIELD_BOTTOM_Y);
136 ctx.lineTo(SHIELD_RIGHT_X, SHIELD_RIGHT_Y);
137 ctx.lineTo(SHIELD_TOP_X, SHIELD_TOP_Y);
138 ctx.lineTo(SHIELD_LEFT_X, SHIELD_LEFT_Y);
139 ctx.lineTo(SHIELD_BOTTOM_X, SHIELD_BOTTOM_Y);
140 ctx.moveTo(SHIELD_CENTER_TOP_X, SHIELD_CENTER_TOP_Y);
141 ctx.lineTo(SHIELD_CENTER_BOTTOM_X, SHIELD_CENTER_BOTTOM_Y);
142 ctx.stroke();
143
Alex Perryb49a3fb2020-02-29 15:26:54 -0800144 this.drawHalfField(ctx, 'red');
145 ctx.rotate(Math.PI);
146 this.drawHalfField(ctx, 'blue');
147 ctx.rotate(Math.PI);
148 }
Alex Perry5427c9a2020-02-15 17:43:45 -0800149
Alex Perryb49a3fb2020-02-29 15:26:54 -0800150 drawHalfField(ctx, color: string): void {
151 // trenches
152 ctx.strokeStyle = color;
Alex Perry5427c9a2020-02-15 17:43:45 -0800153 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800154 ctx.moveTo(TRENCH_X, FIELD_SIDE_Y);
155 ctx.lineTo(TRENCH_X, TRENCH_INSIDE);
156 ctx.lineTo(-TRENCH_X, TRENCH_INSIDE);
157 ctx.lineTo(-TRENCH_X, FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800158 ctx.stroke();
159
160 ctx.strokeStyle = 'black';
161 ctx.beginPath();
162 ctx.moveTo(SPINNER_TOP_X, FIELD_SIDE_Y);
163 ctx.lineTo(SPINNER_TOP_X, TRENCH_INSIDE);
164 ctx.lineTo(SPINNER_BOTTOM_X, TRENCH_INSIDE);
165 ctx.lineTo(SPINNER_BOTTOM_X, FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800166 ctx.stroke();
167
Alex Perry5427c9a2020-02-15 17:43:45 -0800168 ctx.beginPath();
169 ctx.moveTo(INITIATION_X, FIELD_SIDE_Y);
170 ctx.lineTo(INITIATION_X, -FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800171 ctx.stroke();
172
Alex Perryb49a3fb2020-02-29 15:26:54 -0800173 // target/loading
174 ctx.strokeStyle = color;
Alex Perry5427c9a2020-02-15 17:43:45 -0800175 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800176 ctx.moveTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800177 ctx.lineTo(TARGET_ZONE_TIP_X, DS_INSIDE_Y - 0.5 * TARGET_ZONE_WIDTH);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800178 ctx.lineTo(FIELD_EDGE_X, DS_INSIDE_Y - TARGET_ZONE_WIDTH);
Alex Perry5427c9a2020-02-15 17:43:45 -0800179
Alex Perryb49a3fb2020-02-29 15:26:54 -0800180 ctx.moveTo(-FIELD_EDGE_X, DS_INSIDE_Y);
181 ctx.lineTo(-TARGET_ZONE_TIP_X, DS_INSIDE_Y - 0.5 * LOADING_ZONE_WIDTH);
182 ctx.lineTo(-FIELD_EDGE_X, DS_INSIDE_Y - LOADING_ZONE_WIDTH);
Alex Perry5427c9a2020-02-15 17:43:45 -0800183 ctx.stroke();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800184 }
Alex Perry5427c9a2020-02-15 17:43:45 -0800185
Alex Perryb49a3fb2020-02-29 15:26:54 -0800186 drawCamera(x: number, y: number, theta: number): void {
187 const ctx = this.canvas.getContext('2d');
188 ctx.save();
189 ctx.translate(x, y);
190 ctx.rotate(theta);
Alex Perry5427c9a2020-02-15 17:43:45 -0800191 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800192 ctx.moveTo(0.5, 0.5);
193 ctx.lineTo(0, 0);
James Kuszmaul71f91212021-09-25 17:35:48 -0700194 ctx.lineTo(100.0, 0);
195 ctx.lineTo(0, 0);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800196 ctx.lineTo(0.5, -0.5);
Alex Perry5427c9a2020-02-15 17:43:45 -0800197 ctx.stroke();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800198 ctx.beginPath();
Philipp Schradere625ba22020-11-16 20:11:37 -0800199 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800200 ctx.stroke();
201 ctx.restore();
202 }
203
James Kuszmaul5e6aa252021-08-28 22:19:29 -0700204 drawRobot(x: number, y: number, theta: number, turret: number|null): void {
Alex Perry2124ae82020-03-07 14:19:06 -0800205 const ctx = this.canvas.getContext('2d');
206 ctx.save();
207 ctx.translate(x, y);
208 ctx.rotate(theta);
209 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
210 ctx.stroke();
James Kuszmaul5e6aa252021-08-28 22:19:29 -0700211 if (turret) {
212 ctx.save();
213 ctx.rotate(turret + Math.PI);
214 const turretRadius = ROBOT_WIDTH / 4.0;
215 ctx.strokeStyle = "red";
216 // Draw circle for turret.
217 ctx.beginPath();
218 ctx.arc(0, 0, turretRadius, 0, 2.0 * Math.PI);
219 ctx.stroke();
220 // Draw line in circle to show forwards.
221 ctx.beginPath();
222 ctx.moveTo(0, 0);
James Kuszmaul71f91212021-09-25 17:35:48 -0700223 ctx.lineTo(1000.0 * turretRadius, 0);
James Kuszmaul5e6aa252021-08-28 22:19:29 -0700224 ctx.stroke();
225 ctx.restore();
226 }
Alex Perry2124ae82020-03-07 14:19:06 -0800227 ctx.beginPath();
228 ctx.moveTo(0, 0);
James Kuszmaul71f91212021-09-25 17:35:48 -0700229 ctx.lineTo(100.0 * ROBOT_LENGTH / 2, 0);
Alex Perry2124ae82020-03-07 14:19:06 -0800230 ctx.stroke();
231 ctx.restore();
232 }
233
Philipp Schradere625ba22020-11-16 20:11:37 -0800234 draw(): void {
Alex Perryb49a3fb2020-02-29 15:26:54 -0800235 this.reset();
236 this.drawField();
Philipp Schradere625ba22020-11-16 20:11:37 -0800237 // draw cameras
James Kuszmaul5e6aa252021-08-28 22:19:29 -0700238 for (const keyPair of this.imageMatchResult) {
239 const value = keyPair[1];
240 for (let i = 0; i < value.cameraPosesLength(); i++) {
241 const pose = value.cameraPoses(i);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800242 const mat = pose.fieldToCamera();
James Kuszmaul5e6aa252021-08-28 22:19:29 -0700243 // Matrix layout:
244 // [0, 1, 2, 3]
245 // [4, 5, 6, 7]
246 // [8, 9, 10, 11]
247 // [12, 13, 14, 15]
Alex Perryb49a3fb2020-02-29 15:26:54 -0800248 const x = mat.data(3);
249 const y = mat.data(7);
James Kuszmaul5e6aa252021-08-28 22:19:29 -0700250 const theta = Math.atan2(mat.data(6), mat.data(2));
Alex Perry2124ae82020-03-07 14:19:06 -0800251 this.drawCamera(x, y, theta);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800252 }
253 }
254
Alex Perry2124ae82020-03-07 14:19:06 -0800255 if (this.drivetrainStatus) {
256 this.drawRobot(
257 this.drivetrainStatus.x(), this.drivetrainStatus.y(),
James Kuszmaul5e6aa252021-08-28 22:19:29 -0700258 this.drivetrainStatus.theta(),
259 this.superstructureStatus ?
260 this.superstructureStatus.turret().position() :
261 null);
Alex Perry2124ae82020-03-07 14:19:06 -0800262 }
263
Alex Perryb49a3fb2020-02-29 15:26:54 -0800264 window.requestAnimationFrame(() => this.draw());
Alex Perry5427c9a2020-02-15 17:43:45 -0800265 }
266
267 reset(): void {
268 const ctx = this.canvas.getContext('2d');
269 ctx.setTransform(1, 0, 0, 1, 0, 0);
270 const size = window.innerHeight * 0.9;
271 ctx.canvas.height = size;
Alex Perryb49a3fb2020-02-29 15:26:54 -0800272 const width = size / 2 + 20;
273 ctx.canvas.width = width;
274 ctx.clearRect(0, 0, size, width);
Alex Perry5427c9a2020-02-15 17:43:45 -0800275
Alex Perryb49a3fb2020-02-29 15:26:54 -0800276 // Translate to center of display.
277 ctx.translate(width / 2, size / 2);
Alex Perry5427c9a2020-02-15 17:43:45 -0800278 // Coordinate system is:
279 // x -> forward.
280 // y -> to the left.
281 ctx.rotate(-Math.PI / 2);
282 ctx.scale(1, -1);
Alex Perry5427c9a2020-02-15 17:43:45 -0800283
284 const M_TO_PX = (size - 10) / FIELD_LENGTH;
285 ctx.scale(M_TO_PX, M_TO_PX);
286 ctx.lineWidth = 1 / M_TO_PX;
287 }
288}