-
Notifications
You must be signed in to change notification settings - Fork 0
/
JSRoboScout.html
1348 lines (1148 loc) · 51.4 KB
/
JSRoboScout.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Robot Scouting</title>
<style type="text/css" >
dl dt {
font-family: monospace;
}
tr.baddata {
background-color: red;
}
</style>
</head>
<body>
<h1>Robot Scouting</h1>
<h2>Introduction</h2>
<p>
Robot scouting is important, but choosing the scouting details can be difficult.
</p>
<h2>Types of Scouting</h2>
<p>
There are two common types of scouting.
The first type of scouting is pit scouting, where scouts visit the pits, look at the robot, ask questions, and make judgments.
Pit scouting may not be very reliable because teams may give an overly optimistic report of their robot.
The second type of scouting is match scouting.
With more detailed match reporting by FIRST, some details may not be needed.
</p>
<p>
A needed focus is how the data will be used.
Will it be used to decide strategy in a qualification match?
For example, Greybots, team 973, would know the opposing teams capabilities long before the match started.
If none of the opposition can reach the scale, then that can influence match strategy.
</p>
<p>
Teams need not do extensive scouting early on.
A team could just look ahead to the next match, and check out the oppositions robots.
Then, when the team's alliance meets, they can offer a little insight into the opposing alliance.
</p>
<p>
Notion that only teams in the top 8 (or top 4 in FTC) need to scout because only those teams will be
selecting alliance partners.
</p>
<p>
Some teams will also visit the pits of their future alliance partners and offer help.
Teams can help any other team out, but it can be advantageous to focus on alliance mates.
971 has done things such as loaning batteries to alliance mates (some teams do not show up with enough batteries).
</p>
<p>
A rookie team, Technical Support 7419, had a very developed scouting application, but the application did not present results well.
Technical Support was willing to take robots with problems if those problems had apparently been fixed.
Bellarmine 254, on the other hand, wanted to see solid reliability.
If a robot had trouble in early rounds, then other troubles may arise after early troubles are fixed.
Generally, robot teams need to have working hardware early, and they need to exercise that hardware to find and exorcise the bugs.
</p>
<h2>Pit Scouting</h2>
<p>
Develop a form, post the data to a URL, and then present the results.
TBA can be used to fill in some details.
The primary focus of pit scouting should probably be scoring and defense abilities.
Any claims by the scouted team should be evaluated.
Alliance mates have claimed to score 12 game pieces, but during an actual match only scored 4.
Maybe during one practice run the team scored 12 pieces, but question is how the team performs in a typical match against
typical opposition.
</p>
<p>
Pit scouting can be done on paper.
High tech is not needed.
Results can be entered into a computer later.
Citrus Circuits would scout on tablets, and then use low tech communications to convey that information.
One of the problems at matches is teams are not allowed to set up their own WiFi networks.
</p>
<p>
There are judgments to make.
Is the robot well built and rugged or will it fall apart if you breathe on it?
Did the designers make reasonable choices?
Is the robot heavy and cumbersome for its tasks?
</p>
<p>
From the pit scouting report, one should be able to estimate the scoring potential.
For Deep Space, starting sandstorm in H2 is 6 points, returning to H3 is 12 points for 18 points.
A cycle time of 15 seconds gives 8 scores in 120 seconds; if they are mostly cargo, that is 24 points.
So scoring potential is 42 points.
Data also shows threats such as completing a rocket ship or a 15-point HAB for additional ranking points.
That may mean an alliance must defend against such a powerful robot.
Alternatively, a cycle time of 30 seconds means 4 scores; if the robot drops hatch panels or cargo
or frequently fails to
score game pieces, then the robot is not an offensive threat but may become a defender.
</p>
<p>
For some games, the scoring potential is not so clear.
Power Up does not have a clear correlation.
A robot may be able to deliver power cubes fast, but if the opposition delivers them faster,
the robot will not score any points.
Deep Space does have a clear correlation between delivering game pieces and scoring.
If a robot places a patch panel somewhere, that is worth 2 points; delivering cargo is worth 3 points.
</p>
<pre>
Weight
credibility assessment
when was robot ready to drive? Indicates time to debug problems.
how much driver practice (would be good to see if team doing practice matches at the venue)
Drive train: (CIM, mini CIM, 775, Neo, Falcon)
West Coast Drive (center drop, omni wheels)
Mecanum
Swerve
Other
top speed (m/s)
Vision capability
Number of batteries, Number of chargers (shows maturity of the team and expectation)
Elevator (fixed stops)
Arm
Wrist
Turret
Shooter (single sheel, double wheel, linear, hooded)
Many questions are specific to a game.
Point scoring
Hatch Panels: 0, level 1, 2, or 3; alignment method
Hatch Panel from ground.
Hatch Panel alignment speed/method/other metric
How often drop hatch panel (P{drop hatch panel})
Cargo: 0, level 1, 2, or 3
Pickup cargo from Ground
Pickup cargo from Depot (back corner often difficult)
Pickup cargo from Alliance Wall
cycle time
Autodeliver routine (fire and forget)
Can tell if bay already loaded?
Sandstorm
start H1 (3 points) or H2 (6 points) (leap or controlled)
Deliver Hatch Panel or Cargo (possibly 2 items during sandstorm)
Frame Rate 5 10 15 30 60
Pix Resolution 160, 320, 480, 640, 1920 (questions so we can believe 4 Mb/s data rate)
DME
CEP 1 cm, 2 cm, 5 cm
Vision targets; acquistion time (eg, 170 ms)
Flood/Lime light
18 inch white line follower (faster response).
Endgame
climb method. (piston, reuse intake as travel motor, travel motor, broken ankle, Dukes of Hazard).
H1 (3 points), H2 (6 points), H3 (12 points).
time needed to climb. (also gives assessment of reliability if they waffle)
P{climb}
Lift others?
</pre>
<h2>Match Scouting</h2>
<p>
Match scouting shows the robot in action, so the robot's abilities should be more apparent.
If it is catatonic, that's a clear problem.
If it cannot score or takes a long time to score, that's a problem.
If it starts dropping game pieces, that is a problem.
One could view match scouting as updating the pit scouting with more accurate information.
</p>
<p>
Other tactical or strategic information may come out.
A robot may have a method of defense that is particularly effective.
During Power Up, hitting the hind quarter of the robot just as it was about to deliver a power cube was effective.
Instead of blocking, sometimes just getting in the way was good defense.
Pinning robots at their alliance wall was also effective; it would take the pinned robot several seconds to recover, and
once it had recovered, the defender could reexecute a pin.
Such observations are general; they need not apply to a specific team.
It might be good to choose a team that defends well, but almost any team can use good tactics.
</p>
<p>
Some match scouting may be done by FIRST.
It looks like the match report has enough information to determine that R1 of the Blue Alliance
left the HAB during sandstorm and climbed to level 2 during endgame.
If that is the case, then obtain those statistics from FIRST rather than entering them.
</p>
<h2>Application Programming Interfaces</h2>
<p>
There are APIs for both FRC and FTC.
</p>
<p>
A technical issue is how to secure the passwords used by the various APIs.
The passwords should not be visible in the source.
A good way to do this for a client-side program is not clear.
Cookies are possible, and local storage is also possible.
However, development can be trouble.
I do not see <code>localhost</code> maintaining local storage
across browser starts.
Server side is simpler.
</p>
<p>
Some hacks to implement:
</p>
<ul>
<li>Check all games and find highest non-penalty score</li>
<li>check all games and find most power cells scored</li>
<li>For my event, check all teams and find how they did in previous events</li>
</ul>
<h3>Field Management System</h3>
<p>
During FIRST Events, the field keeps track of many scoring details such as counting balls
or deterining the tilt of beams.
Referees are also entering data such as whether a robot moved enough during autonomous or fouled.
</p>
<p>
I am seeing a lot of bad data.
Usually it is a small number of matches, but 2022 Arizona North is a mess.
qm15, qm16, qm18, qm22, qm26, qm29, qm40, qm41, qm46, qm47, qm48, qm49, qm50, qm51, qm52, qm53, qm54, qm55,
qm56, qm57, qm58, qm59, qm60, qm61, qm62, qm63, qm64.
See <a href="https://www.thebluealliance.com/match/2022azfl_qm50">TBA 2022azfl_qm50</a> and
<a href="https://frc-events.firstinspires.org/2022/AZFL/qualifications/50">FI AZFL/qualifications/50</a>.
During teleop, red alliance scored 5 lower cargo and 2 upper for 10 points; that should be nine points.
Is one of the lower cargo being scored as two points (a late autononmous)?
</p>
<p>
More typically, the bad data matches have cargo counts of zero.
Heuneme Port Regional qm15 (I think this was an adjustment).
Ventura County Regional qm25, qm32.
</p>
<h3>The FIRST API</h3>
<p>
FIRST has a interface to obtain match information.
See <a href="https://frc-events.firstinspires.org/services/API">https://frc-events.firstinspires.org/services/API</a>.
See <a href="https://frcevents2.docs.apiary.io/">https://frcevents2.docs.apiary.io/</a>.
The data may be returned as either <code>application/xml</code> or <code>application/json</code>.
There is a mock server that does not do validation.
FIRST has stopped using Apiary, so all of this must be revisited.
</p>
<p>
<a href="https://frc-api-docs.firstinspires.org/">frc-api-docs.firstinspires.org</a>
</p>
<p>
The <code>Last-Modified</code> and <code>If-Modified-Since</code> headers woul be handled by the caching system.
There are better methods now.
Some APIs doe not provide reasonable maximum age for cached objects.
</p>
<p>
An <code>Authorization</code> header is needed.
<code>Authorization: Basic 000000000000000000000000000000000000000000000000000000000000</code>
Where the zeros are <code>username:authorizationKey</code>,
(e.g., <code>sampleuser:7eaa6338-a097-4221-ac04-b6120fcc4d49</code>)
but the whole mess is Base64 encoded to get:
</p>
<p>
<code>Authorization: Basic c2FtcGxldXNlcjo3ZWFhNjMzOC1hMDk3LTQyMjEtYWMwNC1iNjEyMGZjYzRkNDk=</code>
</p>
<p><code id="testbtoa">FAILED!</code></p>
<p>
To use the API, do
<code>xhr.setRequestHeader("Authorization", "Basic " + btoa(str));</code>.
</p>
<button type="button" onclick="foo()">Try a read</button>
<p>
A read from a <code>file:</code> runs into CORS.
The request uses origin null, so the request dies.
It worked from a server, but now a real key is needed.
</p>
<p>
FIRST no longer uses Apiary.
The API now has a version 3.
</p>
<script>
testbtoa.textContent = "Authorization: Basic " + btoa("sampleuser:7eaa6338-a097-4221-ac04-b6120fcc4d49");
function foo() {
// authorization information
var userAPI = "sampleuser";
var keyAPI = "7eaa6338-a097-4221-ac04-b6120fcc4d49";
// make a new request
var xhr = new XMLHttpRequest();
var url = new URL("https://frc-api.firstinspires.org/");
// use the mock server
url = new URL("https://private-anon-84ec3797bf-frcevents2.apiary-mock.com/v2.0/2020/alliances/CPCMO");
xhr.responseType = "json";
xhr.onerror = function (e) {
console.log("ERROR");
console.log(e);
}
xhr.onload = function () {
console.log("finished");
console.log(xhr.response);
}
xhr.open("GET", url);
// headers are set after .open()
// I presume they are encrypted for https....
// authorization
xhr.setRequestHeader("Authorization", "Basic " + btoa(userAPI + ":" + keyAPI));
// I need to set this to get JSON
xhr.setRequestHeader("Accept", "application/json");
// send the request...
xhr.send();
}
</script>
<h3>The Blue Alliance API</h3>
<p>
The Blue Alliance offers an APIv3 to access team, event, and match information.
<a href="https://www.thebluealliance.com/apidocs/v3">The Blue Alliance APIv3</a>.
<a href="https://blog.thebluealliance.com/2017/11/10/tech-talk-efficiently-querying-the-tba-api/">
Tech Talk: Efficiently Querying the TBA API
</a>: Set the <code>X-TBA-Auth-Key: AUTH_KEY_GOES_HERE</code> header rather than the query string
so the cache will hit with the URL.
Somewhere there should be a flag that says do not cache;
see <a href="https://blog.httpwatch.com/2011/01/28/top-7-myths-about-https/">Top 7 Myths about HTTPS</a>.
</p>
<dl>
<dt>/events/{year}</dt>
<dd>array of events with detailed information</dd>
<dt>/event/{eventkey}/teams</dt>
<dd>2018casf array of objects with key:{team}</dd>
<dt>/team/{teamkey}/events</dt>
<dd>frc972 array of objects with year: week:</dd>
<dt>/event/{eventkey}/matches</dt>
<dd>array of objects with actualtime: key: comp_level: match_number: </dd>
<dt>/event/{eventkey}/oprs</dt>
<dd>object oprs: {teamkey: opr} </dd>
</dl>
<h2>Regression</h2>
<p>
Infer abilities.
The match data identifies members of the alliance and scoring values
for team1, team2, and team3.
Is that mapping 1-to-1?
That would give us absolute data on autonomous movement and endgame climb.
CalGames 2021 with Funky Monkeys might give insight.
Also the scoring (phone call - lost train of thought).
</p>
<p>
What is a good way to handle secret keys?
Environment data?
Put it in the server data?
Put in a cookie?
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a>.
</p>
<p>
Find good way to handle year-specific match data.
</p>
<h2>References</h2>
<p>
<a href="https://arxiv.org/pdf/1810.05763.pdf">
Estimating Robot Strengths with Application to Selection of Alliance Members in FIRST Robotics Competitions
</a>
Alejandro Lim, Chin-Tsang Chiang, Jen-Chieh Teng, November 30, 2018.
Discusses mathematical metrics.
</p>
<h2>Form</h2>
<form>
<fieldset>
<legend>Event</legend>
<label>
Year:
<select id="yearEvent" type="text" value="2019" onchange="yearChange(this)">
<option value="2010">2010 Breakaway</option>
<option value="2011">2011 Logo Motion</option>
<option value="2012">2012 Rebound Rumble</option>
<option value="2013">2013 Ultimate Ascent</option>
<option value="2014">2014 Aerial Assist</option>
<option value="2015">2015 Recycle Rush</option>
<option value="2016">2016 FIRST Stronghold</option>
<option value="2017">2017 FIRST Steamworks</option>
<option value="2018">2018 FIRST Power Up</option>
<option value="2019">2019 Destination: Deep Space</option>
<option value="2020">2020 Infinite Recharge</option>
<option value="2021">2021 Infinite Recharge (2021)</option>
<option value="2022" selected>2022 Rapid React</option>
<option value="2023">2023</option>
</select>
</label><br />
<label>Event: <select id="selEvent" onchange="eventChange(this)"></select></label><br />
<label>Week: <input id="weekEvent" type="text" size="4" readonly /></label>
<label>Start: <input id="dateEventStart" type="date" readonly /></label><br />
<label>Location: <input id="locationName" type="text" size="64" readonly /></label><br />
<label>Location: <input id="locationEvent" type="text" size="64" readonly /></label>
</fieldset>
<fieldset>
<legend>API Key</legend>
<label>TBA key: <input id="inputKeyTBA" type="text" size="64" autocomplete="on"/></label>
</fieldset>
</form>
<table>
<thead>
<tr>
<th>Team</th>
<th>Nickname</th>
<th>City</th>
<th>State</th>
</tr>
</thead>
<tbody id="teamTable">
</tbody>
</table>
<p>
Here we could have the 8 alliances.
Double clicking an entry in the table below should add to the next available alliance and remove from
the table of available teams.
I should be able to do an undo a selection. Maybe repeatedly.
In addition, the foot of the table should sum the parameters.
After the alliances are selected, we could automatically fill the table.
</p>
<h2>Team Rankings</h2>
<p>
Teams and their rankings.
</p>
<table>
<thead>
<tr>
<th>Team</th>
<th><abbr title="Offensive Power Rating" onclick="tsort(1)">OPR</abbr></th>
<th><abbr title="Defensive Power Rating" onclick="tsort(2)">DPR</abbr></th>
<th><abbr title="Calculated Contribution to the Winning Margin" onclick="tsort(3)">CCWM</abbr></th>
</tr>
</thead>
<tbody id="opr">
</tbody>
</table>
<h2>Team View</h2>
<p>
Trying to give more detailed summary of performance.
</p>
<button type="button" onclick="fillVision()">Fill Table</button>
<table id="tableVision">
<caption>Team Performance Summary</caption>
<thead>
<tr><th>Team</th><th>Number</th><th colspan="11">Match Data</th></tr>
</thead>
</table>
<h2>Matches</h2>
<table>
<thead>
<tr>
<th>Match</th>
<th>Blue</th>
<th>Red</th>
<th>Winner</th>
</tr>
</thead>
<tbody id="matchTable" onclick="matchClick(this)">
</tbody>
</table>
<h2>Match Information</h2>
<p>
Clicking on an above match should provide the detail here.
This section could also be a live update.
</p>
<h3>Heat Map</h3>
<p>
Tried getting heat map data from 2019 SVR (2019casj), but just got a null.
OK, 2019 off season Chezy Champs (2019cc) has heat maps.
There are 1521 points in qualifying match 1.
</p>
<button type="button" onclick="getHeatMap()">Get Heat Map</button>
<script type="text/javascript">
// clean up the types
/**
* Enum String test
* @readonly
* @enum {string}
*/
const EnumAlliance = {
BLUE: "blue",
RED: "red"
};
/** silly Alliance test
* @returns {EnumAlliance}
* */
function fooAlly() {
return EnumAlliance.BLUE;
}
/** Test Intellisense completion...
* @param {Team} team - the team
* @param {District} district - the district
* @param {FirstEvent} event - the FRC event
* @param {Match_Score_Breakdown_2020} msbd - the match score breakdown
*/
function fooTest(team, district, event, msbd) {
// These variables are not used except to test for Intellisense completion....
team;
district;
event;
msbd;
}
// X-TBA-Auth-Key
/** @type {string} key for The Blue Alliance */
var keyTBA = "";
/** @type {string} base URL for the Blue Alliance */
var urlBase = "https://www.thebluealliance.com/api/v3";
// try local storage: key, setItem, getItem, removeItem, clear
inputKeyTBA.addEventListener("change", (event) => { localStorage.setItem("keyTBA", inputKeyTBA.value); });
if (localStorage.getItem("keyTBA")) {
keyTBA = localStorage.getItem("keyTBA");
}
// TODO: I can get the year with yearEvent.value
/** @type {Object.<string,FirstEvent>} keeps all the events for a given year. Maps event key to event */
var objEvents = null;
// TODO: I can get the selected event with selEvent.value
// Dictionary: teamKey to Team
// This data does not change often (only when teams add or drop out)
/** @type {Object.<string,Team>} teams at the current event */
var objTeams = null;
// I get this array and then sort it to make better sense
// This array needs updating after new matches
/** @type {Match[]} Match data */
var matchData = [];
/** @type {Object} OPR data */
// This needs updating after new matches
var oprData = {};
/** bubble sort table by iCol number
* @param {number} iCol
*/
function tsort(iCol) {
"use strict";
/** @type {boolean} */
var change = true;
/** @type {number} */
var n = opr.childElementCount;
/** @type {number} */
var i;
while (change) {
change = false;
for (i = 1; i < n; i++) {
var r1 = opr.children[i - 1];
var r2 = opr.children[i];
if (Number(r1.children[iCol].textContent) < Number(r2.children[iCol].textContent)) {
opr.insertBefore(r2, r1);
change = true;
}
}
}
}
/** Get the events for a particular year and put them into a select element
* @returns {null}
*/
function loadEvents() {
"use strict";
// Create an HTTP request
var xhr = new XMLHttpRequest();
xhr.onerror = function () {
console.log("error loading Events");
}
xhr.onload = function () {
"use strict";
// the result is an array of FirstEvent
/** @type {Array.<FirstEvent>} Array of all of the events */
var aEvents = xhr.response;
// sort the events
aEvents.sort((x, y) => { return x.name.localeCompare(y.name); });
// should squirrel the FirstEvent data into an object by key
objEvents = {};
// clear the current selection list
while (selEvent.length > 0) {
selEvent.removeChild(selEvent.firstChild);
}
// process each event
aEvents.forEach(function (evt) {
"use strict";
// build an option for the select element
var opt = document.createElement("option");
// set value to event key
opt.value = evt.key;
// add a description; include the week for searches by eye
// opt.textContent = evt.week + " " + evt.name;
opt.textContent = evt.name;
// save the rvent in the global dictionary
// we do not expect events to change anytime soon.
objEvents[evt.key] = evt;
// add the option to the select list
selEvent.appendChild(opt);
})
}
// open the request
// build the URL
xhr.open("GET", urlBase + "/events/" + yearEvent.value);
// set the authorization key
xhr.setRequestHeader("X-TBA-Auth-Key", keyTBA);
xhr.responseType = "json";
// send the request
xhr.send();
}
/** Get the teams at a particular event
* @returns {null}
*/
function loadTeams(keyEvent) {
"use strict";
// create a new HTTP request
var xhr = new XMLHttpRequest();
xhr.onerror = function () {
console.log("error loading Events");
}
xhr.onload = function () {
"use strict";
// the result is an array of teams
/** @type {Array} Array of Team objects */
var aTeams = xhr.response;
console.log("teams");
console.log(xhr.getResponseHeader("Last-Modified"));
console.log(xhr.getResponseHeader("max-age"));
console.log(xhr.getAllResponseHeaders());
// sort the teams
aTeams.sort((x, y) => { return x.team_number - y.team_number; });
// squirrel the event data into an object by key
objTeams = {};
// empty the table
while (teamTable.firstChild) teamTable.removeChild(teamTable.firstChild);
// process each team
aTeams.forEach(function (team) {
"use strict";
objTeams[team.key] = team;
var tr = document.createElement("tr");
var td = document.createElement("td");
td.textContent = team.team_number;
tr.appendChild(td);
td = document.createElement("td");
td.textContent = team.nickname; // + team.school_name;
tr.appendChild(td);
td = document.createElement("td");
td.textContent = team.city;
tr.appendChild(td);
td = document.createElement("td");
td.textContent = team.state_prov;
tr.appendChild(td);
// find earlier events this team was in and its rank/OPR
teamTable.appendChild(tr);
})
}
// open the request
xhr.open("GET", urlBase + "/event/" + keyEvent + "/teams");
xhr.setRequestHeader("X-TBA-Auth-Key", keyTBA);
xhr.responseType = "json";
xhr.send();
}
/** fill OPR table from event name
* @param {string} strEvent
* @returns {null}
*/
function scrapeIt(strEvent) {
"use strict";
// make a new HTTP request
var xhr = new XMLHttpRequest();
xhr.onerror = function () {
console.log("error loading");
console.log(xhr.responseType);
}
xhr.onload = function () {
// console.log("document loaded");
// console.log(xhr.getAllResponseHeaders());
// console.log(xhr.responseType);
// console.log(xhr.responseText);
/** @type {Object} OPR data */
oprData = xhr.response;
console.log("opr");
console.log(xhr.getResponseHeader("Last-Modified"));
console.log(xhr.getResponseHeader("max-age"));
console.log(xhr.getAllResponseHeaders());
console.log(oprData);
/* clear the table */
while (opr.childElementCount > 0) {
opr.deleteRow(0);
}
// fill in the table (OPR data may not exist yet)
if (oprData) {
for (var prop in oprData.oprs) {
var tr = document.createElement("tr");
var td1 = document.createElement("td");
var td2 = document.createElement("td");
var td3 = document.createElement("td");
var td4 = document.createElement("td");
td1.textContent = prop;
td2.textContent = Number(oprData.oprs[prop]).toFixed(2);
td3.textContent = Number(oprData.dprs[prop]).toFixed(2);
td4.textContent = Number(oprData.ccwms[prop]).toFixed(2);
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
tr.appendChild(td4);
opr.appendChild(tr);
}
}
}
xhr.open("GET", urlBase + "/event/" + strEvent + "/oprs");
xhr.setRequestHeader("X-TBA-Auth-Key", keyTBA);
xhr.responseType = "json";
xhr.send();
}
/** fill Matches table from event name
* @param {string} strEvent - the event key (e.g., 2016nytr)
* @returns {null}
*/
function loadMatches(strEvent) {
"use strict";
// new HTTP request
var xhr = new XMLHttpRequest();
xhr.onerror = function () {
console.log("error loading matches");
}
xhr.onload = function () {
// console.log("matches loaded");
// console.log(xhr.getAllResponseHeaders());
// console.log(xhr.responseType);
// console.log(xhr.responseText);
/** @type {Array.<Match>} OPR data */
matchData = xhr.response;
console.log("matchData");
console.log(xhr.getResponseHeader("Last-Modified"));
console.log(xhr.getResponseHeader("max-age"));
console.log(xhr.getAllResponseHeaders());
/**
* calculate a sort weight
* @param {Match} m
* @return {integer} sort weight
*/
function sortval(m) {
// default weight is the match number
var w = m.match_number;
if (m.set_number) w += 100 * m.set_number;
switch (m.comp_level) {
case "f": w += 2000; break;
case "sf": w += 1500; break;
case "qf": w += 1000; break;
case "qm":
default:
break;
}
return w;
}
// sort the matchData
matchData.sort((x, y) => {
// x and y are matches
var xval = sortval(x);
var yval = sortval(y);
return xval - yval;
});
console.log(matchData);
// clear the table
while (matchTable.childElementCount > 0) {
matchTable.deleteRow(0);
}
// fill in the table
var i;
for (i = 0; i < matchData.length; i += 1) {
var m = matchData[i];
var tr = document.createElement("tr");
var td1 = document.createElement("td");
var td2 = document.createElement("td");
var td3 = document.createElement("td");
var td4 = document.createElement("td");
td1.textContent = m.key;
td2.textContent = m.alliances.blue.team_keys + "=" + m.alliances.blue.score;
td3.textContent = m.alliances.red.team_keys + "=" + m.alliances.red.score;
td4.textContent = m.winning_alliance;
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
tr.appendChild(td4);
matchTable.appendChild(tr);
}
}
// get all the matches from the event
xhr.open("GET", urlBase + "/event/" + strEvent + "/matches");
xhr.setRequestHeader("X-TBA-Auth-Key", keyTBA);
xhr.responseType = "json";
// start the request
xhr.send();
}
/** Process a change in the year
* @param {HTMLElement} el
* @returns {null}
*/
function yearChange(el) {
// load the events for a particular year; the year will be in the form
loadEvents();
}
/** Update the form when an event changes
* @param {HTMLElement} el
* @returns {null}
*/
function eventChange(el) {
"use strict";
// get the event key from the select element
var keyEvent = el.value;
// that should guarantee we can access the event (modulo timing issues)
var objEvent = objEvents[keyEvent];
// fill in some form information about the new event
weekEvent.value = objEvent.week;
dateEventStart.value = objEvent.start_date;
locationName.value = objEvent.location_name;
locationEvent.value = objEvent.address;
// load the teams
loadTeams(keyEvent);
// FRC event changed, so load the team performance figures...
scrapeIt(keyEvent);
// load the matches
loadMatches(keyEvent);
}
// Hey! I've already fetched the matches, so I have the score breakdown.
// I can just search the matches for the match that I want.
// But it may be faster to do one match if that is all I want.
// If I'm trying to examine a teams performance, then I should grab matches and wander down all of them.
// So if teamX was in red alliance, then look at red breakdown for that robot.
function matchLoad(keyMatch) {
"use strict";
/** @type {XMLHttpRequest} html request */
var xhr = new XMLHttpRequest();
xhr.onerror = function () {
console.log("error loading match");
}
xhr.onload = function () {
// console.log("matches loaded");
// console.log(xhr.getAllResponseHeaders());
/** @type {Array.<Match>} match data */
var matchData = xhr.response;
console.log(matchData);
console.log(matchData.score_breakdown.blue);
console.log(matchData.score_breakdown.red);
}
xhr.open("GET", urlBase + "/match/" + keyMatch);
xhr.setRequestHeader("X-TBA-Auth-Key", keyTBA);
xhr.responseType = "json";
xhr.send();
}
function matchClick(el) {
console.log(el);
// event is global
console.log(event);
// this gives me the match key (e.g., "2019caoc_f1m1")
console.log(event.target.parentElement.firstChild.textContent);
matchLoad(event.target.parentElement.firstChild.textContent);
}
/**
* Check score_breakdown consistency.
* Some score breakdowns are inconsistent.
* Possibly the result of a score being corrected by FIRST.
* @param {Match} m
* @param {Match_Score_Breakdown_2022_Alliance} sb
* @return {boolean} true if consistent
*/
function checkMatchConsistency(m, sb) {
// total cargo scored in lower hub during auto (2 points each)
var ccargoAutoLower =
sb.autoCargoLowerNear +
sb.autoCargoLowerFar +
sb.autoCargoLowerRed +
sb.autoCargoLowerBlue;
// total cargo scored in upper hub during auto (4 points each)
var ccargoAutoUpper =
sb.autoCargoUpperNear +
sb.autoCargoUpperFar +
sb.autoCargoUpperRed +
sb.autoCargoUpperBlue;
// total cargo scored in lower hub during teleop (1 point each)
var ccargoTeleopLower =
sb.teleopCargoLowerNear +
sb.teleopCargoLowerFar +
sb.teleopCargoLowerRed +
sb.teleopCargoLowerBlue;
// total cargo scored in upper hub during teleop (2 points each)
var ccargoTeleopUpper =
sb.teleopCargoUpperNear +
sb.teleopCargoUpperFar +
sb.teleopCargoUpperRed +
sb.teleopCargoUpperBlue;
// this test works...
if (ccargoAutoLower + ccargoAutoUpper != sb.autoCargoTotal ||
ccargoTeleopLower + ccargoTeleopUpper != sb.teleopCargoTotal) {
console.log("Bad data!");
console.log(m);