Merge "Make loading positions unwrap the turret"
diff --git a/frc971/control_loops/control_loop_test.h b/frc971/control_loops/control_loop_test.h
index a820215..576d476 100644
--- a/frc971/control_loops/control_loop_test.h
+++ b/frc971/control_loops/control_loop_test.h
@@ -61,6 +61,9 @@
void set_team_id(uint16_t team_id) { team_id_ = team_id; }
uint16_t team_id() const { return team_id_; }
+ void set_alliance(aos::Alliance alliance) { alliance_ = alliance; }
+ aos::Alliance alliance() const { return alliance_; }
+
// Sets the enabled/disabled bit and (potentially) rebroadcasts the robot
// state messages.
void SetEnabled(bool enabled) {
@@ -123,9 +126,9 @@
builder.add_enabled(enabled_);
builder.add_autonomous(false);
builder.add_team_id(team_id_);
+ builder.add_alliance(alliance_);
- CHECK_EQ(new_state.Send(builder.Finish()),
- aos::RawSender::Error::kOk);
+ CHECK_EQ(new_state.Send(builder.Finish()), aos::RawSender::Error::kOk);
last_ds_time_ = monotonic_now();
last_enabled_ = enabled_;
@@ -165,6 +168,7 @@
const ::std::chrono::nanoseconds dt_;
uint16_t team_id_ = 971;
+ aos::Alliance alliance_ = aos::Alliance::kInvalid;
int32_t reader_pid_ = 1;
double battery_voltage_ = 12.4;
diff --git a/scouting/db/db.go b/scouting/db/db.go
index d3faa58..e21ca43 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -16,14 +16,13 @@
MatchNumber, Round int32
CompLevel string
R1, R2, R3, B1, B2, B3 int32
- // Each of these variables holds the matchID of the corresponding Stats row
- r1ID, r2ID, r3ID, b1ID, b2ID, b3ID int
}
type Stats struct {
- TeamNumber, MatchNumber int32
- StartingQuadrant int32
- AutoBallPickedUp [5]bool
+ TeamNumber, MatchNumber, Round int32
+ CompLevel string
+ StartingQuadrant int32
+ AutoBallPickedUp [5]bool
// TODO(phil): Re-order auto and teleop fields so auto comes first.
ShotsMissed, UpperGoalShots, LowerGoalShots int32
ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto int32
@@ -69,7 +68,6 @@
}
statement, err := database.Prepare("CREATE TABLE IF NOT EXISTS matches (" +
- "id SERIAL PRIMARY KEY, " +
"MatchNumber INTEGER, " +
"Round INTEGER, " +
"CompLevel VARCHAR, " +
@@ -79,12 +77,7 @@
"B1 INTEGER, " +
"B2 INTEGER, " +
"B3 INTEGER, " +
- "r1ID INTEGER, " +
- "r2ID INTEGER, " +
- "r3ID INTEGER, " +
- "b1ID INTEGER, " +
- "b2ID INTEGER, " +
- "b3ID INTEGER)")
+ "PRIMARY KEY (MatchNumber, Round, CompLevel))")
if err != nil {
database.Close()
return nil, errors.New(fmt.Sprint("Failed to prepare matches table creation: ", err))
@@ -98,9 +91,10 @@
}
statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_match_stats (" +
- "id SERIAL PRIMARY KEY, " +
"TeamNumber INTEGER, " +
"MatchNumber INTEGER, " +
+ "Round INTEGER, " +
+ "CompLevel VARCHAR, " +
"StartingQuadrant INTEGER, " +
"AutoBall1PickedUp BOOLEAN, " +
"AutoBall2PickedUp BOOLEAN, " +
@@ -117,7 +111,8 @@
"DefenseReceivedScore INTEGER, " +
"Climbing INTEGER, " +
"Comment VARCHAR, " +
- "CollectedBy VARCHAR)")
+ "CollectedBy VARCHAR, " +
+ "PRIMARY KEY (TeamNumber, MatchNumber, Round, CompLevel))")
if err != nil {
database.Close()
return nil, errors.New(fmt.Sprint("Failed to prepare stats table creation: ", err))
@@ -207,8 +202,48 @@
// This function will also populate the Stats table with six empty rows every time a match is added
func (database *Database) AddToMatch(m Match) error {
+ statement, err := database.Prepare("INSERT INTO matches(" +
+ "MatchNumber, Round, CompLevel, " +
+ "R1, R2, R3, B1, B2, B3) " +
+ "VALUES (" +
+ "$1, $2, $3, " +
+ "$4, $5, $6, $7, $8, $9) " +
+ "ON CONFLICT (MatchNumber, Round, CompLevel) DO UPDATE SET " +
+ "R1 = EXCLUDED.R1, R2 = EXCLUDED.R2, R3 = EXCLUDED.R3, " +
+ "B1 = EXCLUDED.B1, B2 = EXCLUDED.B2, B3 = EXCLUDED.B3")
+ if err != nil {
+ return errors.New(fmt.Sprint("Failed to prepare insertion into match database: ", err))
+ }
+ defer statement.Close()
+
+ _, err = statement.Exec(m.MatchNumber, m.Round, m.CompLevel,
+ m.R1, m.R2, m.R3, m.B1, m.B2, m.B3)
+ if err != nil {
+ return errors.New(fmt.Sprint("Failed to insert into match database: ", err))
+ }
+ return nil
+}
+
+func (database *Database) AddToStats(s Stats) error {
+ matches, err := database.QueryMatches(s.TeamNumber)
+ if err != nil {
+ return err
+ }
+ foundMatch := false
+ for _, match := range matches {
+ if match.MatchNumber == s.MatchNumber {
+ foundMatch = true
+ break
+ }
+ }
+ if !foundMatch {
+ return errors.New(fmt.Sprint(
+ "Failed to find team ", s.TeamNumber,
+ " in match ", s.MatchNumber, " in the schedule."))
+ }
+
statement, err := database.Prepare("INSERT INTO team_match_stats(" +
- "TeamNumber, MatchNumber, " +
+ "TeamNumber, MatchNumber, Round, CompLevel, " +
"StartingQuadrant, " +
"AutoBall1PickedUp, AutoBall2PickedUp, AutoBall3PickedUp, " +
"AutoBall4PickedUp, AutoBall5PickedUp, " +
@@ -217,98 +252,32 @@
"PlayedDefense, DefenseReceivedScore, Climbing, " +
"Comment, CollectedBy) " +
"VALUES (" +
- "$1, $2, " +
- "$3, " +
- "$4, $5, $6, " +
- "$7, $8, " +
- "$9, $10, $11, " +
- "$12, $13, $14, " +
- "$15, $16, $17, " +
- "$18, $19) " +
- "RETURNING id")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare insertion into stats database: ", err))
- }
- defer statement.Close()
-
- var rowIds [6]int64
- for i, TeamNumber := range []int32{m.R1, m.R2, m.R3, m.B1, m.B2, m.B3} {
- row := statement.QueryRow(
- TeamNumber, m.MatchNumber,
- 0,
- false, false, false,
- false, false,
- 0, 0, 0,
- 0, 0, 0,
- 0, 0, 0,
- "", "")
- err = row.Scan(&rowIds[i])
- if err != nil {
- return errors.New(fmt.Sprint("Failed to insert stats: ", err))
- }
- }
-
- statement, err = database.Prepare("INSERT INTO matches(" +
- "MatchNumber, Round, CompLevel, " +
- "R1, R2, R3, B1, B2, B3, " +
- "r1ID, r2ID, r3ID, b1ID, b2ID, b3ID) " +
- "VALUES (" +
- "$1, $2, $3, " +
- "$4, $5, $6, $7, $8, $9, " +
- "$10, $11, $12, $13, $14, $15)")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare insertion into match database: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec(m.MatchNumber, m.Round, m.CompLevel,
- m.R1, m.R2, m.R3, m.B1, m.B2, m.B3,
- rowIds[0], rowIds[1], rowIds[2], rowIds[3], rowIds[4], rowIds[5])
- if err != nil {
- return errors.New(fmt.Sprint("Failed to insert into match database: ", err))
- }
- return nil
-}
-
-func (database *Database) AddToStats(s Stats) error {
- statement, err := database.Prepare("UPDATE team_match_stats SET " +
- "TeamNumber = $1, MatchNumber = $2, " +
- "StartingQuadrant = $3, " +
- "AutoBall1PickedUp = $4, AutoBall2PickedUp = $5, AutoBall3PickedUp = $6, " +
- "AutoBall4PickedUp = $7, AutoBall5PickedUp = $8, " +
- "ShotsMissed = $9, UpperGoalShots = $10, LowerGoalShots = $11, " +
- "ShotsMissedAuto = $12, UpperGoalAuto = $13, LowerGoalAuto = $14, " +
- "PlayedDefense = $15, DefenseReceivedScore = $16, Climbing = $17, " +
- "Comment = $18, CollectedBy = $19 " +
- "WHERE MatchNumber = $20 AND TeamNumber = $21")
+ "$1, $2, $3, $4, " +
+ "$5, " +
+ "$6, $7, $8, " +
+ "$9, $10, " +
+ "$11, $12, $13, " +
+ "$14, $15, $16, " +
+ "$17, $18, $19, " +
+ "$20, $21)")
if err != nil {
return errors.New(fmt.Sprint("Failed to prepare stats update statement: ", err))
}
defer statement.Close()
- result, err := statement.Exec(
- s.TeamNumber, s.MatchNumber,
+ _, err = statement.Exec(
+ s.TeamNumber, s.MatchNumber, s.Round, s.CompLevel,
s.StartingQuadrant,
s.AutoBallPickedUp[0], s.AutoBallPickedUp[1], s.AutoBallPickedUp[2],
s.AutoBallPickedUp[3], s.AutoBallPickedUp[4],
s.ShotsMissed, s.UpperGoalShots, s.LowerGoalShots,
s.ShotsMissedAuto, s.UpperGoalAuto, s.LowerGoalAuto,
s.PlayedDefense, s.DefenseReceivedScore, s.Climbing,
- s.Comment, s.CollectedBy,
- s.MatchNumber, s.TeamNumber)
+ s.Comment, s.CollectedBy)
if err != nil {
return errors.New(fmt.Sprint("Failed to update stats database: ", err))
}
- numRowsAffected, err := result.RowsAffected()
- if err != nil {
- return errors.New(fmt.Sprint("Failed to query rows affected: ", err))
- }
- if numRowsAffected == 0 {
- return errors.New(fmt.Sprint(
- "Failed to find team ", s.TeamNumber,
- " in match ", s.MatchNumber, " in the schedule."))
- }
return nil
}
@@ -364,10 +333,8 @@
matches := make([]Match, 0)
for rows.Next() {
var match Match
- var id int
- err := rows.Scan(&id, &match.MatchNumber, &match.Round, &match.CompLevel,
- &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3,
- &match.r1ID, &match.r2ID, &match.r3ID, &match.b1ID, &match.b2ID, &match.b3ID)
+ err := rows.Scan(&match.MatchNumber, &match.Round, &match.CompLevel,
+ &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3)
if err != nil {
return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
}
@@ -386,9 +353,8 @@
teams := make([]Stats, 0)
for rows.Next() {
var team Stats
- var id int
- err = rows.Scan(&id,
- &team.TeamNumber, &team.MatchNumber,
+ err = rows.Scan(
+ &team.TeamNumber, &team.MatchNumber, &team.Round, &team.CompLevel,
&team.StartingQuadrant,
&team.AutoBallPickedUp[0], &team.AutoBallPickedUp[1], &team.AutoBallPickedUp[2],
&team.AutoBallPickedUp[3], &team.AutoBallPickedUp[4],
@@ -438,10 +404,8 @@
var matches []Match
for rows.Next() {
var match Match
- var id int
- err = rows.Scan(&id, &match.MatchNumber, &match.Round, &match.CompLevel,
- &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3,
- &match.r1ID, &match.r2ID, &match.r3ID, &match.b1ID, &match.b2ID, &match.b3ID)
+ err = rows.Scan(&match.MatchNumber, &match.Round, &match.CompLevel,
+ &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3)
if err != nil {
return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
}
@@ -460,9 +424,8 @@
var teams []Stats
for rows.Next() {
var team Stats
- var id int
- err = rows.Scan(&id,
- &team.TeamNumber, &team.MatchNumber,
+ err = rows.Scan(
+ &team.TeamNumber, &team.MatchNumber, &team.Round, &team.CompLevel,
&team.StartingQuadrant,
&team.AutoBallPickedUp[0], &team.AutoBallPickedUp[1], &team.AutoBallPickedUp[2],
&team.AutoBallPickedUp[3], &team.AutoBallPickedUp[4],
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index 5725dcb..391f336 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -6,6 +6,7 @@
"os"
"os/exec"
"reflect"
+ "strings"
"testing"
"time"
)
@@ -67,7 +68,6 @@
Round: 1,
CompLevel: "quals",
R1: 9999, R2: 1000, R3: 777, B1: 0000, B2: 4321, B3: 1234,
- r1ID: 1, r2ID: 2, r3ID: 3, b1ID: 4, b2ID: 5, b3ID: 6,
},
}
@@ -176,7 +176,6 @@
err := fixture.db.AddToMatch(Match{
MatchNumber: 7, Round: 1, CompLevel: "quals",
R1: 1236, R2: 1001, R3: 777, B1: 1000, B2: 4321, B3: 1234,
- r1ID: 1, r2ID: 2, r3ID: 3, b1ID: 4, b2ID: 5, b3ID: 6,
})
check(t, err, "Failed to add match")
@@ -193,6 +192,40 @@
}
}
+func TestAddDuplicateStats(t *testing.T) {
+ fixture := createDatabase(t)
+ defer fixture.TearDown()
+
+ stats := Stats{
+ TeamNumber: 1236, MatchNumber: 7,
+ StartingQuadrant: 2,
+ AutoBallPickedUp: [5]bool{false, false, false, true, false},
+ ShotsMissed: 9, UpperGoalShots: 5, LowerGoalShots: 4,
+ ShotsMissedAuto: 3, UpperGoalAuto: 2, LowerGoalAuto: 1,
+ PlayedDefense: 2, DefenseReceivedScore: 0, Climbing: 3,
+ Comment: "this is a comment", CollectedBy: "josh",
+ }
+
+ err := fixture.db.AddToMatch(Match{
+ MatchNumber: 7, Round: 1, CompLevel: "quals",
+ R1: 1236, R2: 1001, R3: 777, B1: 1000, B2: 4321, B3: 1234,
+ })
+ check(t, err, "Failed to add match")
+
+ // Add stats. This should succeed.
+ err = fixture.db.AddToStats(stats)
+ check(t, err, "Failed to add stats to DB")
+
+ // Try again. It should fail this time.
+ err = fixture.db.AddToStats(stats)
+ if err == nil {
+ t.Fatal("Failed to get error when adding duplicate stats.")
+ }
+ if !strings.Contains(err.Error(), "ERROR: duplicate key value violates unique constraint") {
+ t.Fatal("Expected error message to be complain about duplicate key value, but got ", err)
+ }
+}
+
func TestQueryMatchDB(t *testing.T) {
fixture := createDatabase(t)
defer fixture.TearDown()
@@ -213,12 +246,10 @@
Match{
MatchNumber: 2, Round: 1, CompLevel: "quals",
R1: 251, R2: 169, R3: 286, B1: 253, B2: 538, B3: 149,
- r1ID: 1, r2ID: 2, r3ID: 3, b1ID: 4, b2ID: 5, b3ID: 6,
},
Match{
MatchNumber: 3, Round: 1, CompLevel: "quals",
R1: 147, R2: 421, R3: 538, B1: 126, B2: 448, B3: 262,
- r1ID: 13, r2ID: 14, r3ID: 15, b1ID: 16, b2ID: 17, b3ID: 18,
},
}
@@ -236,14 +267,14 @@
testDatabase := []Stats{
Stats{
- TeamNumber: 1235, MatchNumber: 94,
+ TeamNumber: 1235, MatchNumber: 94, Round: 2, CompLevel: "quals",
StartingQuadrant: 1,
AutoBallPickedUp: [5]bool{false, false, false, false, false},
ShotsMissed: 2, UpperGoalShots: 2, LowerGoalShots: 2,
ShotsMissedAuto: 2, UpperGoalAuto: 2, LowerGoalAuto: 2,
PlayedDefense: 2, DefenseReceivedScore: 1, Climbing: 2},
Stats{
- TeamNumber: 1234, MatchNumber: 94,
+ TeamNumber: 1234, MatchNumber: 94, Round: 2, CompLevel: "quals",
StartingQuadrant: 2,
AutoBallPickedUp: [5]bool{false, false, false, false, true},
ShotsMissed: 4, UpperGoalShots: 4, LowerGoalShots: 4,
@@ -251,7 +282,7 @@
PlayedDefense: 7, DefenseReceivedScore: 1, Climbing: 2,
},
Stats{
- TeamNumber: 1233, MatchNumber: 94,
+ TeamNumber: 1233, MatchNumber: 94, Round: 2, CompLevel: "quals",
StartingQuadrant: 3,
AutoBallPickedUp: [5]bool{false, false, false, false, false},
ShotsMissed: 3, UpperGoalShots: 3, LowerGoalShots: 3,
@@ -259,7 +290,7 @@
PlayedDefense: 3, DefenseReceivedScore: 0, Climbing: 3,
},
Stats{
- TeamNumber: 1232, MatchNumber: 94,
+ TeamNumber: 1232, MatchNumber: 94, Round: 2, CompLevel: "quals",
StartingQuadrant: 2,
AutoBallPickedUp: [5]bool{true, false, false, false, true},
ShotsMissed: 5, UpperGoalShots: 5, LowerGoalShots: 5,
@@ -267,7 +298,7 @@
PlayedDefense: 7, DefenseReceivedScore: 2, Climbing: 1,
},
Stats{
- TeamNumber: 1231, MatchNumber: 94,
+ TeamNumber: 1231, MatchNumber: 94, Round: 2, CompLevel: "quals",
StartingQuadrant: 3,
AutoBallPickedUp: [5]bool{false, false, true, false, false},
ShotsMissed: 6, UpperGoalShots: 6, LowerGoalShots: 6,
@@ -275,7 +306,7 @@
PlayedDefense: 7, DefenseReceivedScore: 3, Climbing: 1,
},
Stats{
- TeamNumber: 1239, MatchNumber: 94,
+ TeamNumber: 1239, MatchNumber: 94, Round: 2, CompLevel: "quals",
StartingQuadrant: 4,
AutoBallPickedUp: [5]bool{false, true, true, false, false},
ShotsMissed: 7, UpperGoalShots: 7, LowerGoalShots: 7,
@@ -296,7 +327,7 @@
correct := []Stats{
Stats{
- TeamNumber: 1235, MatchNumber: 94,
+ TeamNumber: 1235, MatchNumber: 94, Round: 2, CompLevel: "quals",
StartingQuadrant: 1,
AutoBallPickedUp: [5]bool{false, false, false, false, false},
ShotsMissed: 2, UpperGoalShots: 2, LowerGoalShots: 2,
@@ -369,27 +400,22 @@
Match{
MatchNumber: 2, Round: 1, CompLevel: "quals",
R1: 251, R2: 169, R3: 286, B1: 253, B2: 538, B3: 149,
- r1ID: 1, r2ID: 2, r3ID: 3, b1ID: 4, b2ID: 5, b3ID: 6,
},
Match{
MatchNumber: 3, Round: 1, CompLevel: "quals",
R1: 147, R2: 421, R3: 538, B1: 126, B2: 448, B3: 262,
- r1ID: 7, r2ID: 8, r3ID: 9, b1ID: 10, b2ID: 11, b3ID: 12,
},
Match{
MatchNumber: 4, Round: 1, CompLevel: "quals",
R1: 251, R2: 169, R3: 286, B1: 653, B2: 538, B3: 149,
- r1ID: 13, r2ID: 14, r3ID: 15, b1ID: 16, b2ID: 17, b3ID: 18,
},
Match{
MatchNumber: 5, Round: 1, CompLevel: "quals",
R1: 198, R2: 1421, R3: 538, B1: 26, B2: 448, B3: 262,
- r1ID: 19, r2ID: 20, r3ID: 21, b1ID: 22, b2ID: 23, b3ID: 24,
},
Match{
MatchNumber: 6, Round: 1, CompLevel: "quals",
R1: 251, R2: 188, R3: 286, B1: 555, B2: 538, B3: 149,
- r1ID: 25, r2ID: 26, r3ID: 27, b1ID: 28, b2ID: 29, b3ID: 30,
},
}
@@ -406,6 +432,49 @@
}
}
+func TestOverwriteNewMatchData(t *testing.T) {
+ fixture := createDatabase(t)
+ defer fixture.TearDown()
+
+ testDatabase := []Match{
+ Match{
+ MatchNumber: 1, Round: 1, CompLevel: "quals",
+ R1: 251, R2: 169, R3: 286, B1: 253, B2: 538, B3: 149,
+ },
+ Match{
+ MatchNumber: 2, Round: 1, CompLevel: "quals",
+ R1: 198, R2: 135, R3: 777, B1: 999, B2: 434, B3: 698,
+ },
+ Match{
+ MatchNumber: 1, Round: 1, CompLevel: "quals",
+ R1: 147, R2: 421, R3: 538, B1: 126, B2: 448, B3: 262,
+ },
+ }
+
+ for i := 0; i < len(testDatabase); i++ {
+ err := fixture.db.AddToMatch(testDatabase[i])
+ check(t, err, fmt.Sprint("Failed to add match", i))
+ }
+
+ correct := []Match{
+ Match{
+ MatchNumber: 2, Round: 1, CompLevel: "quals",
+ R1: 198, R2: 135, R3: 777, B1: 999, B2: 434, B3: 698,
+ },
+ Match{
+ MatchNumber: 1, Round: 1, CompLevel: "quals",
+ R1: 147, R2: 421, R3: 538, B1: 126, B2: 448, B3: 262,
+ },
+ }
+
+ got, err := fixture.db.ReturnMatches()
+ check(t, err, "Failed to get match list")
+
+ if !reflect.DeepEqual(correct, got) {
+ t.Fatalf("Got %#v,\nbut expected %#v.", got, correct)
+ }
+}
+
func TestReturnRankingsDB(t *testing.T) {
fixture := createDatabase(t)
defer fixture.TearDown()
@@ -452,14 +521,14 @@
correct := []Stats{
Stats{
- TeamNumber: 1235, MatchNumber: 94,
+ TeamNumber: 1235, MatchNumber: 94, Round: 1, CompLevel: "quals",
StartingQuadrant: 1,
AutoBallPickedUp: [5]bool{false, false, false, false, false},
ShotsMissed: 2, UpperGoalShots: 2, LowerGoalShots: 2,
ShotsMissedAuto: 2, UpperGoalAuto: 2, LowerGoalAuto: 2,
PlayedDefense: 2, DefenseReceivedScore: 3, Climbing: 2},
Stats{
- TeamNumber: 1236, MatchNumber: 94,
+ TeamNumber: 1236, MatchNumber: 94, Round: 1, CompLevel: "quals",
StartingQuadrant: 2,
AutoBallPickedUp: [5]bool{false, false, false, false, true},
ShotsMissed: 4, UpperGoalShots: 4, LowerGoalShots: 4,
@@ -467,7 +536,7 @@
PlayedDefense: 7, DefenseReceivedScore: 1, Climbing: 2,
},
Stats{
- TeamNumber: 1237, MatchNumber: 94,
+ TeamNumber: 1237, MatchNumber: 94, Round: 1, CompLevel: "quals",
StartingQuadrant: 3,
AutoBallPickedUp: [5]bool{false, false, false, false, false},
ShotsMissed: 3, UpperGoalShots: 3, LowerGoalShots: 3,
@@ -475,7 +544,7 @@
PlayedDefense: 3, DefenseReceivedScore: 0, Climbing: 3,
},
Stats{
- TeamNumber: 1238, MatchNumber: 94,
+ TeamNumber: 1238, MatchNumber: 94, Round: 1, CompLevel: "quals",
StartingQuadrant: 2,
AutoBallPickedUp: [5]bool{true, false, false, false, true},
ShotsMissed: 5, UpperGoalShots: 5, LowerGoalShots: 5,
@@ -483,7 +552,7 @@
PlayedDefense: 7, DefenseReceivedScore: 4, Climbing: 1,
},
Stats{
- TeamNumber: 1239, MatchNumber: 94,
+ TeamNumber: 1239, MatchNumber: 94, Round: 1, CompLevel: "quals",
StartingQuadrant: 3,
AutoBallPickedUp: [5]bool{false, false, true, false, false},
ShotsMissed: 6, UpperGoalShots: 6, LowerGoalShots: 6,
@@ -491,7 +560,7 @@
PlayedDefense: 7, DefenseReceivedScore: 4, Climbing: 1,
},
Stats{
- TeamNumber: 1233, MatchNumber: 94,
+ TeamNumber: 1233, MatchNumber: 94, Round: 1, CompLevel: "quals",
StartingQuadrant: 4,
AutoBallPickedUp: [5]bool{false, true, true, false, false},
ShotsMissed: 7, UpperGoalShots: 7, LowerGoalShots: 7,
diff --git a/scouting/scouting_test.ts b/scouting/scouting_test.ts
index 98e1d00..5740cc8 100644
--- a/scouting/scouting_test.ts
+++ b/scouting/scouting_test.ts
@@ -41,6 +41,12 @@
return element(by.css('.error_message')).getText();
}
+// Returns the currently displayed error message on the screen. This only
+// exists on screens where the web page interacts with the web server.
+function getValueOfInputById(id: string) {
+ return element(by.id(id)).getAttribute('value');
+}
+
// Asserts that the field on the "Submit and Review" screen has a specific
// value.
function expectReviewFieldToBe(fieldName: string, expectedValue: string) {
@@ -120,14 +126,39 @@
it('should: show matches in chronological order.', async () => {
await loadPage();
- expect(await getNthMatchLabel(0)).toEqual('Quals 1');
- expect(await getNthMatchLabel(1)).toEqual('Quals 2');
- expect(await getNthMatchLabel(2)).toEqual('Quals 3');
- expect(await getNthMatchLabel(9)).toEqual('Quals 10');
- // TODO(phil): Validate quarter finals and friends. Right now we don't
- // distinguish between "sets". I.e. we display 4 "Quarter Final 1" matches
- // without being able to distinguish between them.
- expect(await getNthMatchLabel(87)).toEqual('Final 1');
+ expect(await getNthMatchLabel(0)).toEqual('Quals Match 1');
+ expect(await getNthMatchLabel(1)).toEqual('Quals Match 2');
+ expect(await getNthMatchLabel(2)).toEqual('Quals Match 3');
+ expect(await getNthMatchLabel(9)).toEqual('Quals Match 10');
+ expect(await getNthMatchLabel(72)).toEqual('Quarter Final 1 Match 1');
+ expect(await getNthMatchLabel(73)).toEqual('Quarter Final 2 Match 1');
+ expect(await getNthMatchLabel(74)).toEqual('Quarter Final 3 Match 1');
+ expect(await getNthMatchLabel(75)).toEqual('Quarter Final 4 Match 1');
+ expect(await getNthMatchLabel(76)).toEqual('Quarter Final 1 Match 2');
+ expect(await getNthMatchLabel(82)).toEqual('Semi Final 1 Match 1');
+ expect(await getNthMatchLabel(83)).toEqual('Semi Final 2 Match 1');
+ expect(await getNthMatchLabel(84)).toEqual('Semi Final 1 Match 2');
+ expect(await getNthMatchLabel(85)).toEqual('Semi Final 2 Match 2');
+ expect(await getNthMatchLabel(89)).toEqual('Final 1 Match 3');
+ });
+
+ it('should: prefill the match information.', async () => {
+ await loadPage();
+
+ expect(await getHeadingText()).toEqual('Matches');
+
+ // On the 87th row of matches (index 86) click on the second team
+ // (index 1) which resolves to team 5254 in semi final 2 match 3.
+ await element
+ .all(by.css('button.match-item'))
+ .get(86 * 6 + 1)
+ .click();
+
+ expect(await getHeadingText()).toEqual('Team Selection');
+ expect(await getValueOfInputById('match_number')).toEqual('3');
+ expect(await getValueOfInputById('team_number')).toEqual('5254');
+ expect(await getValueOfInputById('round')).toEqual('2');
+ expect(await getValueOfInputById('comp_level')).toEqual('3: sf');
});
it('should: error on unknown match.', async () => {
@@ -197,6 +228,8 @@
expect(await getHeadingText()).toEqual('Team Selection');
await setTextboxByIdTo('match_number', '2');
await setTextboxByIdTo('team_number', '5254');
+ await setTextboxByIdTo('round', '42');
+ await element(by.cssContainingText('option', 'Semi Finals')).click();
await element(by.buttonText('Next')).click();
expect(await getHeadingText()).toEqual('Auto');
@@ -224,6 +257,8 @@
// Validate Team Selection.
await expectReviewFieldToBe('Match number', '2');
await expectReviewFieldToBe('Team number', '5254');
+ await expectReviewFieldToBe('Round', '42');
+ await expectReviewFieldToBe('Comp Level', 'Semi Finals');
// Validate Auto.
await expectNthReviewFieldToBe('Upper Shots Made', 0, '0');
@@ -237,7 +272,7 @@
await expectNthReviewFieldToBe('Missed Shots', 1, '0');
// Validate Climb.
- await expectReviewFieldToBe('Level', 'High');
+ await expectReviewFieldToBe('Climb Level', 'High');
await expectReviewFieldToBe('Comments', 'A very useful comment here.');
// Validate Other.
diff --git a/scouting/webserver/requests/debug/cli/cli_test.py b/scouting/webserver/requests/debug/cli/cli_test.py
index 5b0c749..6189284 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -52,7 +52,7 @@
"event_code": event_code,
})
exit_code, stdout, stderr = run_debug_cli(["-refreshMatchList", json_path])
- self.assertEqual(exit_code, 0, stderr)
+ self.assertEqual(exit_code, 0, f"{year}{event_code}: {stderr}")
self.assertIn("(refresh_match_list_response.RefreshMatchListResponseT)", stdout)
def test_submit_and_request_data_scouting(self):
@@ -62,6 +62,8 @@
json_path = write_json_request({
"team": 100,
"match": 1,
+ "round": 2,
+ "comp_level": "quals",
"starting_quadrant": 3,
"auto_ball_1": True,
"auto_ball_2": False,
@@ -108,7 +110,9 @@
StartingQuadrant: (int32) 3,
ClimbLevel: (request_data_scouting_response.ClimbLevel) Medium,
DefenseReceivedRating: (int32) 4,
- Comment: (string) (len=35) "A very inspiring and useful comment"
+ Comment: (string) (len=35) "A very inspiring and useful comment",
+ Round: (int32) 2,
+ CompLevel: (string) (len=5) "quals"
}"""), stdout)
def test_request_all_matches(self):
@@ -136,5 +140,21 @@
self.assertEqual(stdout.count("MatchNumber:"), 12)
self.assertEqual(len(re.findall(r": \(int32\) 4856[,\n]", stdout)), 12)
+ def test_request_all_matches(self):
+ """Makes sure that we can import the match list multiple times without problems."""
+ request_all_matches_outputs = []
+ for _ in range(2):
+ self.refresh_match_list()
+
+ # RequestAllMatches has no fields.
+ json_path = write_json_request({})
+ exit_code, stdout, stderr = run_debug_cli(["-requestAllMatches", json_path])
+
+ self.assertEqual(exit_code, 0, stderr)
+ request_all_matches_outputs.append(stdout)
+
+ self.maxDiff = None
+ self.assertEqual(request_all_matches_outputs[0], request_all_matches_outputs[1])
+
if __name__ == "__main__":
unittest.main()
diff --git a/scouting/webserver/requests/debug/cli/main.go b/scouting/webserver/requests/debug/cli/main.go
index f6fb38a..d338b5f 100644
--- a/scouting/webserver/requests/debug/cli/main.go
+++ b/scouting/webserver/requests/debug/cli/main.go
@@ -97,6 +97,9 @@
spew.Config.Indent = *indentPtr
+ // Disable pointer addresses. They're not useful for our purposes.
+ spew.Config.DisablePointerAddresses = true
+
// Handle the actual arguments.
maybePerformRequest(
"SubmitDataScouting",
diff --git a/scouting/webserver/requests/messages/request_data_scouting_response.fbs b/scouting/webserver/requests/messages/request_data_scouting_response.fbs
index 3987d7e..9171335 100644
--- a/scouting/webserver/requests/messages/request_data_scouting_response.fbs
+++ b/scouting/webserver/requests/messages/request_data_scouting_response.fbs
@@ -16,6 +16,8 @@
table Stats {
team:int (id: 0);
match:int (id: 1);
+ round:int (id: 20);
+ comp_level:string (id: 21);
missed_shots_auto:int (id: 2);
upper_goal_auto:int (id:3);
diff --git a/scouting/webserver/requests/messages/submit_data_scouting.fbs b/scouting/webserver/requests/messages/submit_data_scouting.fbs
index e136e71..b1ae2b8 100644
--- a/scouting/webserver/requests/messages/submit_data_scouting.fbs
+++ b/scouting/webserver/requests/messages/submit_data_scouting.fbs
@@ -16,6 +16,9 @@
table SubmitDataScouting {
team:int (id: 0);
match:int (id: 1);
+ round:int (id: 19);
+ comp_level:string (id: 20);
+
missed_shots_auto:int (id: 2);
upper_goal_auto:int (id:3);
lower_goal_auto:int (id:4);
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index d67f5e9..af52510 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -144,6 +144,8 @@
stats := db.Stats{
TeamNumber: request.Team(),
MatchNumber: request.Match(),
+ Round: request.Round(),
+ CompLevel: string(request.CompLevel()),
StartingQuadrant: request.StartingQuadrant(),
AutoBallPickedUp: [5]bool{
request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
@@ -294,6 +296,8 @@
response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Team: stat.TeamNumber,
Match: stat.MatchNumber,
+ Round: stat.Round,
+ CompLevel: stat.CompLevel,
StartingQuadrant: stat.StartingQuadrant,
AutoBall1: stat.AutoBallPickedUp[0],
AutoBall2: stat.AutoBallPickedUp[1],
@@ -391,15 +395,14 @@
// Add the match to the database.
err = handler.db.AddToMatch(db.Match{
MatchNumber: int32(match.MatchNumber),
- // TODO(phil): What does Round mean?
- Round: 1,
- CompLevel: match.CompLevel,
- R1: red[0],
- R2: red[1],
- R3: red[2],
- B1: blue[0],
- B2: blue[1],
- B3: blue[2],
+ Round: int32(match.SetNumber),
+ CompLevel: match.CompLevel,
+ R1: red[0],
+ R2: red[1],
+ R3: red[2],
+ B1: blue[0],
+ B2: blue[1],
+ B3: blue[2],
})
if err != nil {
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index b864cf3..20af93c 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -84,6 +84,8 @@
builder.Finish((&submit_data_scouting.SubmitDataScoutingT{
Team: 971,
Match: 1,
+ Round: 8,
+ CompLevel: "quals",
StartingQuadrant: 2,
AutoBall1: true,
AutoBall2: false,
@@ -228,7 +230,7 @@
db := MockDatabase{
stats: []db.Stats{
{
- TeamNumber: 971, MatchNumber: 1,
+ TeamNumber: 971, MatchNumber: 1, Round: 2, CompLevel: "quals",
StartingQuadrant: 1,
AutoBallPickedUp: [5]bool{true, false, false, false, true},
ShotsMissed: 1, UpperGoalShots: 2, LowerGoalShots: 3,
@@ -237,7 +239,7 @@
Comment: "a lovely comment", CollectedBy: "john",
},
{
- TeamNumber: 972, MatchNumber: 1,
+ TeamNumber: 972, MatchNumber: 1, Round: 4, CompLevel: "extra",
StartingQuadrant: 2,
AutoBallPickedUp: [5]bool{false, false, true, false, false},
ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
@@ -263,7 +265,7 @@
expected := request_data_scouting_response.RequestDataScoutingResponseT{
StatsList: []*request_data_scouting_response.StatsT{
{
- Team: 971, Match: 1,
+ Team: 971, Match: 1, Round: 2, CompLevel: "quals",
MissedShotsAuto: 4, UpperGoalAuto: 5, LowerGoalAuto: 6,
MissedShotsTele: 1, UpperGoalTele: 2, LowerGoalTele: 3,
DefenseRating: 7,
@@ -276,7 +278,7 @@
Comment: "a lovely comment",
},
{
- Team: 972, Match: 1,
+ Team: 972, Match: 1, Round: 4, CompLevel: "extra",
MissedShotsAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
MissedShotsTele: 2, UpperGoalTele: 3, LowerGoalTele: 4,
DefenseRating: 8,
@@ -360,6 +362,7 @@
{
CompLevel: "qual",
MatchNumber: 1,
+ SetNumber: 2,
Alliances: scraping.Alliances{
Red: scraping.Alliance{
TeamKeys: []string{
@@ -411,7 +414,7 @@
expectedMatches := []db.Match{
{
MatchNumber: 1,
- Round: 1,
+ Round: 2,
CompLevel: "qual",
R1: 100,
R2: 200,
diff --git a/scouting/www/app.ng.html b/scouting/www/app.ng.html
index 3c9c2c2..7bf7e63 100644
--- a/scouting/www/app.ng.html
+++ b/scouting/www/app.ng.html
@@ -75,6 +75,8 @@
(switchTabsEvent)="switchTabTo($event)"
[teamNumber]="selectedTeamInMatch.teamNumber"
[matchNumber]="selectedTeamInMatch.matchNumber"
+ [round]="selectedTeamInMatch.round"
+ [compLevel]="selectedTeamInMatch.compLevel"
*ngSwitchCase="'Entry'"
></app-entry>
<frc971-notes *ngSwitchCase="'Notes'"></frc971-notes>
diff --git a/scouting/www/app.ts b/scouting/www/app.ts
index 9d6c539..5180218 100644
--- a/scouting/www/app.ts
+++ b/scouting/www/app.ts
@@ -14,6 +14,7 @@
type TeamInMatch = {
teamNumber: number;
matchNumber: number;
+ round: number;
compLevel: string;
};
@@ -26,6 +27,7 @@
selectedTeamInMatch: TeamInMatch = {
teamNumber: 1,
matchNumber: 1,
+ round: 1,
compLevel: 'qm',
};
tab: Tab = 'MatchList';
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index c90e3d0..9637e8c 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -25,6 +25,19 @@
| 'Review and Submit'
| 'Success';
+// TODO(phil): Deduplicate with match_list.component.ts.
+const COMP_LEVELS = ['qm', 'ef', 'qf', 'sf', 'f'] as const;
+type CompLevel = typeof COMP_LEVELS[number];
+
+// TODO(phil): Deduplicate with match_list.component.ts.
+const COMP_LEVEL_LABELS: Record<CompLevel, string> = {
+ qm: 'Qualifications',
+ ef: 'Eighth Finals',
+ qf: 'Quarter Finals',
+ sf: 'Semi Finals',
+ f: 'Finals',
+};
+
@Component({
selector: 'app-entry',
templateUrl: './entry.ng.html',
@@ -34,11 +47,15 @@
// Re-export the type here so that we can use it in the `[value]` attribute
// of radio buttons.
readonly ClimbLevel = ClimbLevel;
+ readonly COMP_LEVELS = COMP_LEVELS;
+ readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
section: Section = 'Team Selection';
@Output() switchTabsEvent = new EventEmitter<string>();
@Input() matchNumber: number = 1;
@Input() teamNumber: number = 1;
+ @Input() round: number = 1;
+ @Input() compLevel: CompLevel = 'qm';
autoUpperShotsMade: number = 0;
autoLowerShotsMade: number = 0;
autoShotsMissed: number = 0;
@@ -113,10 +130,13 @@
this.errorMessage = '';
const builder = new Builder();
+ const compLevel = builder.createString(this.compLevel);
const comment = builder.createString(this.comment);
SubmitDataScouting.startSubmitDataScouting(builder);
SubmitDataScouting.addTeam(builder, this.teamNumber);
SubmitDataScouting.addMatch(builder, this.matchNumber);
+ SubmitDataScouting.addRound(builder, this.round);
+ SubmitDataScouting.addCompLevel(builder, compLevel);
SubmitDataScouting.addMissedShotsAuto(builder, this.autoShotsMissed);
SubmitDataScouting.addUpperGoalAuto(builder, this.autoUpperShotsMade);
SubmitDataScouting.addLowerGoalAuto(builder, this.autoLowerShotsMade);
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index b8eaa78..46e5989 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -28,6 +28,18 @@
max="9999"
/>
</div>
+ <div class="row">
+ <label for="round">Round</label>
+ <input [(ngModel)]="round" type="number" id="round" min="1" max="10" />
+ </div>
+ <div class="row">
+ <label for="comp_level">Round</label>
+ <select [(ngModel)]="compLevel" type="number" id="comp_level">
+ <option *ngFor="let level of COMP_LEVELS" [ngValue]="level">
+ {{COMP_LEVEL_LABELS[level]}}
+ </option>
+ </select>
+ </div>
<div class="buttons">
<!-- hack to right align the next button -->
<div></div>
@@ -348,6 +360,8 @@
<ul>
<li>Match number: {{matchNumber}}</li>
<li>Team number: {{teamNumber}}</li>
+ <li>Round: {{round}}</li>
+ <li>Comp Level: {{COMP_LEVEL_LABELS[compLevel]}}</li>
</ul>
<h4>Auto</h4>
@@ -372,7 +386,7 @@
<h4>Climb</h4>
<ul>
- <li>Level: {{level | levelToString}}</li>
+ <li>Climb Level: {{level | levelToString}}</li>
<li>Comments: {{comment}}</li>
</ul>
diff --git a/scouting/www/match_list/BUILD b/scouting/www/match_list/BUILD
index a33caf8..10c0a22 100644
--- a/scouting/www/match_list/BUILD
+++ b/scouting/www/match_list/BUILD
@@ -19,6 +19,7 @@
"//scouting/webserver/requests/messages:error_response_ts_fbs",
"//scouting/webserver/requests/messages:request_all_matches_response_ts_fbs",
"//scouting/webserver/requests/messages:request_all_matches_ts_fbs",
+ "//scouting/www/rpc",
"@com_github_google_flatbuffers//ts:flatbuffers_ts",
"@npm//@angular/common",
"@npm//@angular/core",
diff --git a/scouting/www/match_list/match_list.component.ts b/scouting/www/match_list/match_list.component.ts
index b2a2731..1dc1f49 100644
--- a/scouting/www/match_list/match_list.component.ts
+++ b/scouting/www/match_list/match_list.component.ts
@@ -1,5 +1,5 @@
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
-import {ByteBuffer, Builder} from 'flatbuffers';
+import {Builder, ByteBuffer} from 'flatbuffers';
import {ErrorResponse} from 'org_frc971/scouting/webserver/requests/messages/error_response_generated';
import {RequestAllMatches} from 'org_frc971/scouting/webserver/requests/messages/request_all_matches_generated';
import {
@@ -7,14 +7,15 @@
RequestAllMatchesResponse,
} from 'org_frc971/scouting/webserver/requests/messages/request_all_matches_response_generated';
+import {MatchListRequestor} from '../rpc/match_list_requestor';
+
type TeamInMatch = {
teamNumber: number;
matchNumber: number;
+ round: number;
compLevel: string;
};
-const MATCH_TYPE_ORDERING = ['qm', 'ef', 'qf', 'sf', 'f'];
-
@Component({
selector: 'app-match-list',
templateUrl: './match_list.ng.html',
@@ -26,6 +27,8 @@
errorMessage: string = '';
matchList: Match[] = [];
+ constructor(private readonly matchListRequestor: MatchListRequestor) {}
+
setTeamInMatch(teamInMatch: TeamInMatch) {
this.selectedTeamEvent.emit(teamInMatch);
}
@@ -59,7 +62,9 @@
}
displayMatchNumber(match: Match): string {
- return `${this.matchType(match)} ${match.matchNumber()}`;
+ // Only display the set number ("round") for eliminations matches.
+ const round = match.compLevel() == 'qm' ? '' : `${match.round()}`;
+ return `${this.matchType(match)} ${round} Match ${match.matchNumber()}`;
}
ngOnInit() {
@@ -67,62 +72,15 @@
}
async fetchMatchList() {
+ this.progressMessage = 'Fetching match list. Please be patient.';
this.errorMessage = '';
- const builder = new Builder();
- RequestAllMatches.startRequestAllMatches(builder);
- builder.finish(RequestAllMatches.endRequestAllMatches(builder));
-
- this.progressMessage = 'Fetching match list. Please be patient.';
-
- const buffer = builder.asUint8Array();
- const res = await fetch('/requests/request/all_matches', {
- method: 'POST',
- body: buffer,
- });
-
- if (res.ok) {
- const resBuffer = await res.arrayBuffer();
- const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
- const parsedResponse =
- RequestAllMatchesResponse.getRootAsRequestAllMatchesResponse(fbBuffer);
-
- // Convert the flatbuffer list into an array. That's more useful.
- this.matchList = [];
- for (let i = 0; i < parsedResponse.matchListLength(); i++) {
- this.matchList.push(parsedResponse.matchList(i));
- }
-
- // Sort the list so it is in chronological order.
- this.matchList.sort((a, b) => {
- const aMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(a.compLevel());
- const bMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(b.compLevel());
- if (aMatchTypeIndex < bMatchTypeIndex) {
- return -1;
- }
- if (aMatchTypeIndex > bMatchTypeIndex) {
- return 1;
- }
- const aMatchNumber = a.matchNumber();
- const bMatchNumber = b.matchNumber();
- if (aMatchNumber < bMatchNumber) {
- return -1;
- }
- if (aMatchNumber > bMatchNumber) {
- return 1;
- }
- return 0;
- });
-
+ try {
+ this.matchList = await this.matchListRequestor.fetchMatchList();
this.progressMessage = 'Successfully fetched match list.';
- } else {
+ } catch (e) {
+ this.errorMessage = e;
this.progressMessage = '';
- const resBuffer = await res.arrayBuffer();
- const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
- const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
-
- const errorMessage = parsedResponse.errorMessage();
- this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
}
}
}
diff --git a/scouting/www/match_list/match_list.ng.html b/scouting/www/match_list/match_list.ng.html
index 2f77198..b2e66f8 100644
--- a/scouting/www/match_list/match_list.ng.html
+++ b/scouting/www/match_list/match_list.ng.html
@@ -13,6 +13,7 @@
(click)="setTeamInMatch({
teamNumber: team.teamNumber,
matchNumber: match.matchNumber(),
+ round: match.round(),
compLevel: match.compLevel()
})"
class="match-item"
diff --git a/scouting/www/rpc/BUILD b/scouting/www/rpc/BUILD
new file mode 100644
index 0000000..581a7b8
--- /dev/null
+++ b/scouting/www/rpc/BUILD
@@ -0,0 +1,19 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_library")
+
+ts_library(
+ name = "rpc",
+ srcs = [
+ "match_list_requestor.ts",
+ ],
+ compiler = "//tools:tsc_wrapped_with_angular",
+ target_compatible_with = ["@platforms//cpu:x86_64"],
+ use_angular_plugin = True,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//scouting/webserver/requests/messages:error_response_ts_fbs",
+ "//scouting/webserver/requests/messages:request_all_matches_response_ts_fbs",
+ "//scouting/webserver/requests/messages:request_all_matches_ts_fbs",
+ "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+ "@npm//@angular/core",
+ ],
+)
diff --git a/scouting/www/rpc/match_list_requestor.ts b/scouting/www/rpc/match_list_requestor.ts
new file mode 100644
index 0000000..ddbb221
--- /dev/null
+++ b/scouting/www/rpc/match_list_requestor.ts
@@ -0,0 +1,83 @@
+import {Injectable} from '@angular/core';
+import {Builder, ByteBuffer} from 'flatbuffers';
+import {ErrorResponse} from 'org_frc971/scouting/webserver/requests/messages/error_response_generated';
+import {RequestAllMatches} from 'org_frc971/scouting/webserver/requests/messages/request_all_matches_generated';
+import {
+ Match,
+ RequestAllMatchesResponse,
+} from 'org_frc971/scouting/webserver/requests/messages/request_all_matches_response_generated';
+
+const MATCH_TYPE_ORDERING = ['qm', 'ef', 'qf', 'sf', 'f'];
+
+@Injectable({providedIn: 'root'})
+export class MatchListRequestor {
+ async fetchMatchList(): Promise<Match[]> {
+ const builder = new Builder();
+ RequestAllMatches.startRequestAllMatches(builder);
+ builder.finish(RequestAllMatches.endRequestAllMatches(builder));
+
+ const buffer = builder.asUint8Array();
+ const res = await fetch('/requests/request/all_matches', {
+ method: 'POST',
+ body: buffer,
+ });
+
+ if (res.ok) {
+ const resBuffer = await res.arrayBuffer();
+ const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
+ const parsedResponse =
+ RequestAllMatchesResponse.getRootAsRequestAllMatchesResponse(fbBuffer);
+
+ // Convert the flatbuffer list into an array. That's more useful.
+ const matchList = [];
+ for (let i = 0; i < parsedResponse.matchListLength(); i++) {
+ matchList.push(parsedResponse.matchList(i));
+ }
+
+ // Sort the list so it is in chronological order.
+ matchList.sort((a, b) => {
+ // First sort by match type. E.g. finals are last.
+ const aMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(a.compLevel());
+ const bMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(b.compLevel());
+ if (aMatchTypeIndex < bMatchTypeIndex) {
+ return -1;
+ }
+ if (aMatchTypeIndex > bMatchTypeIndex) {
+ return 1;
+ }
+ // Then sort by match number. E.g. in semi finals, all match 1 rounds
+ // are done first. Then come match 2 rounds. And then, if necessary,
+ // the match 3 rounds.
+ const aMatchNumber = a.matchNumber();
+ const bMatchNumber = b.matchNumber();
+ if (aMatchNumber < bMatchNumber) {
+ return -1;
+ }
+ if (aMatchNumber > bMatchNumber) {
+ return 1;
+ }
+ // Lastly, sort by round. I.e. Semi Final 1 Match 1 happens first. Then
+ // comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
+ // Semi Final 2 Match 2.
+ const aRound = a.round();
+ const bRound = b.round();
+ if (aRound < bRound) {
+ return -1;
+ }
+ if (aRound > bRound) {
+ return 1;
+ }
+ return 0;
+ });
+
+ return matchList;
+ } else {
+ const resBuffer = await res.arrayBuffer();
+ const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
+ const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
+
+ const errorMessage = parsedResponse.errorMessage();
+ throw `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
+ }
+ }
+}
diff --git a/y2022/actors/autonomous_actor.cc b/y2022/actors/autonomous_actor.cc
index a8a2905..ca109e0 100644
--- a/y2022/actors/autonomous_actor.cc
+++ b/y2022/actors/autonomous_actor.cc
@@ -19,7 +19,7 @@
namespace y2022 {
namespace actors {
namespace {
-constexpr double kExtendIntakeGoal = -0.02;
+constexpr double kExtendIntakeGoal = -0.10;
constexpr double kRetractIntakeGoal = 1.47;
constexpr double kIntakeRollerVoltage = 12.0;
constexpr double kRollerVoltage = 12.0;
@@ -107,10 +107,10 @@
SplineDirection::kBackward),
PlanSpline(std::bind(&AutonomousSplines::Spline2, &auto_splines_,
std::placeholders::_1, alliance_),
- SplineDirection::kForward),
+ SplineDirection::kBackward),
PlanSpline(std::bind(&AutonomousSplines::Spline3, &auto_splines_,
std::placeholders::_1, alliance_),
- SplineDirection::kBackward)};
+ SplineDirection::kForward)};
starting_position_ = rapid_react_splines_.value()[0].starting_position();
CHECK(starting_position_);
}
@@ -208,8 +208,7 @@
// Tell the superstructure a ball was preloaded
if (!WaitForPreloaded()) return;
- // Fire preloaded ball
- set_turret_goal(constants::Values::kTurretBackIntakePos());
+ // Fire preloaded ball while driving
set_fire_at_will(true);
SendSuperstructureGoal();
if (!WaitForBallsShot()) return;
@@ -221,33 +220,44 @@
set_fire_at_will(false);
SendSuperstructureGoal();
- // Drive and intake the 2 balls in nearest to the starting zonei
- set_turret_goal(constants::Values::kTurretFrontIntakePos());
+ // Drive and intake the ball nearest to the starting zone.
+ // Fire while moving.
ExtendBackIntake();
if (!splines[0].WaitForPlan()) return;
splines[0].Start();
- if (!splines[0].WaitForSplineDistanceRemaining(0.02)) return;
+ // Distance before we don't shoot while moving.
+ if (!splines[0].WaitForSplineDistanceRemaining(0.25)) return;
- // Fire the two balls once we stopped
- RetractBackIntake();
set_fire_at_will(true);
SendSuperstructureGoal();
+
+ if (!splines[0].WaitForSplineDistanceRemaining(0.02)) return;
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+
+ // Fire the last ball we picked up when stopped.
+ SendSuperstructureGoal();
+ LOG(INFO) << "Close";
if (!WaitForBallsShot()) return;
LOG(INFO) << "Shot first 3 balls "
<< chrono::duration<double>(aos::monotonic_clock::now() -
start_time)
.count()
<< 's';
- set_fire_at_will(false);
- SendSuperstructureGoal();
// Drive to the human player station while intaking two balls.
// Once is already placed down,
// and one will be rolled to the robot by the human player
- ExtendFrontIntake();
if (!splines[1].WaitForPlan()) return;
splines[1].Start();
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(1500));
+
+ set_fire_at_will(false);
+ SendSuperstructureGoal();
+
if (!splines[1].WaitForSplineDistanceRemaining(0.02)) return;
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
LOG(INFO) << "At balls 4/5 "
<< chrono::duration<double>(aos::monotonic_clock::now() -
start_time)
@@ -323,11 +333,6 @@
CreateProfileParameters(*builder.fbb(), 20.0, 60.0));
flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
- turret_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
- *builder.fbb(), turret_goal_,
- CreateProfileParameters(*builder.fbb(), 12.0, 20.0));
-
- flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
catapult_return_position_offset =
CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
*builder.fbb(), kCatapultReturnPosition,
@@ -352,7 +357,6 @@
superstructure_builder.add_turret_intake(*requested_intake_);
}
superstructure_builder.add_transfer_roller_speed(transfer_roller_voltage_);
- superstructure_builder.add_turret(turret_offset);
superstructure_builder.add_catapult(catapult_goal_offset);
superstructure_builder.add_fire(fire_);
superstructure_builder.add_preloaded(preloaded_);
@@ -400,31 +404,12 @@
superstructure_status_fetcher_.Fetch();
CHECK(superstructure_status_fetcher_.get());
- // Don't do anything if we aren't loaded
- if (superstructure_status_fetcher_->state() !=
- control_loops::superstructure::SuperstructureState::LOADED &&
- superstructure_status_fetcher_->state() !=
- control_loops::superstructure::SuperstructureState::SHOOTING) {
- LOG(WARNING) << "No balls to shoot";
- return true;
- }
-
- // Since we're loaded, there will atleast be 1 ball to shoot
- int num_wanted = 1;
-
- // If we have another ball, we will shoot 2
- if (superstructure_status_fetcher_->front_intake_has_ball() ||
- superstructure_status_fetcher_->back_intake_has_ball()) {
- num_wanted++;
- }
-
::aos::time::PhasedLoop phased_loop(frc971::controls::kLoopFrequency,
event_loop()->monotonic_now(),
ActorBase::kLoopOffset);
superstructure_status_fetcher_.Fetch();
CHECK(superstructure_status_fetcher_.get() != nullptr);
- int initial_balls = superstructure_status_fetcher_->shot_count();
- LOG(INFO) << "Waiting for balls, started with " << initial_balls;
+
while (true) {
if (ShouldCancel()) {
return false;
@@ -432,8 +417,11 @@
phased_loop.SleepUntilNext();
superstructure_status_fetcher_.Fetch();
CHECK(superstructure_status_fetcher_.get() != nullptr);
- if (superstructure_status_fetcher_->shot_count() - initial_balls >=
- num_wanted) {
+
+ if (!superstructure_status_fetcher_->front_intake_has_ball() &&
+ !superstructure_status_fetcher_->back_intake_has_ball() &&
+ superstructure_status_fetcher_->state() ==
+ control_loops::superstructure::SuperstructureState::IDLE) {
return true;
}
}
diff --git a/y2022/actors/autonomous_actor.h b/y2022/actors/autonomous_actor.h
index ec66fb3..0cf8c6d 100644
--- a/y2022/actors/autonomous_actor.h
+++ b/y2022/actors/autonomous_actor.h
@@ -47,7 +47,6 @@
void set_requested_intake(std::optional<RequestedIntake> requested_intake) {
requested_intake_ = requested_intake;
}
- void set_turret_goal(double turret_goal) { turret_goal_ = turret_goal; }
void set_fire_at_will(bool fire) { fire_ = fire; }
void set_preloaded(bool preloaded) { preloaded_ = preloaded; }
@@ -77,7 +76,6 @@
double roller_back_voltage_ = 0.0;
double transfer_roller_voltage_ = 0.0;
std::optional<RequestedIntake> requested_intake_ = std::nullopt;
- double turret_goal_ = 0.0;
bool fire_ = false;
bool preloaded_ = false;
diff --git a/y2022/actors/splines/spline_5_ball_1.json b/y2022/actors/splines/spline_5_ball_1.json
index da3e4cf..46fddbb 100644
--- a/y2022/actors/splines/spline_5_ball_1.json
+++ b/y2022/actors/splines/spline_5_ball_1.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [-0.18145693702491972, -0.1806686149879133, -0.05595918014581436, 5.762204620882601, 2.7805678460726355, 1.6146169804687496], "spline_y": [2.346189480782648, 3.6925675615333544, 4.41262134323365, 2.4753395126953124, 2.2341888067461992, 1.3005395681218328], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.5}, {"constraint_type": "VOLTAGE", "value": 12.0}, {"constraint_type": "VELOCITY", "value": 0.8, "start_distance": 1.0, "end_distance": 1.15}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [0.009329753853116074, -0.014583556392633312, 1.086141950245409, 1.3463506181539948, 1.8252560302734366, 2.7940085985321357], "spline_y": [2.2499321755598816, 3.695204931543886, 3.9907963594941256, 3.2671894020316525, 2.3428532547468994, 2.267936657588998], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.5}, {"constraint_type": "VOLTAGE", "value": 12.0}, {"constraint_type": "VELOCITY", "value": 0.5, "start_distance": 1.0, "end_distance": 1.20}, {"constraint_type": "VELOCITY", "value": 1.0, "start_distance": 2.8, "end_distance": 10}]}
diff --git a/y2022/actors/splines/spline_5_ball_2.json b/y2022/actors/splines/spline_5_ball_2.json
index 3efd1ee..3ccf4d4 100644
--- a/y2022/actors/splines/spline_5_ball_2.json
+++ b/y2022/actors/splines/spline_5_ball_2.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [1.6037446032516893, 2.2055167265625, 2.8212725389450344, 6.148134261553881, 5.92062789622044, 6.7046250148859805], "spline_y": [1.2861465107685808, 1.7993420469805743, 1.286805497714088, 2.0935212995201415, 1.9849658141364017, 2.755576908889358], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 3.0}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [2.8029000394337356, 3.598701010385372, 3.592928864987311, 5.716241105891047, 6.058409698241228, 6.836921351984797], "spline_y": [2.2659592486204163, 2.2235586475607194, 1.3732945972518653, 1.187231336623733, 1.965522252857657, 2.7153394202517944], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 3.0}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2022/actors/splines/spline_5_ball_3.json b/y2022/actors/splines/spline_5_ball_3.json
index 6b239fc..7c288ca 100644
--- a/y2022/actors/splines/spline_5_ball_3.json
+++ b/y2022/actors/splines/spline_5_ball_3.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [6.702231375950168, 6.373881457031249, 5.758688966174009, 3.1788453508620487, 2.273453592205448, 1.6114305300886826], "spline_y": [2.7438724869219806, 2.4293757261929896, 2.0768880836927197, 1.7809922274871859, 1.852294682145808, 1.2821724076488596], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 4.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 3}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [6.853097963576857, 6.385098954905726, 4.82969339985708, 2.83784620575764, 1.5541577889609748, 1.0363839414488067], "spline_y": [2.714615301031989, 2.2383835427652135, 1.708398624422522, 2.517585062352501, 2.5751182100225627, 2.1689745582854774], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 4.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 3.0}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2022/constants.cc b/y2022/constants.cc
index a6b9a28..60601bf 100644
--- a/y2022/constants.cc
+++ b/y2022/constants.cc
@@ -137,9 +137,9 @@
{1.9, {0.1, 19.0}},
{2.12, {0.15, 18.8}},
{2.9, {0.25, 19.2}},
- {3.2, {0.28, 20.5}},
+ {3.2, {0.28, 20.3}},
- {3.86, {0.35, 20.9}},
+ {3.60, {0.33, 20.3}},
{4.9, {0.4, 21.9}},
{5.4, {0.4, 23.9}},
{6.0, {0.40, 25.0}},
@@ -207,24 +207,26 @@
case kCompTeamNumber:
climber->potentiometer_offset = -0.0463847608752 - 0.0376876182111 +
0.0629263851579 - 0.00682128836400001 +
- 0.0172237531191;
+ 0.0172237531191 - 0.0172237531191;
intake_front->potentiometer_offset =
2.79628370453323 - 0.0250288114832881 + 0.577152542437606;
intake_front->subsystem_params.zeroing_constants
.measured_absolute_position = 0.26963366701647;
- intake_back->potentiometer_offset =
- 3.1409576474047 + 0.278653334013286 + 0.00879137908308503;
+ intake_back->potentiometer_offset = 3.1409576474047 + 0.278653334013286 +
+ 0.00879137908308503 +
+ 0.0837134053818833;
intake_back->subsystem_params.zeroing_constants
- .measured_absolute_position = 0.242434593996789;
+ .measured_absolute_position = 0.15924088639178;
turret->potentiometer_offset = -9.99970387166721 + 0.06415943 +
0.073290115367682 - 0.0634440443622909 +
0.213601224728352 + 0.0657973101027296 -
- 0.114726411377978;
+ 0.114726411377978 - 0.980314029089968 -
+ 0.0266013159299456 + 0.0631240002215899;
turret->subsystem_params.zeroing_constants.measured_absolute_position =
- 0.39190961531060;
+ 1.35180753332209;
flipper_arm_left->potentiometer_offset = -6.4;
flipper_arm_right->potentiometer_offset = 5.56;
diff --git a/y2022/control_loops/drivetrain/localizer.cc b/y2022/control_loops/drivetrain/localizer.cc
index 65df654..79ced2d 100644
--- a/y2022/control_loops/drivetrain/localizer.cc
+++ b/y2022/control_loops/drivetrain/localizer.cc
@@ -48,7 +48,7 @@
joystick_state_fetcher_->autonomous()) {
// TODO(james): This is an inelegant way to avoid having the localizer mess
// up splines. Do better.
- return;
+ //return;
}
if (localizer_output_fetcher_.Fetch()) {
clock_offset_fetcher_.Fetch();
diff --git a/y2022/control_loops/superstructure/BUILD b/y2022/control_loops/superstructure/BUILD
index ebd9ace..f67fa12 100644
--- a/y2022/control_loops/superstructure/BUILD
+++ b/y2022/control_loops/superstructure/BUILD
@@ -88,6 +88,7 @@
"//y2022:constants",
"//y2022/control_loops/superstructure/catapult",
"//y2022/control_loops/superstructure/turret:aiming",
+ "//y2022/vision:ball_color_fbs",
],
)
@@ -180,6 +181,7 @@
"//frc971/queues:gyro_fbs",
"//third_party:phoenix",
"//third_party:wpilib",
+ "//y2022/localizer:localizer_output_fbs",
],
)
diff --git a/y2022/control_loops/superstructure/led_indicator.cc b/y2022/control_loops/superstructure/led_indicator.cc
index 94d0506..b636250 100644
--- a/y2022/control_loops/superstructure/led_indicator.cc
+++ b/y2022/control_loops/superstructure/led_indicator.cc
@@ -17,6 +17,9 @@
client_statistics_fetcher_(
event_loop_->MakeFetcher<aos::message_bridge::ClientStatistics>(
"/roborio/aos")),
+ localizer_output_fetcher_(
+ event_loop_->MakeFetcher<frc971::controls::LocalizerOutput>(
+ "/localizer")),
gyro_reading_fetcher_(
event_loop_->MakeFetcher<frc971::sensors::GyroReading>(
"/drivetrain")) {
@@ -58,12 +61,6 @@
}
return false;
}
-
-bool DrivingFast(
- const frc971::control_loops::drivetrain::Output &drivetrain_out) {
- return (drivetrain_out.left_voltage() >= 11.5 ||
- drivetrain_out.right_voltage() >= 11.5);
-}
} // namespace
void LedIndicator::DecideColor() {
@@ -72,6 +69,15 @@
drivetrain_output_fetcher_.Fetch();
client_statistics_fetcher_.Fetch();
gyro_reading_fetcher_.Fetch();
+ localizer_output_fetcher_.Fetch();
+
+ if (localizer_output_fetcher_.get()) {
+ if (localizer_output_fetcher_->image_accepted_count() !=
+ last_accepted_count_) {
+ last_accepted_count_ = localizer_output_fetcher_->image_accepted_count();
+ last_accepted_time_ = event_loop_->monotonic_now();
+ }
+ }
// Estopped
if (superstructure_status_fetcher_.get() &&
@@ -90,7 +96,8 @@
// If the imu gyro readings are not being sent/updated recently
if (!gyro_reading_fetcher_.get() ||
gyro_reading_fetcher_.context().monotonic_event_time <
- event_loop_->monotonic_now() - frc971::controls::kLoopFrequency * 10) {
+ event_loop_->monotonic_now() -
+ frc971::controls::kLoopFrequency * 10) {
if (imu_flash_) {
DisplayLed(255, 0, 0);
} else {
@@ -122,13 +129,6 @@
return;
}
- // Driving fast
- if (drivetrain_output_fetcher_.get() &&
- DrivingFast(*drivetrain_output_fetcher_)) {
- DisplayLed(138, 43, 226);
- return;
- }
-
// Statemachine
if (superstructure_status_fetcher_.get()) {
switch (superstructure_status_fetcher_->state()) {
@@ -147,18 +147,16 @@
} else if (superstructure_status_fetcher_->front_intake_has_ball() ||
superstructure_status_fetcher_->back_intake_has_ball()) {
DisplayLed(165, 42, 42);
- } else {
- DisplayLed(0, 255, 0);
}
break;
case (SuperstructureState::SHOOTING):
- if (!superstructure_status_fetcher_->flippers_open()) {
- DisplayLed(255, 105, 180);
- } else {
- DisplayLed(0, 255, 255);
- }
break;
}
+
+ if (event_loop_->monotonic_now() <
+ last_accepted_time_ + std::chrono::seconds(2)) {
+ DisplayLed(255, 0, 255);
+ }
return;
}
}
diff --git a/y2022/control_loops/superstructure/led_indicator.h b/y2022/control_loops/superstructure/led_indicator.h
index 0f44788..c058254 100644
--- a/y2022/control_loops/superstructure/led_indicator.h
+++ b/y2022/control_loops/superstructure/led_indicator.h
@@ -12,6 +12,7 @@
#include "frc971/queues/gyro_generated.h"
#include "y2022/control_loops/superstructure/superstructure_output_generated.h"
#include "y2022/control_loops/superstructure/superstructure_status_generated.h"
+#include "y2022/localizer/localizer_output_generated.h"
namespace y2022::control_loops::superstructure {
@@ -58,8 +59,12 @@
server_statistics_fetcher_;
aos::Fetcher<aos::message_bridge::ClientStatistics>
client_statistics_fetcher_;
+ aos::Fetcher<frc971::controls::LocalizerOutput> localizer_output_fetcher_;
aos::Fetcher<frc971::sensors::GyroReading> gyro_reading_fetcher_;
+ size_t last_accepted_count_ = 0;
+ aos::monotonic_clock::time_point last_accepted_time_ =
+ aos::monotonic_clock::min_time;
size_t imu_counter_ = 0;
bool imu_flash_ = false;
size_t disconnected_counter_ = 0;
diff --git a/y2022/control_loops/superstructure/superstructure.cc b/y2022/control_loops/superstructure/superstructure.cc
index c3d8fa5..8ff1e1c 100644
--- a/y2022/control_loops/superstructure/superstructure.cc
+++ b/y2022/control_loops/superstructure/superstructure.cc
@@ -32,6 +32,10 @@
"/drivetrain")),
can_position_fetcher_(
event_loop->MakeFetcher<CANPosition>("/superstructure")),
+ joystick_state_fetcher_(
+ event_loop->MakeFetcher<aos::JoystickState>("/aos")),
+ ball_color_fetcher_(event_loop->MakeFetcher<y2022::vision::BallColor>(
+ "/superstructure")),
aimer_(values) {
event_loop->SetRuntimeRealtimePriority(30);
}
@@ -55,10 +59,43 @@
frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal, 512>
turret_loading_goal_buffer;
aos::FlatbufferFixedAllocatorArray<CatapultGoal, 512> catapult_goal_buffer;
+ aos::FlatbufferFixedAllocatorArray<CatapultGoal, 512>
+ catapult_discarding_goal_buffer;
const aos::monotonic_clock::time_point timestamp =
event_loop()->context().monotonic_event_time;
+ if (joystick_state_fetcher_.Fetch() &&
+ joystick_state_fetcher_->has_alliance()) {
+ alliance_ = joystick_state_fetcher_->alliance();
+ }
+
+ if (ball_color_fetcher_.Fetch() && ball_color_fetcher_->has_ball_color()) {
+ ball_color_ = ball_color_fetcher_->ball_color();
+ }
+
+ if (alliance_ != aos::Alliance::kInvalid &&
+ ball_color_ != aos::Alliance::kInvalid && alliance_ != ball_color_) {
+ switch (state_) {
+ case SuperstructureState::IDLE:
+ break;
+ case SuperstructureState::TRANSFERRING:
+ break;
+ case SuperstructureState::LOADING:
+ break;
+ case SuperstructureState::LOADED:
+ discarding_ball_ = true;
+ break;
+ case SuperstructureState::SHOOTING:
+ if (!fire_) {
+ // we can still tell it not to shoot into the hub
+ // and change the turret and catapult goals
+ discarding_ball_ = true;
+ }
+ break;
+ }
+ }
+
drivetrain_status_fetcher_.Fetch();
const float velocity = robot_velocity();
@@ -92,8 +129,9 @@
climber_servo = unsafe_goal->climber_servo();
- turret_goal =
- unsafe_goal->auto_aim() ? auto_aim_goal : unsafe_goal->turret();
+ turret_goal = unsafe_goal->auto_aim() && !discarding_ball_
+ ? auto_aim_goal
+ : unsafe_goal->turret();
catapult_goal = unsafe_goal->catapult();
@@ -107,7 +145,7 @@
std::optional<flatbuffers::Offset<
frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal>>
return_position_offset;
- if (unsafe_goal != nullptr && unsafe_goal->has_catapult() &&
+ if (unsafe_goal->has_catapult() &&
unsafe_goal->catapult()->has_return_position()) {
return_position_offset = {aos::CopyFlatBuffer(
unsafe_goal->catapult()->return_position(), catapult_goal_fbb)};
@@ -122,6 +160,27 @@
catapult_goal = &catapult_goal_buffer.message();
}
+ if (discarding_ball_) {
+ flatbuffers::FlatBufferBuilder *catapult_goal_fbb =
+ catapult_discarding_goal_buffer.fbb();
+ std::optional<flatbuffers::Offset<
+ frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal>>
+ return_position_offset;
+ if (unsafe_goal->has_catapult() &&
+ unsafe_goal->catapult()->has_return_position()) {
+ return_position_offset = {aos::CopyFlatBuffer(
+ unsafe_goal->catapult()->return_position(), catapult_goal_fbb)};
+ }
+ CatapultGoal::Builder builder(*catapult_goal_fbb);
+ builder.add_shot_position(kDiscardingPosition);
+ builder.add_shot_velocity(kDiscardingVelocity);
+ if (return_position_offset.has_value()) {
+ builder.add_return_position(return_position_offset.value());
+ }
+ catapult_discarding_goal_buffer.Finish(builder.Finish());
+ catapult_goal = &catapult_discarding_goal_buffer.message();
+ }
+
if (unsafe_goal->has_turret_intake()) {
have_active_intake_request = true;
}
@@ -326,6 +385,8 @@
} else if (timestamp >
loading_timer_ + constants::Values::kExtraLoadingTime()) {
state_ = SuperstructureState::LOADED;
+ // reset color and wait for a new one once we know the ball is in place
+ ball_color_ = aos::Alliance::kInvalid;
reseating_in_catapult_ = false;
}
break;
@@ -339,10 +400,11 @@
}
turret_goal = &turret_loading_goal_buffer.message();
}
+
if (unsafe_goal->cancel_shot()) {
// Cancel the shot process
state_ = SuperstructureState::IDLE;
- } else if (unsafe_goal->fire()) {
+ } else if (unsafe_goal->fire() || discarding_ball_) {
// Start if we were asked to and the turret is at goal
state_ = SuperstructureState::SHOOTING;
prev_shot_count_ = catapult_.shot_count();
@@ -428,6 +490,7 @@
if (catapult_.shot_count() > prev_shot_count_ && near_return_position) {
prev_shot_count_ = catapult_.shot_count();
fire_ = false;
+ discarding_ball_ = false;
state_ = SuperstructureState::IDLE;
}
@@ -495,7 +558,7 @@
output_struct.transfer_roller_voltage = transfer_roller_speed;
output_struct.flipper_arms_voltage = flipper_arms_voltage;
if (climber_servo) {
- output_struct.climber_servo_left = 0.0;
+ output_struct.climber_servo_left = 0.5;
output_struct.climber_servo_right = 1.0;
} else {
output_struct.climber_servo_left = 1.0;
@@ -535,6 +598,7 @@
status_builder.add_reseating_in_catapult(reseating_in_catapult_);
status_builder.add_fire(fire_);
status_builder.add_moving_too_fast(moving_too_fast);
+ status_builder.add_discarding_ball(discarding_ball_);
status_builder.add_ready_to_fire(state_ == SuperstructureState::LOADED &&
turret_near_goal && !collided);
status_builder.add_state(state_);
diff --git a/y2022/control_loops/superstructure/superstructure.h b/y2022/control_loops/superstructure/superstructure.h
index 14fa8ab..48d07f1 100644
--- a/y2022/control_loops/superstructure/superstructure.h
+++ b/y2022/control_loops/superstructure/superstructure.h
@@ -13,6 +13,7 @@
#include "y2022/control_loops/superstructure/superstructure_position_generated.h"
#include "y2022/control_loops/superstructure/superstructure_status_generated.h"
#include "y2022/control_loops/superstructure/turret/aiming.h"
+#include "y2022/vision/ball_color_generated.h"
namespace y2022 {
namespace control_loops {
@@ -35,6 +36,8 @@
static constexpr double kCatapultGoalThreshold = 0.05;
// potentiometer will be more noisy
static constexpr double kFlipperGoalThreshold = 0.05;
+ static constexpr double kDiscardingPosition = 0.35;
+ static constexpr double kDiscardingVelocity = 6.0;
explicit Superstructure(::aos::EventLoop *event_loop,
std::shared_ptr<const constants::Values> values,
@@ -72,6 +75,8 @@
aos::Fetcher<frc971::control_loops::drivetrain::Status>
drivetrain_status_fetcher_;
aos::Fetcher<CANPosition> can_position_fetcher_;
+ aos::Fetcher<aos::JoystickState> joystick_state_fetcher_;
+ aos::Fetcher<y2022::vision::BallColor> ball_color_fetcher_;
int prev_shot_count_ = 0;
@@ -80,6 +85,9 @@
bool flippers_open_ = false;
bool reseating_in_catapult_ = false;
bool fire_ = false;
+ bool discarding_ball_ = false;
+ aos::Alliance alliance_ = aos::Alliance::kInvalid;
+ aos::Alliance ball_color_ = aos::Alliance::kInvalid;
aos::monotonic_clock::time_point front_intake_beambreak_timer_ =
aos::monotonic_clock::min_time;
diff --git a/y2022/control_loops/superstructure/superstructure_lib_test.cc b/y2022/control_loops/superstructure/superstructure_lib_test.cc
index 42fd861..093da84 100644
--- a/y2022/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2022/control_loops/superstructure/superstructure_lib_test.cc
@@ -328,9 +328,11 @@
values_(std::make_shared<constants::Values>(constants::MakeValues(
frc971::control_loops::testing::kTeamNumber))),
roborio_(aos::configuration::GetNode(configuration(), "roborio")),
+ logger_pi_(aos::configuration::GetNode(configuration(), "logger")),
superstructure_event_loop(MakeEventLoop("Superstructure", roborio_)),
superstructure_(superstructure_event_loop.get(), values_),
test_event_loop_(MakeEventLoop("test", roborio_)),
+ ball_color_event_loop_(MakeEventLoop("ball color test", logger_pi_)),
superstructure_goal_fetcher_(
test_event_loop_->MakeFetcher<Goal>("/superstructure")),
superstructure_goal_sender_(
@@ -345,6 +347,9 @@
test_event_loop_->MakeSender<Position>("/superstructure")),
drivetrain_status_sender_(
test_event_loop_->MakeSender<DrivetrainStatus>("/drivetrain")),
+ ball_color_sender_(
+ ball_color_event_loop_->MakeSender<y2022::vision::BallColor>(
+ "/superstructure")),
superstructure_plant_event_loop_(MakeEventLoop("plant", roborio_)),
superstructure_plant_(superstructure_plant_event_loop_.get(), values_,
dt()) {
@@ -476,10 +481,12 @@
std::shared_ptr<const constants::Values> values_;
const aos::Node *const roborio_;
+ const aos::Node *const logger_pi_;
::std::unique_ptr<::aos::EventLoop> superstructure_event_loop;
::y2022::control_loops::superstructure::Superstructure superstructure_;
::std::unique_ptr<::aos::EventLoop> test_event_loop_;
+ ::std::unique_ptr<aos::EventLoop> ball_color_event_loop_;
::aos::PhasedLoopHandler *phased_loop_handle_ = nullptr;
::aos::Fetcher<Goal> superstructure_goal_fetcher_;
@@ -489,6 +496,7 @@
::aos::Fetcher<Position> superstructure_position_fetcher_;
::aos::Sender<Position> superstructure_position_sender_;
::aos::Sender<DrivetrainStatus> drivetrain_status_sender_;
+ ::aos::Sender<y2022::vision::BallColor> ball_color_sender_;
::std::unique_ptr<::aos::EventLoop> superstructure_plant_event_loop_;
SuperstructureSimulation superstructure_plant_;
@@ -1286,6 +1294,80 @@
shot_params.shot_angle);
}
+// Tests that balls get discarded when they are the wrong color.
+TEST_F(SuperstructureTest, BallDiscarding) {
+ set_alliance(aos::Alliance::kInvalid);
+ SetEnabled(true);
+ WaitUntilZeroed();
+
+ // Set ourselves up 5m from the target--the turret goal should be 90 deg (we
+ // need to shoot out the right of the robot, and we shoot out of the back of
+ // the turret).
+ SendDrivetrainStatus(0.0, {0.0, 5.0}, 0.0);
+
+ RunFor(chrono::milliseconds(500));
+ set_alliance(aos::Alliance::kBlue);
+
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_auto_aim(true);
+ goal_builder.add_preloaded(true);
+ goal_builder.add_turret_intake(RequestedIntake::kFront);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ // Give it time to stabilize.
+ RunFor(chrono::seconds(2));
+
+ superstructure_status_fetcher_.Fetch();
+ EXPECT_NEAR(M_PI_2, superstructure_status_fetcher_->turret()->position(),
+ 5e-4);
+
+ {
+ auto builder = ball_color_sender_.MakeBuilder();
+
+ y2022::vision::BallColor::Builder ball_color_builder =
+ builder.MakeBuilder<y2022::vision::BallColor>();
+
+ ball_color_builder.add_ball_color(aos::Alliance::kBlue);
+
+ ASSERT_EQ(builder.Send(ball_color_builder.Finish()),
+ aos::RawSender::Error::kOk);
+ }
+
+ RunFor(chrono::milliseconds(100));
+ superstructure_status_fetcher_.Fetch();
+ EXPECT_EQ(superstructure_status_fetcher_->state(),
+ SuperstructureState::LOADED);
+
+ {
+ auto builder = ball_color_sender_.MakeBuilder();
+
+ y2022::vision::BallColor::Builder ball_color_builder =
+ builder.MakeBuilder<y2022::vision::BallColor>();
+
+ ball_color_builder.add_ball_color(aos::Alliance::kRed);
+
+ ASSERT_EQ(builder.Send(ball_color_builder.Finish()),
+ aos::RawSender::Error::kOk);
+ }
+
+ RunFor(dt());
+
+ superstructure_status_fetcher_.Fetch();
+ EXPECT_EQ(superstructure_status_fetcher_->state(),
+ SuperstructureState::SHOOTING);
+
+ RunFor(chrono::milliseconds(2000));
+ superstructure_status_fetcher_.Fetch();
+ EXPECT_NEAR(constants::Values::kTurretFrontIntakePos(),
+ superstructure_status_fetcher_->turret()->position(), 5e-4);
+}
+
} // namespace testing
} // namespace superstructure
} // namespace control_loops
diff --git a/y2022/control_loops/superstructure/superstructure_status.fbs b/y2022/control_loops/superstructure/superstructure_status.fbs
index 9cf9a5f..ac60ce3 100644
--- a/y2022/control_loops/superstructure/superstructure_status.fbs
+++ b/y2022/control_loops/superstructure/superstructure_status.fbs
@@ -59,6 +59,9 @@
ready_to_fire:bool (id: 20);
// Whether the robot is moving too fast to shoot
moving_too_fast:bool (id: 21);
+ // True if the robot has detected that it is holding
+ // the wrong color ball and is now discarding it.
+ discarding_ball:bool (id: 22);
// Whether the catapult was told to fire,
// meaning that the turret and flippers are ready for firing
// and we were asked to fire. Different from fire flag in goal.
diff --git a/y2022/localizer/BUILD b/y2022/localizer/BUILD
index edc3791..243259a 100644
--- a/y2022/localizer/BUILD
+++ b/y2022/localizer/BUILD
@@ -105,6 +105,7 @@
"//frc971/control_loops/drivetrain:drivetrain_status_fbs",
"//frc971/control_loops/drivetrain:improved_down_estimator",
"//frc971/control_loops/drivetrain:localizer_fbs",
+ "//frc971/input:joystick_state_fbs",
"//frc971/wpilib:imu_batch_fbs",
"//frc971/wpilib:imu_fbs",
"//frc971/zeroing:imu_zeroer",
diff --git a/y2022/localizer/localizer.cc b/y2022/localizer/localizer.cc
index 783d368..4b0588c 100644
--- a/y2022/localizer/localizer.cc
+++ b/y2022/localizer/localizer.cc
@@ -642,7 +642,11 @@
H_model(1, kY) = 1.0;
H_accel(0, kX) = 1.0;
H_accel(1, kY) = 1.0;
- R.diagonal() << 1e-2, 1e-2;
+ if (aggressive_corrections_) {
+ R.diagonal() << 1e-2, 1e-2;
+ } else {
+ R.diagonal() << 1e-0, 1e-0;
+ }
const Eigen::Matrix<double, kNModelStates, 2> K_model =
P_model_ * H_model.transpose() *
@@ -886,6 +890,8 @@
event_loop_
->MakeFetcher<y2022::control_loops::superstructure::Status>(
"/superstructure")),
+ joystick_state_fetcher_(
+ event_loop_->MakeFetcher<aos::JoystickState>("/roborio/aos")),
zeroer_(zeroing::ImuZeroer::FaultBehavior::kTemporary),
left_encoder_(-DrivetrainWrapPeriod() / 2.0, DrivetrainWrapPeriod()),
right_encoder_(-DrivetrainWrapPeriod() / 2.0, DrivetrainWrapPeriod()) {
@@ -926,39 +932,44 @@
absl::StrCat("/", kPisToUse[camera_index], "/camera"));
}
aos::TimerHandler *estimate_timer = event_loop_->AddTimer([this]() {
- for (size_t camera_index = 0; camera_index < kPisToUse.size();
- ++camera_index) {
- if (model_based_.NumQueuedImageDebugs() ==
- ModelBasedLocalizer::kDebugBufferSize ||
- (last_visualization_send_ + kMinVisualizationPeriod <
- event_loop_->monotonic_now())) {
- auto builder = visualization_sender_.MakeBuilder();
- visualization_sender_.CheckOk(
- builder.Send(model_based_.PopulateVisualization(builder.fbb())));
- }
- if (target_estimate_fetchers_[camera_index].Fetch()) {
- const std::optional<aos::monotonic_clock::duration> monotonic_offset =
- ClockOffset(kPisToUse[camera_index]);
- if (!monotonic_offset.has_value()) {
- continue;
+ joystick_state_fetcher_.Fetch();
+ const bool maybe_in_auto = (joystick_state_fetcher_.get() != nullptr)
+ ? joystick_state_fetcher_->autonomous()
+ : true;
+ model_based_.set_use_aggressive_image_corrections(!maybe_in_auto);
+ for (size_t camera_index = 0; camera_index < kPisToUse.size();
+ ++camera_index) {
+ if (model_based_.NumQueuedImageDebugs() ==
+ ModelBasedLocalizer::kDebugBufferSize ||
+ (last_visualization_send_ + kMinVisualizationPeriod <
+ event_loop_->monotonic_now())) {
+ auto builder = visualization_sender_.MakeBuilder();
+ visualization_sender_.CheckOk(
+ builder.Send(model_based_.PopulateVisualization(builder.fbb())));
}
- // TODO(james): Get timestamp from message contents.
- aos::monotonic_clock::time_point capture_time(
- target_estimate_fetchers_[camera_index]
- .context()
- .monotonic_remote_time -
- monotonic_offset.value());
- if (capture_time > target_estimate_fetchers_[camera_index]
- .context()
- .monotonic_event_time) {
- model_based_.TallyRejection(RejectionReason::IMAGE_FROM_FUTURE);
- continue;
+ if (target_estimate_fetchers_[camera_index].Fetch()) {
+ const std::optional<aos::monotonic_clock::duration> monotonic_offset =
+ ClockOffset(kPisToUse[camera_index]);
+ if (!monotonic_offset.has_value()) {
+ continue;
+ }
+ // TODO(james): Get timestamp from message contents.
+ aos::monotonic_clock::time_point capture_time(
+ target_estimate_fetchers_[camera_index]
+ .context()
+ .monotonic_remote_time -
+ monotonic_offset.value());
+ if (capture_time > target_estimate_fetchers_[camera_index]
+ .context()
+ .monotonic_event_time) {
+ model_based_.TallyRejection(RejectionReason::IMAGE_FROM_FUTURE);
+ continue;
+ }
+ capture_time -= pico_offset_error_;
+ model_based_.HandleImageMatch(
+ capture_time, target_estimate_fetchers_[camera_index].get(),
+ camera_index);
}
- capture_time -= pico_offset_error_;
- model_based_.HandleImageMatch(
- capture_time, target_estimate_fetchers_[camera_index].get(),
- camera_index);
- }
}
});
event_loop_->OnRun([this, estimate_timer]() {
@@ -1098,6 +1109,7 @@
output_builder.add_y(model_based_.xytheta()(1));
output_builder.add_theta(model_based_.xytheta()(2));
output_builder.add_zeroed(zeroer_.Zeroed());
+ output_builder.add_image_accepted_count(model_based_.total_accepted());
const Eigen::Quaterniond &orientation = model_based_.orientation();
Quaternion quaternion;
quaternion.mutate_x(orientation.x());
diff --git a/y2022/localizer/localizer.h b/y2022/localizer/localizer.h
index f8205d7..fc15e9f 100644
--- a/y2022/localizer/localizer.h
+++ b/y2022/localizer/localizer.h
@@ -9,6 +9,7 @@
#include "aos/network/message_bridge_server_generated.h"
#include "aos/time/time.h"
#include "frc971/control_loops/drivetrain/drivetrain_output_generated.h"
+#include "frc971/input/joystick_state_generated.h"
#include "frc971/control_loops/drivetrain/improved_down_estimator.h"
#include "frc971/control_loops/drivetrain/localizer_generated.h"
#include "frc971/zeroing/imu_zeroer.h"
@@ -136,6 +137,9 @@
AccelState accel_state() const { return current_state_.accel_state; };
void set_longitudinal_offset(double offset) { long_offset_ = offset; }
+ void set_use_aggressive_image_corrections(bool aggressive) {
+ aggressive_corrections_ = aggressive;
+ }
void TallyRejection(const RejectionReason reason);
@@ -146,6 +150,8 @@
std::array<LedOutput, kNumPis> led_outputs() const { return led_outputs_; }
+ int total_accepted() const { return statistics_.total_accepted; }
+
private:
struct CombinedState {
AccelState accel_state = AccelState::Zero();
@@ -267,6 +273,10 @@
// center, negative = behind center.
double long_offset_ = -0.15;
+ // Whether to use more aggressive corrections on the localizer. Only do this
+ // in teleop, since it can make spline control really jumpy.
+ bool aggressive_corrections_ = false;
+
double last_residual_ = 0.0;
double filtered_residual_ = 0.0;
Eigen::Vector2d filtered_residual_accel_ = Eigen::Vector2d::Zero();
@@ -334,6 +344,7 @@
target_estimate_fetchers_;
aos::Fetcher<y2022::control_loops::superstructure::Status>
superstructure_fetcher_;
+ aos::Fetcher<aos::JoystickState> joystick_state_fetcher_;
zeroing::ImuZeroer zeroer_;
aos::monotonic_clock::time_point last_output_send_ =
aos::monotonic_clock::min_time;
diff --git a/y2022/localizer/localizer_output.fbs b/y2022/localizer/localizer_output.fbs
index ec3302a..ff25c31 100644
--- a/y2022/localizer/localizer_output.fbs
+++ b/y2022/localizer/localizer_output.fbs
@@ -34,6 +34,9 @@
// Whether each led should be on.
// Indices correspond to pi number.
led_outputs:[LedOutput] (id: 6);
+
+ // Cumulative number of accepted images.
+ image_accepted_count:uint (id: 7);
}
root_type LocalizerOutput;
diff --git a/y2022/vision/camera_reader_main.cc b/y2022/vision/camera_reader_main.cc
index 9320152..bfd1c7d 100644
--- a/y2022/vision/camera_reader_main.cc
+++ b/y2022/vision/camera_reader_main.cc
@@ -7,10 +7,10 @@
// --override_hostname pi-7971-1 --ignore_timestamps true
DECLARE_bool(use_outdoors);
DEFINE_string(config, "aos_config.json", "Path to the config file to use.");
-DEFINE_double(duty_cycle, 0.6, "Duty cycle of the LEDs");
+DEFINE_double(duty_cycle, 0.65, "Duty cycle of the LEDs");
DEFINE_uint32(exposure, 5,
"Exposure time, in 100us increments; 0 implies auto exposure");
-DEFINE_uint32(outdoors_exposure, 20,
+DEFINE_uint32(outdoors_exposure, 4,
"Exposure time when using --use_outdoors, in 100us increments; 0 "
"implies auto exposure");
diff --git a/y2022/y2022_logger.json b/y2022/y2022_logger.json
index f54ccd7..196ee96 100644
--- a/y2022/y2022_logger.json
+++ b/y2022/y2022_logger.json
@@ -42,7 +42,7 @@
]
},
{
- "name": "/aos/remote_timestamps/roborio/superstructure/y2022-vision-BallColor",
+ "name": "/logger/aos/remote_timestamps/roborio/superstructure/y2022-vision-BallColor",
"type": "aos.message_bridge.RemoteMessage",
"source_node": "logger",
"logger": "NOT_LOGGED",
@@ -391,6 +391,7 @@
{
"name": "/logger/camera",
"type": "frc971.vision.CameraImage",
+ "logger": "NOT_LOGGED",
"source_node": "logger",
"frequency": 100,
"max_size": 620000,