Merge "Forward JoystickState to vision pis"
diff --git a/aos/starter/starterd_lib.cc b/aos/starter/starterd_lib.cc
index 485d1f1..b8b7343 100644
--- a/aos/starter/starterd_lib.cc
+++ b/aos/starter/starterd_lib.cc
@@ -84,7 +84,8 @@
if (aos::configuration::MultiNode(config_msg_)) {
std::string_view current_node = event_loop_.node()->name()->string_view();
for (const aos::Application *application : *applications) {
- CHECK(application->has_nodes());
+ CHECK(application->has_nodes())
+ << ": Missing nodes on " << aos::FlatbufferToJson(application);
for (const flatbuffers::String *node : *application->nodes()) {
if (node->string_view() == current_node) {
AddApplication(application);
diff --git a/frc971/vision/BUILD b/frc971/vision/BUILD
index 936a441..f3ebecb 100644
--- a/frc971/vision/BUILD
+++ b/frc971/vision/BUILD
@@ -32,6 +32,13 @@
visibility = ["//visibility:public"],
)
+flatbuffer_ts_library(
+ name = "target_map_ts_fbs",
+ srcs = ["target_map.fbs"],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+)
+
flatbuffer_py_library(
name = "calibration_fbs_python",
srcs = [
diff --git a/frc971/vision/target_map.fbs b/frc971/vision/target_map.fbs
index e635760..de79744 100644
--- a/frc971/vision/target_map.fbs
+++ b/frc971/vision/target_map.fbs
@@ -63,6 +63,13 @@
// End-of-frame timestamp for the frame with tag detections.
// (for use case 2.).
monotonic_timestamp_ns:int64 (id: 2);
+
+ // Number of april tags rejected (cumulative) because
+ // of low decision margin (affected by lighting).
+ // We do the decision margin rejection in aprilrobotics
+ // so we don't have an excessive amount of random target
+ // detections (for use case 2).
+ rejections:uint64 (id: 3);
}
root_type TargetMap;
diff --git a/scouting/DriverRank/src/DriverRank.jl b/scouting/DriverRank/src/DriverRank.jl
index 39ac95e..e759fea 100755
--- a/scouting/DriverRank/src/DriverRank.jl
+++ b/scouting/DriverRank/src/DriverRank.jl
@@ -106,7 +106,8 @@
input_csv::String,
output_csv::String,
)
- df = DataFrame(CSV.File(input_csv))
+ # Force all team numbers to be parsed as strings.
+ df = DataFrame(CSV.File(input_csv, types=String))
rank1 = "Rank 1 (best)"
rank2 = "Rank 2"
diff --git a/scouting/db/db.go b/scouting/db/db.go
index adf1eae..8c7a93a 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -7,6 +7,7 @@
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
+ "strconv"
)
type Database struct {
@@ -19,7 +20,7 @@
CompLevel string `gorm:"primaryKey"`
Alliance string `gorm:"primaryKey"` // "R" or "B"
AlliancePosition int32 `gorm:"primaryKey"` // 1, 2, or 3
- TeamNumber int32
+ TeamNumber string
}
type Shift struct {
@@ -75,7 +76,7 @@
LowConesAuto, MiddleConesAuto, HighConesAuto, ConesDroppedAuto int32
LowCubes, MiddleCubes, HighCubes, CubesDropped int32
LowCones, MiddleCones, HighCones, ConesDropped int32
- AvgCycle int32
+ AvgCycle int64
// The username of the person who collected these statistics.
// "unknown" if submitted without logging in.
// Empty if the stats have not yet been collected.
@@ -195,7 +196,7 @@
}
func (database *Database) AddToStats(s Stats) error {
- matches, err := database.queryMatches(s.TeamNumber)
+ matches, err := database.queryMatches(strconv.Itoa(int(s.TeamNumber)))
if err != nil {
return err
}
@@ -344,7 +345,7 @@
return rankins, result.Error
}
-func (database *Database) queryMatches(teamNumber_ int32) ([]TeamMatch, error) {
+func (database *Database) queryMatches(teamNumber_ string) ([]TeamMatch, error) {
var matches []TeamMatch
result := database.
Where("team_number = $1", teamNumber_).
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index a6bfa79..294dc13 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -6,7 +6,6 @@
"os"
"os/exec"
"reflect"
- "strconv"
"strings"
"testing"
"time"
@@ -75,27 +74,27 @@
correct := []TeamMatch{
TeamMatch{
MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 9999,
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "9999",
},
TeamMatch{
MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 1000,
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "1000",
},
TeamMatch{
MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 777,
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "777",
},
TeamMatch{
MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 0000,
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "0000",
},
TeamMatch{
MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 4321,
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "4321",
},
TeamMatch{
MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 1234,
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "1234",
},
}
@@ -202,17 +201,17 @@
}
matches := []TeamMatch{
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 1236},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "1236"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 1001},
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "1001"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 777},
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "777"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 1000},
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "1000"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 4321},
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "4321"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 1234},
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "1234"},
}
for _, match := range matches {
@@ -292,15 +291,15 @@
matches := []TeamMatch{
TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 6344},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "6344"},
TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 7454},
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "7454"},
TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 4354},
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "4354"},
TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 6533},
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "6533"},
TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 8354},
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "8354"},
}
for _, match := range matches {
@@ -360,11 +359,11 @@
matches := []TeamMatch{
TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 6344},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "6344"},
TeamMatch{MatchNumber: 4, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 7454},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "7454"},
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 6344},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "6344"},
}
for _, match := range matches {
@@ -413,17 +412,17 @@
matches := []TeamMatch{
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 1236},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "1236"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 1001},
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "1001"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 777},
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "777"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 1000},
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "1000"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 4321},
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "4321"},
TeamMatch{MatchNumber: 7, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 1234},
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "1234"},
}
for _, match := range matches {
@@ -527,25 +526,25 @@
originalMatches := []TeamMatch{
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 1111},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "1111"},
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 2314},
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "2314"},
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 1742},
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "1742"},
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 2454},
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "2454"},
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 3242},
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "3242"},
}
// Matches for which we want to delete the stats.
matches := []TeamMatch{
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "quals",
- TeamNumber: 1111},
+ TeamNumber: "1111"},
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "quals",
- TeamNumber: 2314},
+ TeamNumber: "2314"},
TeamMatch{MatchNumber: 5, SetNumber: 1, CompLevel: "quals",
- TeamNumber: 1742},
+ TeamNumber: "1742"},
}
for _, match := range originalMatches {
@@ -560,7 +559,7 @@
}
for _, match := range matches {
- err := fixture.db.DeleteFromStats(match.CompLevel, match.MatchNumber, match.SetNumber, strconv.Itoa(int(match.TeamNumber)))
+ err := fixture.db.DeleteFromStats(match.CompLevel, match.MatchNumber, match.SetNumber, match.TeamNumber)
check(t, err, "Failed to delete stat")
}
@@ -663,17 +662,17 @@
matches := []TeamMatch{
TeamMatch{MatchNumber: 94, SetNumber: 2, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 1235},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "1235"},
TeamMatch{MatchNumber: 94, SetNumber: 2, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 1234},
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "1234"},
TeamMatch{MatchNumber: 94, SetNumber: 2, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 1233},
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "1233"},
TeamMatch{MatchNumber: 94, SetNumber: 2, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 1232},
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "1232"},
TeamMatch{MatchNumber: 94, SetNumber: 2, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 1231},
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "1231"},
TeamMatch{MatchNumber: 94, SetNumber: 2, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 1239},
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "1239"},
}
for _, match := range matches {
@@ -759,15 +758,15 @@
correct := []TeamMatch{
TeamMatch{
- MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "R", AlliancePosition: 1, TeamNumber: 6835},
+ MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "R", AlliancePosition: 1, TeamNumber: "6835"},
TeamMatch{
- MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "R", AlliancePosition: 2, TeamNumber: 4834},
+ MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "R", AlliancePosition: 2, TeamNumber: "4834"},
TeamMatch{
- MatchNumber: 9, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 3, TeamNumber: 9824},
+ MatchNumber: 9, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 3, TeamNumber: "9824"},
TeamMatch{
- MatchNumber: 7, SetNumber: 2, CompLevel: "quals", Alliance: "B", AlliancePosition: 1, TeamNumber: 3732},
+ MatchNumber: 7, SetNumber: 2, CompLevel: "quals", Alliance: "B", AlliancePosition: 1, TeamNumber: "3732"},
TeamMatch{
- MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 1, TeamNumber: 3732},
+ MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 1, TeamNumber: "3732"},
}
for i := 0; i < len(correct); i++ {
@@ -789,11 +788,11 @@
testDatabase := []TeamMatch{
TeamMatch{
- MatchNumber: 9, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 3, TeamNumber: 4464},
+ MatchNumber: 9, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 3, TeamNumber: "4464"},
TeamMatch{
- MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "R", AlliancePosition: 2, TeamNumber: 2352},
+ MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "R", AlliancePosition: 2, TeamNumber: "2352"},
TeamMatch{
- MatchNumber: 9, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 3, TeamNumber: 6321},
+ MatchNumber: 9, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 3, TeamNumber: "6321"},
}
for i := 0; i < len(testDatabase); i++ {
@@ -803,9 +802,9 @@
correct := []TeamMatch{
TeamMatch{
- MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "R", AlliancePosition: 2, TeamNumber: 2352},
+ MatchNumber: 8, SetNumber: 1, CompLevel: "quals", Alliance: "R", AlliancePosition: 2, TeamNumber: "2352"},
TeamMatch{
- MatchNumber: 9, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 3, TeamNumber: 6321},
+ MatchNumber: 9, SetNumber: 1, CompLevel: "quals", Alliance: "B", AlliancePosition: 3, TeamNumber: "6321"},
}
got, err := fixture.db.ReturnMatches()
@@ -940,17 +939,17 @@
matches := []TeamMatch{
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 1235},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "1235"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 1236},
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "1236"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 1237},
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "1237"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 1238},
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "1238"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 1239},
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "1239"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 1233},
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "1233"},
}
for _, match := range matches {
@@ -1020,13 +1019,13 @@
matches := []TeamMatch{
TeamMatch{MatchNumber: 2, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 2343},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "2343"},
TeamMatch{MatchNumber: 2, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 5443},
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "5443"},
TeamMatch{MatchNumber: 2, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 5436},
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "5436"},
TeamMatch{MatchNumber: 2, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 5643},
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "5643"},
}
for _, match := range matches {
@@ -1079,17 +1078,17 @@
matches := []TeamMatch{
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 1235},
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "1235"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 1236},
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "1236"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 1237},
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "1237"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 1238},
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "1238"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 1239},
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "1239"},
TeamMatch{MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 1233},
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "1233"},
}
for _, match := range matches {
diff --git a/scouting/scraping/scrape.go b/scouting/scraping/scrape.go
index 9cb2336..c6aa6f4 100644
--- a/scouting/scraping/scrape.go
+++ b/scouting/scraping/scrape.go
@@ -78,7 +78,7 @@
defer resp.Body.Close()
if resp.StatusCode != 200 {
- return nil, errors.New(fmt.Sprint("Got unexpected status code from TBA API request: ", resp.Status))
+ return nil, errors.New(fmt.Sprint("Got unexpected status code from TBA API request ", req.URL, ": ", resp.Status))
}
// Get all bytes from response body.
diff --git a/scouting/webserver/main.go b/scouting/webserver/main.go
index 4752cf4..1b5a002 100644
--- a/scouting/webserver/main.go
+++ b/scouting/webserver/main.go
@@ -9,6 +9,7 @@
"log"
"os"
"os/signal"
+ "path"
"strconv"
"syscall"
"time"
@@ -58,6 +59,14 @@
return 8080
}
+func getDefaultBlueAllianceConfig() string {
+ workspaceDirectory := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
+ if workspaceDirectory != "" {
+ return path.Join(workspaceDirectory, "scouting_config.json")
+ }
+ return "scouting_config.json"
+}
+
func main() {
portPtr := flag.Int("port", getDefaultPort(), "The port number to bind to.")
dirPtr := flag.String("directory", ".", "The directory to serve at /.")
@@ -71,9 +80,11 @@
"-db_config.")
dbConnectRetries := flag.Int("db_retries", 5,
"The number of seconds to retry connecting to the database on startup.")
- blueAllianceConfigPtr := flag.String("tba_config", "",
+ blueAllianceConfigPtr := flag.String("tba_config", getDefaultBlueAllianceConfig(),
"The path to your The Blue Alliance JSON config. "+
"It needs an \"api_key\" field with your TBA API key. "+
+ "It needs a \"year\" field with the event year. "+
+ "It needs an \"event_code\" field with the event code. "+
"Optionally, it can have a \"base_url\" field with the TBA API base URL.")
flag.Parse()
diff --git a/scouting/webserver/match_list/match_list.go b/scouting/webserver/match_list/match_list.go
index 9029438..c5af661 100644
--- a/scouting/webserver/match_list/match_list.go
+++ b/scouting/webserver/match_list/match_list.go
@@ -97,32 +97,32 @@
{
MatchNumber: int32(match.MatchNumber),
SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "R", AlliancePosition: 1, TeamNumber: red[0],
+ Alliance: "R", AlliancePosition: 1, TeamNumber: strconv.Itoa(int(red[0])),
},
{
MatchNumber: int32(match.MatchNumber),
SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "R", AlliancePosition: 2, TeamNumber: red[1],
+ Alliance: "R", AlliancePosition: 2, TeamNumber: strconv.Itoa(int(red[1])),
},
{
MatchNumber: int32(match.MatchNumber),
SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "R", AlliancePosition: 3, TeamNumber: red[2],
+ Alliance: "R", AlliancePosition: 3, TeamNumber: strconv.Itoa(int(red[2])),
},
{
MatchNumber: int32(match.MatchNumber),
SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "B", AlliancePosition: 1, TeamNumber: blue[0],
+ Alliance: "B", AlliancePosition: 1, TeamNumber: strconv.Itoa(int(blue[0])),
},
{
MatchNumber: int32(match.MatchNumber),
SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "B", AlliancePosition: 2, TeamNumber: blue[1],
+ Alliance: "B", AlliancePosition: 2, TeamNumber: strconv.Itoa(int(blue[1])),
},
{
MatchNumber: int32(match.MatchNumber),
SetNumber: int32(match.SetNumber), CompLevel: match.CompLevel,
- Alliance: "B", AlliancePosition: 3, TeamNumber: blue[2],
+ Alliance: "B", AlliancePosition: 3, TeamNumber: strconv.Itoa(int(blue[2])),
},
}
diff --git a/scouting/webserver/requests/BUILD b/scouting/webserver/requests/BUILD
index 4c4870c..935d721 100644
--- a/scouting/webserver/requests/BUILD
+++ b/scouting/webserver/requests/BUILD
@@ -60,6 +60,7 @@
"//scouting/webserver/requests/messages:request_notes_for_team_go_fbs",
"//scouting/webserver/requests/messages:request_shift_schedule_go_fbs",
"//scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs",
+ "//scouting/webserver/requests/messages:submit_actions_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
"//scouting/webserver/requests/messages:submit_driver_ranking_go_fbs",
diff --git a/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs b/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs
index d9d36b3..93583ce 100644
--- a/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs
+++ b/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs
@@ -24,7 +24,8 @@
middle_cones:int (id:16);
high_cones:int (id:17);
cones_dropped:int (id:18);
- avg_cycle:int (id:19);
+ // Time in nanoseconds.
+ avg_cycle:int64 (id:19);
collected_by:string (id:20);
}
@@ -33,4 +34,4 @@
stats_list:[Stats2023] (id:0);
}
-root_type Request2023DataScoutingResponse;
\ No newline at end of file
+root_type Request2023DataScoutingResponse;
diff --git a/scouting/webserver/requests/messages/request_all_matches_response.fbs b/scouting/webserver/requests/messages/request_all_matches_response.fbs
index 9d3be62..55da7bb 100644
--- a/scouting/webserver/requests/messages/request_all_matches_response.fbs
+++ b/scouting/webserver/requests/messages/request_all_matches_response.fbs
@@ -1,15 +1,28 @@
namespace scouting.webserver.requests;
+// Specifies whether a team has been scouted for this particular match.
+table ScoutedLevel {
+ r1: bool (id: 0);
+ r2: bool (id: 1);
+ r3: bool (id: 2);
+ b1: bool (id: 3);
+ b2: bool (id: 4);
+ b3: bool (id: 5);
+}
+
table Match {
match_number:int (id: 0);
set_number:int (id: 1);
comp_level:string (id: 2);
- r1:int (id: 3);
- r2:int (id: 4);
- r3:int (id: 5);
- b1:int (id: 6);
- b2:int (id: 7);
- b3:int (id: 8);
+ r1:string (id: 3);
+ r2:string (id: 4);
+ r3:string (id: 5);
+ b1:string (id: 6);
+ b2:string (id: 7);
+ b3:string (id: 8);
+
+ // Tells you how completely we've data scouted this match.
+ data_scouted: ScoutedLevel (id: 9);
}
table RequestAllMatchesResponse {
diff --git a/scouting/webserver/requests/messages/submit_actions.fbs b/scouting/webserver/requests/messages/submit_actions.fbs
index dfb980f..5488a79 100644
--- a/scouting/webserver/requests/messages/submit_actions.fbs
+++ b/scouting/webserver/requests/messages/submit_actions.fbs
@@ -50,12 +50,15 @@
}
table Action {
- timestamp:int (id:0);
+ timestamp:int64 (id:0);
action_taken:ActionType (id:2);
}
table SubmitActions {
- actions_list:[Action] (id:0);
+ team_number:string (id: 0);
+ match_number:int (id: 1);
+ set_number:int (id: 2);
+ comp_level:string (id: 3);
+ actions_list:[Action] (id:4);
+ collected_by:string (id: 5);
}
-
-root_type SubmitActions;
\ No newline at end of file
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 467542a..3aa9076 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -79,6 +79,7 @@
ReturnAllShifts() ([]db.Shift, error)
ReturnStats() ([]db.Stats, error)
ReturnStats2023() ([]db.Stats2023, error)
+ ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string) ([]db.Stats2023, error)
QueryAllShifts(int) ([]db.Shift, error)
QueryStats(int) ([]db.Stats, error)
QueryNotes(int32) ([]string, error)
@@ -212,6 +213,16 @@
db Database
}
+// Change structure of match objects in the database(1 per team) to
+// the old match structure(1 per match) that the webserver uses.
+// We use the information in this struct to identify which match object
+// corresponds to which old match structure object.
+type MatchAssemblyKey struct {
+ MatchNumber int32
+ SetNumber int32
+ CompLevel string
+}
+
func findIndexInList(list []string, comp_level string) (int, error) {
for index, value := range list {
if value == comp_level {
@@ -221,6 +232,15 @@
return -1, errors.New(fmt.Sprint("Failed to find comp level ", comp_level, " in list ", list))
}
+func (handler requestAllMatchesHandler) teamHasBeenDataScouted(key MatchAssemblyKey, teamNumber string) (bool, error) {
+ stats, err := handler.db.ReturnStats2023ForTeam(
+ teamNumber, key.MatchNumber, key.SetNumber, key.CompLevel)
+ if err != nil {
+ return false, err
+ }
+ return (len(stats) > 0), nil
+}
+
func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
requestBytes, err := io.ReadAll(req.Body)
if err != nil {
@@ -239,35 +259,42 @@
return
}
- // Change structure of match objects in the database(1 per team) to
- // the old match structure(1 per match) that the webserver uses.
- type Key struct {
- MatchNumber int32
- SetNumber int32
- CompLevel string
- }
-
- assembledMatches := map[Key]request_all_matches_response.MatchT{}
+ assembledMatches := map[MatchAssemblyKey]request_all_matches_response.MatchT{}
for _, match := range matches {
- key := Key{match.MatchNumber, match.SetNumber, match.CompLevel}
+ key := MatchAssemblyKey{match.MatchNumber, match.SetNumber, match.CompLevel}
+
+ // Retrieve the converted match structure we have assembled so
+ // far. If we haven't started assembling one yet, then start a
+ // new one.
entry, ok := assembledMatches[key]
if !ok {
entry = request_all_matches_response.MatchT{
MatchNumber: match.MatchNumber,
SetNumber: match.SetNumber,
CompLevel: match.CompLevel,
+ DataScouted: &request_all_matches_response.ScoutedLevelT{},
}
}
+
+ var team *string
+ var dataScoutedTeam *bool
+
+ // Fill in the field for the match that we have in in the
+ // database. In the database, each match row only has 1 team
+ // number.
switch match.Alliance {
case "R":
switch match.AlliancePosition {
case 1:
- entry.R1 = match.TeamNumber
+ team = &entry.R1
+ dataScoutedTeam = &entry.DataScouted.R1
case 2:
- entry.R2 = match.TeamNumber
+ team = &entry.R2
+ dataScoutedTeam = &entry.DataScouted.R2
case 3:
- entry.R3 = match.TeamNumber
+ team = &entry.R3
+ dataScoutedTeam = &entry.DataScouted.R3
default:
respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown red position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
return
@@ -275,11 +302,14 @@
case "B":
switch match.AlliancePosition {
case 1:
- entry.B1 = match.TeamNumber
+ team = &entry.B1
+ dataScoutedTeam = &entry.DataScouted.B1
case 2:
- entry.B2 = match.TeamNumber
+ team = &entry.B2
+ dataScoutedTeam = &entry.DataScouted.B2
case 3:
- entry.B3 = match.TeamNumber
+ team = &entry.B3
+ dataScoutedTeam = &entry.DataScouted.B3
default:
respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown blue position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
return
@@ -288,6 +318,21 @@
respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown alliance ", match.Alliance, " in match ", strconv.Itoa(int(match.AlliancePosition))))
return
}
+
+ *team = match.TeamNumber
+
+ // Figure out if this team has been data scouted already.
+ *dataScoutedTeam, err = handler.teamHasBeenDataScouted(key, match.TeamNumber)
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, fmt.Sprint(
+ "Failed to determine data scouting status for team ",
+ strconv.Itoa(int(match.AlliancePosition)),
+ " in match ",
+ strconv.Itoa(int(match.MatchNumber)),
+ err))
+ return
+ }
+
assembledMatches[key] = entry
}
@@ -454,6 +499,104 @@
w.Write(builder.FinishedBytes())
}
+func ConvertActionsToStat(submitActions *submit_actions.SubmitActions) (db.Stats2023, error) {
+ overall_time := int64(0)
+ cycles := int64(0)
+ picked_up := false
+ lastPlacedTime := int64(0)
+ stat := db.Stats2023{TeamNumber: string(submitActions.TeamNumber()), MatchNumber: submitActions.MatchNumber(), SetNumber: submitActions.SetNumber(), CompLevel: string(submitActions.CompLevel()),
+ StartingQuadrant: 0, LowCubesAuto: 0, MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 0,
+ LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0, ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0, HighCubes: 0,
+ CubesDropped: 0, LowCones: 0, MiddleCones: 0, HighCones: 0, ConesDropped: 0, AvgCycle: 0, CollectedBy: string(submitActions.CollectedBy()),
+ }
+ // Loop over all actions.
+ for i := 0; i < submitActions.ActionsListLength(); i++ {
+ var action submit_actions.Action
+ if !submitActions.ActionsList(&action, i) {
+ return db.Stats2023{}, errors.New(fmt.Sprintf("Failed to parse submit_actions.Action"))
+ }
+ actionTable := new(flatbuffers.Table)
+ action_type := action.ActionTakenType()
+ if !action.ActionTaken(actionTable) {
+ return db.Stats2023{}, errors.New(fmt.Sprint("Failed to parse sub-action or sub-action was missing"))
+ }
+ if action_type == submit_actions.ActionTypeStartMatchAction {
+ var startMatchAction submit_actions.StartMatchAction
+ startMatchAction.Init(actionTable.Bytes, actionTable.Pos)
+ stat.StartingQuadrant = startMatchAction.Position()
+ } else if action_type == submit_actions.ActionTypePickupObjectAction {
+ var pick_up_action submit_actions.PickupObjectAction
+ pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
+ if picked_up == true {
+ object := pick_up_action.ObjectType().String()
+ auto := pick_up_action.Auto()
+ if object == "kCube" && auto == false {
+ stat.CubesDropped += 1
+ } else if object == "kCube" && auto == true {
+ stat.CubesDroppedAuto += 1
+ } else if object == "kCone" && auto == false {
+ stat.ConesDropped += 1
+ } else if object == "kCube" && auto == true {
+ stat.ConesDroppedAuto += 1
+ }
+ } else {
+ picked_up = true
+ }
+ } else if action_type == submit_actions.ActionTypePlaceObjectAction {
+ var place_action submit_actions.PlaceObjectAction
+ place_action.Init(actionTable.Bytes, actionTable.Pos)
+ if !picked_up {
+ return db.Stats2023{}, errors.New(fmt.Sprintf("Got PlaceObjectAction without corresponding PickupObjectAction"))
+ }
+ object := place_action.ObjectType()
+ level := place_action.ScoreLevel()
+ auto := place_action.Auto()
+ if object == 0 && level == 0 && auto == true {
+ stat.LowCubesAuto += 1
+ } else if object == 0 && level == 0 && auto == false {
+ stat.LowCubes += 1
+ } else if object == 0 && level == 1 && auto == true {
+ stat.MiddleCubesAuto += 1
+ } else if object == 0 && level == 1 && auto == false {
+ stat.MiddleCubes += 1
+ } else if object == 0 && level == 2 && auto == true {
+ stat.HighCubesAuto += 1
+ } else if object == 0 && level == 2 && auto == false {
+ stat.HighCubes += 1
+ } else if object == 1 && level == 0 && auto == true {
+ stat.LowConesAuto += 1
+ } else if object == 1 && level == 0 && auto == false {
+ stat.LowCones += 1
+ } else if object == 1 && level == 1 && auto == true {
+ stat.MiddleConesAuto += 1
+ } else if object == 1 && level == 1 && auto == false {
+ stat.MiddleCones += 1
+ } else if object == 1 && level == 2 && auto == true {
+ stat.HighConesAuto += 1
+ } else if object == 1 && level == 2 && auto == false {
+ stat.HighCones += 1
+ } else {
+ return db.Stats2023{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
+ }
+ picked_up = false
+ if lastPlacedTime != int64(0) {
+ // If this is not the first time we place,
+ // start counting cycle time. We define cycle
+ // time as the time between placements.
+ overall_time += int64(action.Timestamp()) - lastPlacedTime
+ cycles += 1
+ }
+ lastPlacedTime = int64(action.Timestamp())
+ }
+ }
+ if cycles != 0 {
+ stat.AvgCycle = overall_time / cycles
+ } else {
+ stat.AvgCycle = 0
+ }
+ return stat, nil
+}
+
// Handles a Request2023DataScouting request.
type request2023DataScoutingHandler struct {
db Database
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index efd770b..50d6820 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -23,6 +23,7 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
@@ -127,75 +128,98 @@
matches: []db.TeamMatch{
{
MatchNumber: 1, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 5,
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "5",
},
{
MatchNumber: 1, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 42,
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "42",
},
{
MatchNumber: 1, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 600,
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "600",
},
{
MatchNumber: 1, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 971,
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "971",
},
{
MatchNumber: 1, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 400,
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "400",
},
{
MatchNumber: 1, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 200,
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "200",
},
{
MatchNumber: 2, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 6,
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "6",
},
{
MatchNumber: 2, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 43,
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "43",
},
{
MatchNumber: 2, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 601,
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "601",
},
{
MatchNumber: 2, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 972,
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "972",
},
{
MatchNumber: 2, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 401,
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "401",
},
{
MatchNumber: 2, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 201,
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "201",
},
{
MatchNumber: 3, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 1, TeamNumber: 7,
+ Alliance: "R", AlliancePosition: 1, TeamNumber: "7",
},
{
MatchNumber: 3, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 2, TeamNumber: 44,
+ Alliance: "R", AlliancePosition: 2, TeamNumber: "44",
},
{
MatchNumber: 3, SetNumber: 1, CompLevel: "qm",
- Alliance: "R", AlliancePosition: 3, TeamNumber: 602,
+ Alliance: "R", AlliancePosition: 3, TeamNumber: "602",
},
{
MatchNumber: 3, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 1, TeamNumber: 973,
+ Alliance: "B", AlliancePosition: 1, TeamNumber: "973",
},
{
MatchNumber: 3, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 2, TeamNumber: 402,
+ Alliance: "B", AlliancePosition: 2, TeamNumber: "402",
},
{
MatchNumber: 3, SetNumber: 1, CompLevel: "qm",
- Alliance: "B", AlliancePosition: 3, TeamNumber: 202,
+ Alliance: "B", AlliancePosition: 3, TeamNumber: "202",
+ },
+ },
+ // Pretend that we have some data scouting data.
+ stats2023: []db.Stats2023{
+ {
+ TeamNumber: "5", MatchNumber: 1, SetNumber: 1,
+ CompLevel: "qm", StartingQuadrant: 3, LowCubesAuto: 10,
+ MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 0,
+ LowConesAuto: 1, MiddleConesAuto: 2, HighConesAuto: 1,
+ ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 1,
+ HighCubes: 2, CubesDropped: 1, LowCones: 1,
+ MiddleCones: 2, HighCones: 0, ConesDropped: 1,
+ AvgCycle: 34, CollectedBy: "alex",
+ },
+ {
+ TeamNumber: "973", MatchNumber: 3, SetNumber: 1,
+ CompLevel: "qm", StartingQuadrant: 1, LowCubesAuto: 0,
+ MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 2,
+ LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
+ ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
+ HighCubes: 1, CubesDropped: 0, LowCones: 0,
+ MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+ AvgCycle: 53, CollectedBy: "bob",
},
},
}
@@ -218,15 +242,28 @@
// R1, R2, R3, B1, B2, B3
{
1, 1, "qm",
- 5, 42, 600, 971, 400, 200,
+ "5", "42", "600", "971", "400", "200",
+ &request_all_matches_response.ScoutedLevelT{
+ // The R1 team has already been data
+ // scouted.
+ true, false, false, false, false, false,
+ },
},
{
2, 1, "qm",
- 6, 43, 601, 972, 401, 201,
+ "6", "43", "601", "972", "401", "201",
+ &request_all_matches_response.ScoutedLevelT{
+ false, false, false, false, false, false,
+ },
},
{
3, 1, "qm",
- 7, 44, 602, 973, 402, 202,
+ "7", "44", "602", "973", "402", "202",
+ &request_all_matches_response.ScoutedLevelT{
+ // The B1 team has already been data
+ // scouted.
+ false, false, false, true, false, false,
+ },
},
},
}
@@ -391,6 +428,103 @@
}
}
+// Validates that we can request the 2023 stats.
+func TestConvertActionsToStat(t *testing.T) {
+ builder := flatbuffers.NewBuilder(1024)
+ builder.Finish((&submit_actions.SubmitActionsT{
+ TeamNumber: "4244",
+ MatchNumber: 3,
+ SetNumber: 1,
+ CompLevel: "quals",
+ CollectedBy: "katie",
+ ActionsList: []*submit_actions.ActionT{
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypeStartMatchAction,
+ Value: &submit_actions.StartMatchActionT{
+ Position: 1,
+ },
+ },
+ Timestamp: 0,
+ },
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePickupObjectAction,
+ Value: &submit_actions.PickupObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCube,
+ Auto: true,
+ },
+ },
+ Timestamp: 400,
+ },
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePickupObjectAction,
+ Value: &submit_actions.PickupObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCube,
+ Auto: true,
+ },
+ },
+ Timestamp: 800,
+ },
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePlaceObjectAction,
+ Value: &submit_actions.PlaceObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCube,
+ ScoreLevel: submit_actions.ScoreLevelkLow,
+ Auto: true,
+ },
+ },
+ Timestamp: 2000,
+ },
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePickupObjectAction,
+ Value: &submit_actions.PickupObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCone,
+ Auto: false,
+ },
+ },
+ Timestamp: 2800,
+ },
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePlaceObjectAction,
+ Value: &submit_actions.PlaceObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCone,
+ ScoreLevel: submit_actions.ScoreLevelkHigh,
+ Auto: false,
+ },
+ },
+ Timestamp: 3100,
+ },
+ },
+ }).Pack(builder))
+
+ submitActions := submit_actions.GetRootAsSubmitActions(builder.FinishedBytes(), 0)
+ response, err := ConvertActionsToStat(submitActions)
+
+ if err != nil {
+ t.Fatal("Failed to convert actions to stats: ", err)
+ }
+
+ expected := db.Stats2023{
+ TeamNumber: "4244", MatchNumber: 3, SetNumber: 1,
+ CompLevel: "quals", StartingQuadrant: 1, LowCubesAuto: 1,
+ MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 1,
+ LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
+ ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0,
+ HighCubes: 0, CubesDropped: 0, LowCones: 0,
+ MiddleCones: 0, HighCones: 1, ConesDropped: 0,
+ AvgCycle: 1100, CollectedBy: "katie",
+ }
+
+ if expected != response {
+ t.Fatal("Expected ", expected, ", but got ", response)
+ }
+}
+
func TestSubmitNotes(t *testing.T) {
database := MockDatabase{}
scoutingServer := server.NewScoutingServer()
@@ -776,6 +910,16 @@
return database.stats2023, nil
}
+func (database *MockDatabase) ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string) ([]db.Stats2023, error) {
+ var results []db.Stats2023
+ for _, stats := range database.stats2023 {
+ if stats.TeamNumber == teamNumber && stats.MatchNumber == matchNumber && stats.SetNumber == setNumber && stats.CompLevel == compLevel {
+ results = append(results, stats)
+ }
+ }
+ return results, nil
+}
+
func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
return []db.Stats{}, nil
}
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index 44fc958..aef97f7 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -118,7 +118,6 @@
matchStartTimestamp: number = 0;
addAction(action: ActionT): void {
- action.timestamp = Math.floor(Date.now() / 1000);
if (action.type == 'startMatchAction') {
// Unix nanosecond timestamp.
this.matchStartTimestamp = Date.now() * 1e6;
@@ -193,7 +192,7 @@
StartMatchAction.createStartMatchAction(builder, action.position);
actionOffset = Action.createAction(
builder,
- action.timestamp || 0,
+ BigInt(action.timestamp || 0),
ActionType.StartMatchAction,
startMatchActionOffset
);
@@ -208,7 +207,7 @@
);
actionOffset = Action.createAction(
builder,
- action.timestamp || 0,
+ BigInt(action.timestamp || 0),
ActionType.PickupObjectAction,
pickupObjectActionOffset
);
@@ -223,7 +222,7 @@
);
actionOffset = Action.createAction(
builder,
- action.timestamp || 0,
+ BigInt(action.timestamp || 0),
ActionType.AutoBalanceAction,
autoBalanceActionOffset
);
@@ -239,7 +238,7 @@
);
actionOffset = Action.createAction(
builder,
- action.timestamp || 0,
+ BigInt(action.timestamp || 0),
ActionType.PlaceObjectAction,
placeObjectActionOffset
);
@@ -250,7 +249,7 @@
RobotDeathAction.createRobotDeathAction(builder, action.robotOn);
actionOffset = Action.createAction(
builder,
- action.timestamp || 0,
+ BigInt(action.timestamp || 0),
ActionType.RobotDeathAction,
robotDeathActionOffset
);
@@ -264,7 +263,7 @@
);
actionOffset = Action.createAction(
builder,
- action.timestamp || 0,
+ BigInt(action.timestamp || 0),
ActionType.EndMatchAction,
endMatchActionOffset
);
diff --git a/scouting/www/match_list/match_list.component.css b/scouting/www/match_list/match_list.component.css
index e7c071c..f77be5e 100644
--- a/scouting/www/match_list/match_list.component.css
+++ b/scouting/www/match_list/match_list.component.css
@@ -6,6 +6,10 @@
background-color: #dc3545;
}
+button:disabled {
+ background-color: #524143;
+}
+
.blue {
background-color: #0d6efd;
}
@@ -22,3 +26,7 @@
/* minimum touch target size */
height: 44px;
}
+
+div.hidden_row {
+ display: none;
+}
diff --git a/scouting/www/match_list/match_list.component.ts b/scouting/www/match_list/match_list.component.ts
index eb5284f..0deeb11 100644
--- a/scouting/www/match_list/match_list.component.ts
+++ b/scouting/www/match_list/match_list.component.ts
@@ -10,7 +10,7 @@
import {MatchListRequestor} from '@org_frc971/scouting/www/rpc';
type TeamInMatch = {
- teamNumber: number;
+ teamNumber: string;
matchNumber: number;
setNumber: number;
compLevel: string;
@@ -26,21 +26,85 @@
progressMessage: string = '';
errorMessage: string = '';
matchList: Match[] = [];
+ hideCompletedMatches: boolean = true;
constructor(private readonly matchListRequestor: MatchListRequestor) {}
+ // Returns true if the match is fully scouted. Returns false otherwise.
+ matchIsFullyScouted(match: Match): boolean {
+ const scouted = match.dataScouted();
+ return (
+ scouted.r1() &&
+ scouted.r2() &&
+ scouted.r3() &&
+ scouted.b1() &&
+ scouted.b2() &&
+ scouted.b3()
+ );
+ }
+
+ // Returns true if at least one team in this match has been scouted. Returns
+ // false otherwise.
+ matchIsPartiallyScouted(match: Match): boolean {
+ const scouted = match.dataScouted();
+ return (
+ scouted.r1() ||
+ scouted.r2() ||
+ scouted.r3() ||
+ scouted.b1() ||
+ scouted.b2() ||
+ scouted.b3()
+ );
+ }
+
+ // Returns a class for the row to hide it if all teams in this match have
+ // already been scouted.
+ getRowClass(match: Match): string {
+ if (this.hideCompletedMatches && this.matchIsFullyScouted(match)) {
+ return 'hidden_row';
+ }
+ return '';
+ }
+
setTeamInMatch(teamInMatch: TeamInMatch) {
this.selectedTeamEvent.emit(teamInMatch);
}
- teamsInMatch(match: Match): {teamNumber: number; color: 'red' | 'blue'}[] {
+ teamsInMatch(
+ match: Match
+ ): {teamNumber: string; color: 'red' | 'blue'; disabled: boolean}[] {
+ const scouted = match.dataScouted();
return [
- {teamNumber: match.r1(), color: 'red'},
- {teamNumber: match.r2(), color: 'red'},
- {teamNumber: match.r3(), color: 'red'},
- {teamNumber: match.b1(), color: 'blue'},
- {teamNumber: match.b2(), color: 'blue'},
- {teamNumber: match.b3(), color: 'blue'},
+ {
+ teamNumber: match.r1(),
+ color: 'red',
+ disabled: this.hideCompletedMatches && scouted.r1(),
+ },
+ {
+ teamNumber: match.r2(),
+ color: 'red',
+ disabled: this.hideCompletedMatches && scouted.r2(),
+ },
+ {
+ teamNumber: match.r3(),
+ color: 'red',
+ disabled: this.hideCompletedMatches && scouted.r3(),
+ },
+ {
+ teamNumber: match.b1(),
+ color: 'blue',
+ disabled: this.hideCompletedMatches && scouted.b1(),
+ },
+ {
+ teamNumber: match.b2(),
+ color: 'blue',
+ disabled: this.hideCompletedMatches && scouted.b2(),
+ },
+ {
+ teamNumber: match.b3(),
+ color: 'blue',
+ disabled: this.hideCompletedMatches && scouted.b3(),
+ },
];
}
@@ -64,7 +128,22 @@
displayMatchNumber(match: Match): string {
// Only display the set number for eliminations matches.
const setNumber = match.compLevel() == 'qm' ? '' : `${match.setNumber()}`;
- return `${this.matchType(match)} ${setNumber} Match ${match.matchNumber()}`;
+ const matchType = this.matchType(match);
+ const mainText = `${matchType} ${setNumber} Match ${match.matchNumber()}`;
+
+ // When showing the full match list (i.e. not hiding completed matches)
+ // it's useful to know if a match has already been scouted or not.
+ const suffix = (() => {
+ if (this.matchIsFullyScouted(match)) {
+ return '(fully scouted)';
+ } else if (this.matchIsPartiallyScouted(match)) {
+ return '(partially scouted)';
+ } else {
+ return '';
+ }
+ })();
+
+ return `${mainText} ${suffix}`;
}
ngOnInit() {
diff --git a/scouting/www/match_list/match_list.ng.html b/scouting/www/match_list/match_list.ng.html
index e890faf..0ebbe4c 100644
--- a/scouting/www/match_list/match_list.ng.html
+++ b/scouting/www/match_list/match_list.ng.html
@@ -2,8 +2,16 @@
<h2>Matches</h2>
</div>
+<label>
+ <input type="checkbox" [(ngModel)]="hideCompletedMatches" />
+ Hide completed matches
+</label>
+
<div class="container-fluid">
- <div class="row" *ngFor="let match of matchList; index as i">
+ <div
+ *ngFor="let match of matchList; index as i"
+ [ngClass]="'row ' + getRowClass(match)"
+ >
<span class="badge bg-secondary rounded-left">
{{ displayMatchNumber(match) }}
</span>
@@ -18,6 +26,7 @@
})"
class="match-item"
[ngClass]="team.color"
+ [disabled]="team.disabled"
>
{{ team.teamNumber }}
</button>
diff --git a/scouting/www/notes/notes.component.ts b/scouting/www/notes/notes.component.ts
index 21264f2..94a1586 100644
--- a/scouting/www/notes/notes.component.ts
+++ b/scouting/www/notes/notes.component.ts
@@ -111,7 +111,7 @@
setTeamNumber() {
let data: Input = {
teamNumber: this.teamNumberSelection,
- notesData: 'Auto: \nTeleop: \nEngame: ',
+ notesData: 'Match: \nAuto: \nTeleop: \nEngame: ',
keywordsData: {
goodDriving: false,
badDriving: false,
diff --git a/y2023/vision/aprilrobotics.cc b/y2023/vision/aprilrobotics.cc
index 45b0cf9..95ad541 100644
--- a/y2023/vision/aprilrobotics.cc
+++ b/y2023/vision/aprilrobotics.cc
@@ -36,7 +36,8 @@
target_map_sender_(
event_loop->MakeSender<frc971::vision::TargetMap>("/camera")),
image_annotations_sender_(
- event_loop->MakeSender<foxglove::ImageAnnotations>("/camera")) {
+ event_loop->MakeSender<foxglove::ImageAnnotations>("/camera")),
+ rejections_(0) {
tag_family_ = tag16h5_create();
tag_detector_ = apriltag_detector_create();
@@ -89,17 +90,18 @@
aos::monotonic_clock::time_point eof) {
image_size_ = image_grayscale.size();
- std::vector<Detection> detections = DetectTags(image_grayscale, eof);
+ DetectionResult result = DetectTags(image_grayscale, eof);
auto builder = target_map_sender_.MakeBuilder();
std::vector<flatbuffers::Offset<frc971::vision::TargetPoseFbs>> target_poses;
- for (const auto &detection : detections) {
+ for (const auto &detection : result.detections) {
target_poses.emplace_back(BuildTargetPose(detection, builder.fbb()));
}
const auto target_poses_offset = builder.fbb()->CreateVector(target_poses);
auto target_map_builder = builder.MakeBuilder<frc971::vision::TargetMap>();
target_map_builder.add_target_poses(target_poses_offset);
target_map_builder.add_monotonic_timestamp_ns(eof.time_since_epoch().count());
+ target_map_builder.add_rejections(result.rejections);
builder.CheckOk(builder.Send(target_map_builder.Finish()));
}
@@ -177,7 +179,7 @@
return corner_points;
}
-std::vector<AprilRoboticsDetector::Detection> AprilRoboticsDetector::DetectTags(
+AprilRoboticsDetector::DetectionResult AprilRoboticsDetector::DetectTags(
cv::Mat image, aos::monotonic_clock::time_point eof) {
const aos::monotonic_clock::time_point start_time =
aos::monotonic_clock::now();
@@ -273,6 +275,8 @@
.pose = pose,
.pose_error = pose_error,
.distortion_factor = distortion_factor});
+ } else {
+ rejections_++;
}
}
@@ -292,7 +296,7 @@
VLOG(1) << "Took " << chrono::duration<double>(end_time - start_time).count()
<< " seconds to detect overall";
- return results;
+ return {.detections = results, .rejections = rejections_};
}
} // namespace vision
diff --git a/y2023/vision/aprilrobotics.h b/y2023/vision/aprilrobotics.h
index bf9265b..fab2d30 100644
--- a/y2023/vision/aprilrobotics.h
+++ b/y2023/vision/aprilrobotics.h
@@ -31,6 +31,11 @@
double distortion_factor;
};
+ struct DetectionResult {
+ std::vector<Detection> detections;
+ size_t rejections;
+ };
+
AprilRoboticsDetector(aos::EventLoop *event_loop,
std::string_view channel_name);
~AprilRoboticsDetector();
@@ -43,8 +48,8 @@
// Helper function to store detection points in vector of Point2f's
std::vector<cv::Point2f> MakeCornerVector(const apriltag_detection_t *det);
- std::vector<Detection> DetectTags(cv::Mat image,
- aos::monotonic_clock::time_point eof);
+ DetectionResult DetectTags(cv::Mat image,
+ aos::monotonic_clock::time_point eof);
const std::optional<cv::Mat> extrinsics() const { return extrinsics_; }
const cv::Mat intrinsics() const { return intrinsics_; }
@@ -78,6 +83,8 @@
frc971::vision::ImageCallback image_callback_;
aos::Sender<frc971::vision::TargetMap> target_map_sender_;
aos::Sender<foxglove::ImageAnnotations> image_annotations_sender_;
+
+ size_t rejections_;
};
} // namespace vision
diff --git a/y2023/www/BUILD b/y2023/www/BUILD
index f2df646..404247e 100644
--- a/y2023/www/BUILD
+++ b/y2023/www/BUILD
@@ -35,6 +35,7 @@
"//frc971/control_loops:control_loops_ts_fbs",
"//frc971/control_loops/drivetrain:drivetrain_status_ts_fbs",
"//frc971/control_loops/drivetrain/localization:localizer_output_ts_fbs",
+ "//frc971/vision:target_map_ts_fbs",
"//y2023/control_loops/superstructure:superstructure_status_ts_fbs",
"//y2023/localizer:status_ts_fbs",
"//y2023/localizer:visualization_ts_fbs",
diff --git a/y2023/www/field_handler.ts b/y2023/www/field_handler.ts
index b8656a2..2f62c7d 100644
--- a/y2023/www/field_handler.ts
+++ b/y2023/www/field_handler.ts
@@ -1,14 +1,14 @@
-import {ByteBuffer} from 'flatbuffers';
-
+import {ByteBuffer} from 'flatbuffers'
import {ClientStatistics} from '../../aos/network/message_bridge_client_generated'
import {ServerStatistics, State as ConnectionState} from '../../aos/network/message_bridge_server_generated'
-import {Connection} from '../../aos/network/www/proxy';
-import {ZeroingError} from '../../frc971/control_loops/control_loops_generated';
-import {Status as DrivetrainStatus} from '../../frc971/control_loops/drivetrain/drivetrain_status_generated';
-import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated';
+import {Connection} from '../../aos/network/www/proxy'
+import {ZeroingError} from '../../frc971/control_loops/control_loops_generated'
+import {Status as DrivetrainStatus} from '../../frc971/control_loops/drivetrain/drivetrain_status_generated'
+import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated'
+import {TargetMap} from '../../frc971/vision/target_map_generated'
import {ArmState, ArmStatus, EndEffectorState, Status as SuperstructureStatus} from '../control_loops/superstructure/superstructure_status_generated'
-import {RejectionReason} from '../localizer/status_generated';
-import {TargetEstimateDebug, Visualization} from '../localizer/visualization_generated';
+import {RejectionReason} from '../localizer/status_generated'
+import {TargetEstimateDebug, Visualization} from '../localizer/visualization_generated'
import {Class} from '../vision/game_pieces_generated'
import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
@@ -38,6 +38,10 @@
(document.getElementById('theta') as HTMLElement);
private imagesAcceptedCounter: HTMLElement =
(document.getElementById('images_accepted') as HTMLElement);
+ // HTML elements for rejection reasons for individual pis. Indices
+ // corresponding to RejectionReason enum values will be for those reasons. The
+ // final row will account for images rejected by the aprilrobotics detector
+ // instead of the localizer.
private rejectionReasonCells: HTMLElement[][] = [];
private messageBridgeDiv: HTMLElement =
(document.getElementById('message_bridge_status') as HTMLElement);
@@ -64,8 +68,6 @@
(document.getElementById('arm_distal') as HTMLElement);
private zeroingFaults: HTMLElement =
(document.getElementById('zeroing_faults') as HTMLElement);
- _
-
constructor(private readonly connection: Connection) {
(document.getElementById('field') as HTMLElement).appendChild(this.canvas);
@@ -75,7 +77,7 @@
{
const row = document.createElement('div');
const nameCell = document.createElement('div');
- nameCell.innerHTML = "Rejection Reason";
+ nameCell.innerHTML = 'Rejection Reason';
row.appendChild(nameCell);
for (const pi of PIS) {
const nodeCell = document.createElement('div');
@@ -98,7 +100,25 @@
for (const pi of PIS) {
const valueCell = document.createElement('div');
valueCell.innerHTML = 'NA';
- this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(valueCell);
+ this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
+ valueCell);
+ row.appendChild(valueCell);
+ }
+ document.getElementById('vision_readouts').appendChild(row);
+ }
+
+ // Add rejection reason row for aprilrobotics rejections.
+ {
+ const row = document.createElement('div');
+ const nameCell = document.createElement('div');
+ nameCell.innerHTML = 'Rejected by aprilrobotics';
+ row.appendChild(nameCell);
+ this.rejectionReasonCells.push([]);
+ for (const pi of PIS) {
+ const valueCell = document.createElement('div');
+ valueCell.innerHTML = 'NA';
+ this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
+ valueCell);
row.appendChild(valueCell);
}
document.getElementById('vision_readouts').appendChild(row);
@@ -121,6 +141,13 @@
this.handleLocalizerDebug(Number(pi), data);
});
}
+ for (const pi in PIS) {
+ // Make unreliable to reduce network spam.
+ this.connection.addHandler(
+ '/' + PIS[pi] + '/camera', 'frc971.vision.TargetMap', (data) => {
+ this.handlePiTargetMap(pi, data);
+ });
+ }
this.connection.addHandler(
'/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
this.handleDrivetrainStatus(data);
@@ -151,7 +178,7 @@
const debug = this.localizerImageMatches.get(now);
if (debug.statistics()) {
- if (debug.statistics().rejectionReasonsLength() ==
+ if ((debug.statistics().rejectionReasonsLength() + 1) ==
this.rejectionReasonCells.length) {
for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
++ii) {
@@ -164,6 +191,13 @@
}
}
+ private handlePiTargetMap(pi: string, data: Uint8Array): void {
+ const fbBuffer = new ByteBuffer(data);
+ const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
+ this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
+ .innerHTML = targetMap.rejections().toString();
+ }
+
private handleLocalizerOutput(data: Uint8Array): void {
const fbBuffer = new ByteBuffer(data);
this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);