blob: 1a757a0c95003474565fffbff49c0aba41da7e1e [file] [log] [blame]
Philipp Schrader817cce32022-03-26 15:00:00 -07001import {
2 Component,
3 ElementRef,
4 EventEmitter,
5 Input,
6 OnInit,
7 Output,
8 ViewChild,
9} from '@angular/core';
Ravago Jones2813c032022-03-16 23:44:11 -070010import {FormsModule} from '@angular/forms';
James Kuszmauldac091f2022-03-22 09:35:06 -070011import {Builder, ByteBuffer} from 'flatbuffers';
Philipp Schradere5d13942024-03-17 15:44:35 -070012import {ErrorResponse} from '@org_frc971/scouting/webserver/requests/messages/error_response_generated';
Philipp Schrader817cce32022-03-26 15:00:00 -070013import {
Filip Kujawa0ef334c2023-02-20 19:42:45 -080014 StartMatchAction,
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070015 StartMatchActionT,
Emily Markovaf17f2812024-04-03 20:55:12 -070016 NoShowActionT,
17 NoShowAction,
Emily Markovadcadcb62024-02-03 13:07:17 -080018 ScoreType,
19 StageType,
20 Submit2024Actions,
Filip Kujawa0b4b1e52023-04-15 14:05:40 -070021 MobilityAction,
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070022 MobilityActionT,
Emily Markovadcadcb62024-02-03 13:07:17 -080023 PenaltyAction,
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070024 PenaltyActionT,
Emily Markovadcadcb62024-02-03 13:07:17 -080025 PickupNoteAction,
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070026 PickupNoteActionT,
Emily Markovadcadcb62024-02-03 13:07:17 -080027 PlaceNoteAction,
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070028 PlaceNoteActionT,
Filip Kujawa0ef334c2023-02-20 19:42:45 -080029 RobotDeathAction,
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070030 RobotDeathActionT,
Filip Kujawa0ef334c2023-02-20 19:42:45 -080031 EndMatchAction,
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070032 EndMatchActionT,
Filip Kujawa0ef334c2023-02-20 19:42:45 -080033 ActionType,
34 Action,
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070035 ActionT,
Philipp Schradere5d13942024-03-17 15:44:35 -070036} from '@org_frc971/scouting/webserver/requests/messages/submit_2024_actions_generated';
37import {Match} from '@org_frc971/scouting/webserver/requests/messages/request_all_matches_response_generated';
Philipp Schraderad2a6fb2024-03-20 20:51:36 -070038import {
39 MatchListRequestor,
40 ActionsSubmitter,
41} from '@org_frc971/scouting/www/rpc';
Emily Markova521725a2024-03-21 18:46:04 -070042import {RequestCurrentScouting} from '@org_frc971/scouting/webserver/requests/messages/request_current_scouting_generated';
43import {RequestCurrentScoutingResponse} from '@org_frc971/scouting/webserver/requests/messages/request_current_scouting_response_generated';
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070044import {ActionHelper, ConcreteAction} from './action_helper';
Philipp Schradere2e27ff2024-02-25 22:08:55 -080045import * as pako from 'pako';
Philipp Schrader8b8ed672022-03-05 18:08:50 -080046
Philipp Schrader817cce32022-03-26 15:00:00 -070047type Section =
48 | 'Team Selection'
Filip Kujawa0ef334c2023-02-20 19:42:45 -080049 | 'Init'
50 | 'Pickup'
51 | 'Place'
52 | 'Endgame'
53 | 'Dead'
Philipp Schrader817cce32022-03-26 15:00:00 -070054 | 'Review and Submit'
Philipp Schradere2e27ff2024-02-25 22:08:55 -080055 | 'QR Code'
Philipp Schrader817cce32022-03-26 15:00:00 -070056 | 'Success';
Philipp Schrader80587432022-03-05 15:41:22 -080057
Emily Markova9c18e9c2024-04-03 20:06:27 -070058type CompType = 'PreScouting' | 'Practice' | 'Regular';
59
Philipp Schrader8aeb14f2022-04-08 21:23:18 -070060// TODO(phil): Deduplicate with match_list.component.ts.
61const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
Philipp Schraderba315da2024-03-17 16:16:50 -070062export type CompLevel = typeof COMP_LEVELS[number];
Philipp Schrader8aeb14f2022-04-08 21:23:18 -070063
64// TODO(phil): Deduplicate with match_list.component.ts.
65const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
66 qm: 'Qualifications',
67 ef: 'Eighth Finals',
68 qf: 'Quarter Finals',
69 sf: 'Semi Finals',
70 f: 'Finals',
71};
72
Philipp Schradere2e27ff2024-02-25 22:08:55 -080073// The maximum number of bytes per QR code. The user can adjust this value to
74// make the QR code contain less information, but easier to scan.
75const QR_CODE_PIECE_SIZES = [150, 300, 450, 600, 750, 900];
76
77// The default index into QR_CODE_PIECE_SIZES.
Philipp Schrader329b55a2024-04-18 09:42:59 -070078const DEFAULT_QR_CODE_PIECE_SIZE_INDEX = QR_CODE_PIECE_SIZES.indexOf(450);
Philipp Schradere2e27ff2024-02-25 22:08:55 -080079
Philipp Schrader3d7dedc2024-03-16 16:27:25 -070080// The actions that are purely used for tracking state. They don't actually
81// have any permanent meaning and will not be saved in the database.
82const STATE_ACTIONS: ActionType[] = [
83 ActionType.EndAutoPhaseAction,
84 ActionType.EndTeleopPhaseAction,
85];
emilym38d08ba2022-10-22 15:25:01 -070086
Philipp Schrader23993e82022-03-18 18:54:00 -070087@Component({
88 selector: 'app-entry',
89 templateUrl: './entry.ng.html',
Philipp Schrader175a93c2023-02-19 13:13:40 -080090 styleUrls: ['../app/common.css', './entry.component.css'],
Philipp Schrader23993e82022-03-18 18:54:00 -070091})
Philipp Schrader75021f52023-04-09 21:14:13 -070092export class EntryComponent implements OnInit {
Philipp Schrader36df73a2022-03-17 23:27:24 -070093 // Re-export the type here so that we can use it in the `[value]` attribute
94 // of radio buttons.
Philipp Schrader8aeb14f2022-04-08 21:23:18 -070095 readonly COMP_LEVELS = COMP_LEVELS;
96 readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
Philipp Schradere2e27ff2024-02-25 22:08:55 -080097 readonly QR_CODE_PIECE_SIZES = QR_CODE_PIECE_SIZES;
Emily Markovadcadcb62024-02-03 13:07:17 -080098 readonly ScoreType = ScoreType;
Emily Markova6079e2f2024-02-17 13:17:24 -080099 readonly StageType = StageType;
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700100 readonly ActionT = ActionT;
101 readonly ActionType = ActionType;
Emily Markovaf17f2812024-04-03 20:55:12 -0700102 readonly NoShowActionT = NoShowActionT;
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700103 readonly StartMatchActionT = StartMatchActionT;
104 readonly MobilityActionT = MobilityActionT;
105 readonly PickupNoteActionT = PickupNoteActionT;
106 readonly PlaceNoteActionT = PlaceNoteActionT;
107 readonly RobotDeathActionT = RobotDeathActionT;
108 readonly PenaltyActionT = PenaltyActionT;
109 readonly EndMatchActionT = EndMatchActionT;
Philipp Schrader36df73a2022-03-17 23:27:24 -0700110
Ravago Jones2813c032022-03-16 23:44:11 -0700111 section: Section = 'Team Selection';
Ravago Jones2813c032022-03-16 23:44:11 -0700112 @Input() matchNumber: number = 1;
Emily Markovae68b7632023-12-30 14:17:55 -0800113 @Input() teamNumber: string = '1';
Philipp Schrader30b4a682022-04-16 14:36:17 -0700114 @Input() setNumber: number = 1;
Philipp Schrader8aeb14f2022-04-08 21:23:18 -0700115 @Input() compLevel: CompLevel = 'qm';
Emily Markova9c18e9c2024-04-03 20:06:27 -0700116 @Input() compType: CompType = 'Regular';
Philipp Schrader75021f52023-04-09 21:14:13 -0700117 @Input() skipTeamSelection = false;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800118
Philipp Schrader63198402024-03-16 14:19:02 -0700119 @ViewChild('header') header: ElementRef;
120
Philipp Schrader8702b782023-04-15 17:33:37 -0700121 matchList: Match[] = [];
122
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700123 actionHelper: ActionHelper;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800124 actionList: ActionT[] = [];
Philipp Schrader8702b782023-04-15 17:33:37 -0700125 progressMessage: string = '';
Ravago Jones2813c032022-03-16 23:44:11 -0700126 errorMessage: string = '';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800127 autoPhase: boolean = true;
Filip Kujawab73e94c2023-04-19 09:33:14 -0700128 mobilityCompleted: boolean = false;
Philipp Schraderba315da2024-03-17 16:16:50 -0700129 // TODO(phil): Come up with a better name here.
Evelyn Yangc8036b12023-10-11 21:14:46 -0700130 selectedValue = 0;
Philipp Schraderba315da2024-03-17 16:16:50 -0700131 endGameAction: StageType = StageType.kMISSING;
132 noteIsTrapped: boolean = false;
133 endGameSpotlight: boolean = false;
134
Evelyn Yangc8036b12023-10-11 21:14:46 -0700135 nextTeamNumber = '';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800136
137 matchStartTimestamp: number = 0;
Emily Markovadcadcb62024-02-03 13:07:17 -0800138 penalties: number = 0;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800139
Philipp Schrader8702b782023-04-15 17:33:37 -0700140 teamSelectionIsValid = false;
Emily Markova521725a2024-03-21 18:46:04 -0700141 searchForDuplicateScouting: boolean = false;
142 duplicateSearchTimer: ReturnType<typeof setTimeout> | null = null;
Philipp Schrader8702b782023-04-15 17:33:37 -0700143
Philipp Schradere2e27ff2024-02-25 22:08:55 -0800144 // When the user chooses to generate QR codes, we convert the flatbuffer into
145 // a long string. Since we frequently have more data than we can display in a
146 // single QR code, we break the data into multiple QR codes. The data for
147 // each QR code ("pieces") is stored in the `qrCodeValuePieces` list below.
148 // The `qrCodeValueIndex` keeps track of which QR code we're currently
149 // displaying.
150 qrCodeValuePieceSize = QR_CODE_PIECE_SIZES[DEFAULT_QR_CODE_PIECE_SIZE_INDEX];
151 qrCodeValuePieces: string[] = [];
152 qrCodeValueIndex: number = 0;
153
Philipp Schraderad2a6fb2024-03-20 20:51:36 -0700154 constructor(
155 private readonly matchListRequestor: MatchListRequestor,
156 private readonly actionsSubmitter: ActionsSubmitter
157 ) {}
Philipp Schrader8702b782023-04-15 17:33:37 -0700158
Philipp Schrader75021f52023-04-09 21:14:13 -0700159 ngOnInit() {
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700160 this.actionHelper = new ActionHelper(
161 (actionType: ActionType, action: ConcreteAction) => {
162 this.addAction(actionType, action);
163 }
164 );
165
Philipp Schrader75021f52023-04-09 21:14:13 -0700166 // When the user navigated from the match list, we can skip the team
167 // selection. I.e. we trust that the user clicked the correct button.
168 this.section = this.skipTeamSelection ? 'Init' : 'Team Selection';
Evelyn Yangc8036b12023-10-11 21:14:46 -0700169 this.fetchMatchList();
170 }
Philipp Schrader8702b782023-04-15 17:33:37 -0700171
Emily Markova521725a2024-03-21 18:46:04 -0700172 ngOnDestroy() {
173 clearInterval(this.duplicateSearchTimer);
174 }
175
Evelyn Yangc8036b12023-10-11 21:14:46 -0700176 goToNextTeam() {
177 this.ngOnInit();
Emily Markova521725a2024-03-21 18:46:04 -0700178 this.ngOnDestroy();
Evelyn Yangc8036b12023-10-11 21:14:46 -0700179 this.teamNumber = this.nextTeamNumber;
180 this.nextTeamNumber = '';
Philipp Schrader8702b782023-04-15 17:33:37 -0700181 }
182
183 async fetchMatchList() {
184 this.progressMessage = 'Fetching match list. Please be patient.';
185 this.errorMessage = '';
186
187 try {
188 this.matchList = await this.matchListRequestor.fetchMatchList();
189 this.progressMessage = 'Successfully fetched match list.';
190 } catch (e) {
191 this.errorMessage = e;
192 this.progressMessage = '';
193 }
194 }
195
Emily Markova521725a2024-03-21 18:46:04 -0700196 async findOthersScoutingThisRobot() {
197 const builder = new Builder();
198 const teamNumber = builder.createString(this.teamNumber);
199
200 builder.finish(
201 RequestCurrentScouting.createRequestCurrentScouting(builder, teamNumber)
202 );
203
204 const buffer = builder.asUint8Array();
205 const res = await fetch('/requests/request/current_scouting', {
206 method: 'POST',
207 body: buffer,
208 });
209
210 if (!res.ok) {
211 const resBuffer = await res.arrayBuffer();
212 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
213 const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
214 const errorMessage = parsedResponse.errorMessage();
215 this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
216 } else {
217 const resBuffer = await res.arrayBuffer();
218 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
219 const parsedResponse =
220 RequestCurrentScoutingResponse.getRootAsRequestCurrentScoutingResponse(
221 fbBuffer
222 );
223 const collectedBy = [];
224 for (let i = 0; i < parsedResponse.collectedByLength(); i++) {
225 collectedBy.push(parsedResponse.collectedBy(i).name());
226 }
227 this.duplicateSearchTimer = setInterval(() => {
228 if (this.searchForDuplicateScouting && collectedBy.length != 0) {
229 if (
230 confirm(
231 'This team is currently being scouted by ' +
232 collectedBy +
233 '. Would you like to receive more alerts?'
234 ) != true
235 ) {
236 this.searchForDuplicateScouting = false;
237 }
238 }
239 }, 10000);
240 }
241 }
242
Philipp Schrader8702b782023-04-15 17:33:37 -0700243 // This gets called when the user changes something on the Init screen.
244 // It makes sure that the user can't click "Next" until the information is
Emily Markova9c18e9c2024-04-03 20:06:27 -0700245 // valid, or this is for pre-scouting or practice matches.
Philipp Schrader8702b782023-04-15 17:33:37 -0700246 updateTeamSelectionValidity(): void {
Emily Markova9c18e9c2024-04-03 20:06:27 -0700247 this.teamSelectionIsValid =
248 this.compType != 'Regular' || this.matchIsInMatchList();
Philipp Schrader8702b782023-04-15 17:33:37 -0700249 }
250
251 matchIsInMatchList(): boolean {
252 // If the user deletes the content of the teamNumber field, the value here
253 // is undefined. Guard against that.
254 if (this.teamNumber == null) {
255 return false;
256 }
Philipp Schrader8702b782023-04-15 17:33:37 -0700257
258 for (const match of this.matchList) {
259 if (
260 this.matchNumber == match.matchNumber() &&
261 this.setNumber == match.setNumber() &&
262 this.compLevel == match.compLevel() &&
Evelyn Yangc8036b12023-10-11 21:14:46 -0700263 (this.teamNumber === match.r1() ||
264 this.teamNumber === match.r2() ||
265 this.teamNumber === match.r3() ||
266 this.teamNumber === match.b1() ||
267 this.teamNumber === match.b2() ||
268 this.teamNumber === match.b3())
Philipp Schrader8702b782023-04-15 17:33:37 -0700269 ) {
270 return true;
271 }
272 }
273 return false;
Philipp Schrader75021f52023-04-09 21:14:13 -0700274 }
275
Emily Markovadcadcb62024-02-03 13:07:17 -0800276 addPenalty(): void {
277 this.penalties += 1;
278 }
279
280 removePenalty(): void {
281 if (this.penalties > 0) {
282 this.penalties -= 1;
283 }
284 }
285
286 addPenalties(): void {
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700287 this.actionHelper.addPenaltyAction({penalties: this.penalties});
Emily Markovadcadcb62024-02-03 13:07:17 -0800288 }
289
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700290 addAction(actionType: ActionType, action: ConcreteAction): void {
291 let timestamp: number = 0;
292
293 if (actionType == ActionType.StartMatchAction) {
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800294 // Unix nanosecond timestamp.
295 this.matchStartTimestamp = Date.now() * 1e6;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800296 } else {
297 // Unix nanosecond timestamp relative to match start.
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700298 timestamp = Date.now() * 1e6 - this.matchStartTimestamp;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800299 }
300
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700301 if (actionType == ActionType.EndMatchAction) {
Philipp Schradere2e27ff2024-02-25 22:08:55 -0800302 // endMatchAction occurs at the same time as penaltyAction so add to its
303 // timestamp to make it unique.
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700304 timestamp += 1;
Emily Markovadcadcb62024-02-03 13:07:17 -0800305 }
306
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700307 if (actionType == ActionType.MobilityAction) {
Filip Kujawab73e94c2023-04-19 09:33:14 -0700308 this.mobilityCompleted = true;
309 }
310
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700311 this.actionList.push(new ActionT(BigInt(timestamp), actionType, action));
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800312 }
313
314 undoLastAction() {
315 if (this.actionList.length > 0) {
316 let lastAction = this.actionList.pop();
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700317 switch (lastAction?.actionTakenType) {
318 case ActionType.EndAutoPhaseAction:
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800319 this.autoPhase = true;
Emily Markovadcadcb62024-02-03 13:07:17 -0800320 this.section = 'Pickup';
Philipp Schrader652a4c92024-04-18 10:53:39 -0700321 break;
Emily Markovaf17f2812024-04-03 20:55:12 -0700322 case ActionType.NoShowAction:
323 this.autoPhase = true;
324 this.section = 'Init';
325 break;
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700326 case ActionType.PickupNoteAction:
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800327 this.section = 'Pickup';
328 break;
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700329 case ActionType.EndTeleopPhaseAction:
Emily Markovadcadcb62024-02-03 13:07:17 -0800330 this.section = 'Pickup';
331 break;
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700332 case ActionType.PlaceNoteAction:
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800333 this.section = 'Place';
334 break;
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700335 case ActionType.EndMatchAction:
Emily Markovadcadcb62024-02-03 13:07:17 -0800336 this.section = 'Endgame';
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700337 case ActionType.MobilityAction:
Emily Markovadcadcb62024-02-03 13:07:17 -0800338 this.mobilityCompleted = false;
339 break;
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700340 case ActionType.StartMatchAction:
Emily Markovadcadcb62024-02-03 13:07:17 -0800341 this.section = 'Init';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800342 break;
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700343 case ActionType.RobotDeathAction:
Filip Kujawa9f56d0e2023-03-03 19:44:43 -0800344 // TODO(FILIP): Return user to the screen they
345 // clicked dead robot on. Pickup is fine for now but
346 // might cause confusion.
347 this.section = 'Pickup';
348 break;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800349 default:
350 break;
351 }
352 }
353 }
354
Emily Markovadcadcb62024-02-03 13:07:17 -0800355 stringifyScoreType(scoreType: ScoreType): String {
356 return ScoreType[scoreType];
Emily Markovaf4b06a22023-05-10 17:44:09 -0700357 }
358
Emily Markovadcadcb62024-02-03 13:07:17 -0800359 stringifyStageType(stageType: StageType): String {
360 return StageType[stageType];
Emily Markovaf4b06a22023-05-10 17:44:09 -0700361 }
362
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800363 changeSectionTo(target: Section) {
Philipp Schrader8702b782023-04-15 17:33:37 -0700364 // Clear the messages since they won't be relevant in the next section.
365 this.errorMessage = '';
366 this.progressMessage = '';
367
Philipp Schradere2e27ff2024-02-25 22:08:55 -0800368 // For the QR code screen, we need to make the value to encode available.
369 if (target == 'QR Code') {
370 this.updateQrCodeValuePieceSize();
371 }
372
Emily Markova521725a2024-03-21 18:46:04 -0700373 if (target == 'Init') {
374 this.searchForDuplicateScouting = true;
375 this.findOthersScoutingThisRobot();
376 }
377
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800378 this.section = target;
379 }
Philipp Schrader80587432022-03-05 15:41:22 -0800380
Ravago Jones2813c032022-03-16 23:44:11 -0700381 private scrollToTop() {
382 this.header.nativeElement.scrollIntoView();
383 }
384
Philipp Schradere2e27ff2024-02-25 22:08:55 -0800385 createActionsBuffer() {
James Kuszmauldac091f2022-03-22 09:35:06 -0700386 const builder = new Builder();
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800387 const actionOffsets: number[] = [];
388
389 for (const action of this.actionList) {
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700390 if (STATE_ACTIONS.includes(action.actionTakenType)) {
391 // Actions only used for undo purposes are not submitted.
392 continue;
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800393 }
Philipp Schrader3d7dedc2024-03-16 16:27:25 -0700394 actionOffsets.push(action.pack(builder));
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800395 }
Emily Markovae68b7632023-12-30 14:17:55 -0800396 const teamNumberFb = builder.createString(this.teamNumber);
Philipp Schradere859e6e2023-03-22 19:59:51 -0700397 const compLevelFb = builder.createString(this.compLevel);
Emily Markova9c18e9c2024-04-03 20:06:27 -0700398 const compTypeFb = builder.createString(this.compType);
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800399
Emily Markovadcadcb62024-02-03 13:07:17 -0800400 const actionsVector = Submit2024Actions.createActionsListVector(
Philipp Schrader817cce32022-03-26 15:00:00 -0700401 builder,
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800402 actionOffsets
Philipp Schrader817cce32022-03-26 15:00:00 -0700403 );
Emily Markovadcadcb62024-02-03 13:07:17 -0800404 Submit2024Actions.startSubmit2024Actions(builder);
405 Submit2024Actions.addTeamNumber(builder, teamNumberFb);
406 Submit2024Actions.addMatchNumber(builder, this.matchNumber);
407 Submit2024Actions.addSetNumber(builder, this.setNumber);
408 Submit2024Actions.addCompLevel(builder, compLevelFb);
409 Submit2024Actions.addActionsList(builder, actionsVector);
Emily Markova9c18e9c2024-04-03 20:06:27 -0700410 Submit2024Actions.addCompType(builder, compTypeFb);
Emily Markovadcadcb62024-02-03 13:07:17 -0800411 builder.finish(Submit2024Actions.endSubmit2024Actions(builder));
Ravago Jones2813c032022-03-16 23:44:11 -0700412
Philipp Schradere2e27ff2024-02-25 22:08:55 -0800413 return builder.asUint8Array();
414 }
415
416 // Same as createActionsBuffer, but encoded as Base64. It's also split into
417 // a number of pieces so that each piece is roughly limited to
418 // `qrCodeValuePieceSize` bytes.
419 createBase64ActionsBuffers(): string[] {
420 const originalBuffer = this.createActionsBuffer();
421 const deflatedData = pako.deflate(originalBuffer, {level: 9});
422
423 const pieceSize = this.qrCodeValuePieceSize;
424 const fullValue = btoa(String.fromCharCode(...deflatedData));
425 const numPieces = Math.ceil(fullValue.length / pieceSize);
426
427 let splitData: string[] = [];
428 for (let i = 0; i < numPieces; i++) {
429 const splitPiece = fullValue.slice(i * pieceSize, (i + 1) * pieceSize);
430 splitData.push(`${i}_${numPieces}_${pieceSize}_${splitPiece}`);
431 }
432 return splitData;
433 }
434
435 setQrCodeValueIndex(index: number) {
436 this.qrCodeValueIndex = Math.max(
437 0,
438 Math.min(index, this.qrCodeValuePieces.length - 1)
439 );
440 }
441
442 updateQrCodeValuePieceSize() {
443 this.qrCodeValuePieces = this.createBase64ActionsBuffers();
444 this.qrCodeValueIndex = 0;
445 }
446
447 async submit2024Actions() {
Philipp Schraderad2a6fb2024-03-20 20:51:36 -0700448 const res = await this.actionsSubmitter.submit(this.createActionsBuffer());
Ravago Jones2813c032022-03-16 23:44:11 -0700449
450 if (res.ok) {
451 // We successfully submitted the data. Report success.
452 this.section = 'Success';
Filip Kujawa0ef334c2023-02-20 19:42:45 -0800453 this.actionList = [];
Evelyn Yangc8036b12023-10-11 21:14:46 -0700454
Philipp Schrader63198402024-03-16 14:19:02 -0700455 // Keep track of the position of the last robot, use to figure out what
456 // the next robot in the same position is.
Evelyn Yangc8036b12023-10-11 21:14:46 -0700457 let lastTeamPos = '0';
458 for (const match of this.matchList) {
459 if (
460 this.matchNumber === match.matchNumber() &&
461 this.setNumber === match.setNumber() &&
462 this.compLevel === match.compLevel()
463 ) {
464 this.teamNumber = this.teamNumber;
465 if (this.teamNumber == match.r1()) {
466 lastTeamPos = 'r1';
467 } else if (this.teamNumber == match.r2()) {
468 lastTeamPos = 'r2';
469 } else if (this.teamNumber == match.r3()) {
470 lastTeamPos = 'r3';
471 } else if (this.teamNumber == match.b1()) {
472 lastTeamPos = 'b1';
473 } else if (this.teamNumber == match.b2()) {
474 lastTeamPos = 'b2';
475 } else if (this.teamNumber == match.b3()) {
476 lastTeamPos = 'b3';
477 } else {
478 console.log('Position of scouted team not found.');
479 }
480 break;
481 }
482 }
483 if (lastTeamPos != '0') {
484 this.matchNumber += 1;
485 for (const match of this.matchList) {
486 if (
487 this.matchNumber == match.matchNumber() &&
488 this.setNumber == match.setNumber() &&
489 this.compLevel == match.compLevel()
490 ) {
491 if (lastTeamPos == 'r1') {
492 this.nextTeamNumber = match.r1();
493 } else if (lastTeamPos == 'r2') {
494 this.nextTeamNumber = match.r2();
495 } else if (lastTeamPos == 'r3') {
496 this.nextTeamNumber = match.r3();
497 } else if (lastTeamPos == 'b1') {
498 this.nextTeamNumber = match.b1();
499 } else if (lastTeamPos == 'b2') {
500 this.nextTeamNumber = match.b2();
501 } else if (lastTeamPos == 'b3') {
502 this.nextTeamNumber = match.b3();
503 } else {
504 console.log('Position of last team not found.');
505 }
506 break;
507 }
508 }
509 } else {
510 console.log('Last team position not found.');
511 }
512 this.matchList = [];
513 this.progressMessage = '';
514 this.errorMessage = '';
515 this.autoPhase = true;
516 this.actionList = [];
517 this.mobilityCompleted = false;
Emily Markova9c18e9c2024-04-03 20:06:27 -0700518 this.compType = 'Regular';
Evelyn Yangc8036b12023-10-11 21:14:46 -0700519 this.matchStartTimestamp = 0;
520 this.selectedValue = 0;
Emily Markova521725a2024-03-21 18:46:04 -0700521 this.searchForDuplicateScouting = false;
Ravago Jones2813c032022-03-16 23:44:11 -0700522 } else {
523 const resBuffer = await res.arrayBuffer();
524 const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
James Kuszmauldac091f2022-03-22 09:35:06 -0700525 const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
Ravago Jones2813c032022-03-16 23:44:11 -0700526
527 const errorMessage = parsedResponse.errorMessage();
Philipp Schrader817cce32022-03-26 15:00:00 -0700528 this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
Alex Perrybb3d2062022-03-05 18:14:33 -0800529 }
Ravago Jones2813c032022-03-16 23:44:11 -0700530 }
Philipp Schrader80587432022-03-05 15:41:22 -0800531}